/* ==========================================================================
   Parking Luxembourg — design system
   Light "Daylight" theme: colourful basemap + frosted-glass floating panels.
   Conventions: design tokens in :root, sections below, no magic numbers in rules.
   ========================================================================== */

:root {
  /* tell the UA we support both schemes so native form controls, scrollbars and the
     <select> dropdown adopt the right palette */
  color-scheme: light dark;

  /* surfaces & text */
  --bg: #ECEFF4;
  --surface: #FCFCFE;
  --surface-alt: #E6EAF2;
  --text: #0E1422;
  --text-dim: #5B6577;
  /* darkened from #8A93A3 (which failed WCAG AA on white) to ~4.6:1 — this token labels the list
     "key" that decodes the whole UI plus the chart axis numbers, so it must be legible */
  --text-faint: #565F6E;
  --border: #D8DEEA;

  /* spacing scale — 4px base; re-maps the ad-hoc rem one-offs to a shared grid */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* brand / chrome accent (never used to encode data) */
  --accent: #4F46E5;
  --accent-soft: #EEF0FE;

  /* occupancy — two-token model: vivid FILL for shapes, INK for text (AA on white) */
  --free-fill: #10B981;  --busy-fill: #F59E0B;  --full-fill: #EF4444;  --unknown-fill: #94A3B8;
  --free-ink:  #047857;  --busy-ink:  #B45309;  --full-ink:  #DC2626;  --unknown-ink:  #64748B;

  /* source colours (categorical): VDL city vs CFL park-and-ride */
  --src-vdl: #6366F1;
  --src-cfl: #0284C7; /* was #0EA5E9 (sky-500, 2.70:1 on white pill — fails WCAG 1.4.11 3:1); sky-600 gives 4.14:1 */

  /* glass */
  --glass-bg: rgba(255, 255, 255, 0.66);
  --glass-border: rgba(255, 255, 255, 0.85);
  --glass-blur: blur(26px) saturate(1.5);

  /* elevation & shape */
  --radius: 22px;
  --radius-sm: 14px;
  --shadow: 0 1px 2px rgba(14, 20, 34, 0.06), 0 12px 32px rgba(14, 20, 34, 0.10);
  --shadow-sm: 0 4px 16px rgba(14, 20, 34, 0.08);
  --shadow-glass: 0 1px 0 rgba(255, 255, 255, 0.6) inset, 0 14px 40px rgba(14, 20, 34, 0.14);

  /* stacking layers — single source of truth so a new floating surface can never sit
     below Leaflet's own panes/controls again (the bug that hid the deep-link + 404 pages).
     Leaflet paints its controls at z-index 1000, so any panel that overlaps them must be ≥1001. */
  --z-overlay: 900;          /* desktop floating panels (.cmd/.peek) — corners, never over controls */
  --z-leaflet-controls: 1000;/* Leaflet's own zoom/attribution layer (reference; set by leaflet.css) */
  --z-panel: 1100;           /* full-bleed panels that cover the map + its controls (mobile sheets, /parking, 404) */
  --z-nav: 1200;             /* top nav — always reachable above every panel */
}

/* --- dark theme (tokens) -------------------------------------------------- */
/* Token override — the design system reads from :root vars, so the whole app re-themes from here.
   Occupancy INK tokens flip to brighter tints (the AA-on-white inks vanish on dark); the vivid FILL
   hues stay (they read on both). Direct-property dark overrides (.topnav, mobile panels, skeletons…)
   live at the END of this file so they win by source order over the later light rules they mirror —
   placing them here let those later light rules override them (white panels in dark mode). Basemap
   swaps to CARTO dark_all in leaflet-interop.js when this scheme is active. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #13171F;
    --surface: #1C2330;
    --surface-alt: #262F3F;
    --text: #DCE2EC;
    --text-dim: #9BA6B8;
    --text-faint: #97A2B4;          /* ~4.6:1 on the dark surface */
    --border: #37425A;

    --accent: #818CF8;              /* lightened indigo for AA on dark */
    --accent-soft: rgba(129, 140, 248, 0.24); /* raised from 0.16 — brings .badge, .sortbar.on, .lang.on above 4.5:1 on panel bg */

    --free-ink: #34D399;  --busy-ink: #FBBF24;  --full-ink: #F87171;  --unknown-ink: #9AA6B8;

    --src-vdl: #818CF8;  --src-cfl: #38BDF8;

    --glass-bg: rgba(28, 35, 48, 0.66);
    --glass-border: rgba(255, 255, 255, 0.10);
    --glass-blur: blur(26px) saturate(1.4);

    --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 12px 32px rgba(0, 0, 0, 0.55);
    --shadow-sm: 0 4px 16px rgba(0, 0, 0, 0.45);
    --shadow-glass: 0 1px 0 rgba(255, 255, 255, 0.06) inset, 0 14px 40px rgba(0, 0, 0, 0.6);
  }
}

/* --- base ---------------------------------------------------------------- */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* kill the default translucent-grey iOS tap box on every control (looks broken on glass) */
* { -webkit-tap-highlight-color: transparent; }
/* tappable controls: drop the 300ms double-tap-zoom delay */
.prow, .nav-link, .lang, .seg button, .star, .homebtn, .dirbtn, .peek .close, .empty-clear, .search-clear { touch-action: manipulation; }
html, body { height: 100%; }

/* --- accessibility -------------------------------------------------------- */
/* A single visible focus ring for keyboard users on every interactive surface (rows, buttons, links,
   inputs, the language switch). :focus-visible keeps it OFF for mouse/touch so the design stays clean.
   Several controls set outline:none for the mouse path — this restores it for keyboard. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.search:focus-within { box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 3px var(--accent); }
/* honour the OS "reduce motion" setting — kill the splash bob, list shimmer, live-dot pulse, panel
   slide-in and marker transitions for users who get motion sick or have vestibular disorders */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important; scroll-behavior: auto !important;
  }
}
body {
  font-family: 'Inter var', 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}
/* dvh tracks iOS Safari's collapsing address bar; vh is the fallback for older browsers */
#app { height: 100vh; height: 100dvh; }
a { color: var(--accent); text-decoration: none; }
h1, h2, h3 { font-weight: 680; letter-spacing: -0.022em; }
h1:focus, h1:focus-visible { outline: none; } /* FocusOnNavigate targets h1; no visible ring on a heading */
.tabnum { font-variant-numeric: tabular-nums; }
.loading { padding: 1.4rem; color: var(--text-dim); }

/* --- glass surface ------------------------------------------------------- */
.card, .glass {
  position: relative;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-glass);
}

/* --- Blazor runtime error banner (hidden until the framework sets display:block) --- */
#blazor-error-ui {
    display: none;
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 10000;
    padding: 0.6rem 1.25rem calc(0.7rem + env(safe-area-inset-bottom));
    background: #FEF3C7; color: #78350F; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }
#blazor-error-ui .reload { color: inherit; }

/* --- immersive shell (map behind, panels float over) --------------------- */
.shell { position: relative; width: 100vw; height: 100vh; height: 100dvh; overflow: hidden; background: var(--bg); }
.mapfill { position: absolute; inset: 0; }
.mapfill > div { width: 100%; height: 100%; }

/* --- top nav + language switcher ----------------------------------------- */
.topnav {
  position: absolute; top: max(1.2rem, env(safe-area-inset-top)); left: 50%; transform: translateX(-50%); z-index: var(--z-nav);
  display: flex; gap: 0.25rem; padding: 0.3rem;
  /* near-opaque so map pills + panel edges never bleed through the centered nav */
  background: rgba(255, 255, 255, 0.93);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset, 0 8px 28px rgba(14, 20, 34, 0.18);
}
.nav-link {
  display: inline-block; padding: 0.4rem 1rem; border-radius: var(--radius-sm);
  color: var(--text-dim); font-size: 0.88rem; font-weight: 560;
  transition: color .15s, background .15s;
}
@media (hover: hover) { .nav-link:hover { color: var(--text); } }
.nav-link:active { color: var(--text); background: var(--accent-soft); }
.nav-link.active { color: var(--accent); background: var(--accent-soft); }
.nav-sep { align-self: center; width: 1px; height: 18px; margin: 0 0.35rem; background: var(--border); }
.lang {
  padding: 0.4rem 0.5rem; border: none; border-radius: 9px; background: none;
  color: var(--text-dim); font-size: 0.74rem; font-weight: 700; cursor: pointer;
  transition: color .12s, background .12s;
}
@media (hover: hover) { .lang:hover { color: var(--text); } }
.lang:active { color: var(--text); background: var(--accent-soft); }
.lang.on { color: var(--accent); background: var(--accent-soft); }

/* --- command panel (home, left) ------------------------------------------ */
.cmd {
  position: absolute; top: 1.2rem; left: 1.2rem; bottom: 1.2rem; width: 376px; z-index: var(--z-overlay);
  display: flex; flex-direction: column; padding: 1.3rem;
}
.kx { font-size: 0.62rem; font-weight: 680; letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-dim); }
.cmd h1 { margin-top: 0.2rem; font-size: 1.35rem; }
.search {
  display: flex; align-items: center; gap: 0.5rem; margin: 0.9rem 0;
  padding: 0.6rem 0.8rem; background: var(--surface-alt); border-radius: var(--radius-sm);
}
.search input {
  width: 100%; border: none; outline: none; background: transparent;
  color: var(--text); font-size: 0.85rem;
}
.search input::placeholder { color: var(--text-dim); }
.search-clear {
  flex: none; padding: 0 0 0 4px; border: none; background: none;
  color: var(--text-faint); font-size: 0.85rem; line-height: 1; cursor: pointer;
  transition: color .12s;
}
@media (hover: hover) { .search-clear:hover { color: var(--text-dim); } }
.search-clear:active { color: var(--text-dim); }

/* source segmented control */
.seg { display: flex; gap: 4px; margin-bottom: 0.7rem; padding: 3px; background: var(--surface-alt); border-radius: var(--radius-sm); }
.seg button {
  flex: 1; padding: 0.42rem; border: none; border-radius: 9px; background: none;
  color: var(--text-dim); font-size: 0.78rem; font-weight: 560; cursor: pointer;
  transition: background .12s, color .12s;
}
.seg button:active { color: var(--accent); background: var(--surface); }
.seg button.on { color: var(--accent); background: var(--surface); box-shadow: var(--shadow-sm); }
/* ★ favourites filter: a compact fixed-width slot so City / Park & Ride keep their room */
.seg .segfav { flex: 0 0 auto; min-width: 34px; padding-left: 0.5rem; padding-right: 0.5rem; font-size: 0.95rem; line-height: 1; }
.seg .segfav.on { color: #F59E0B; background: var(--surface); box-shadow: var(--shadow-sm); }

/* source legend + dots */
.srclegend { display: flex; gap: 1.1rem; margin-bottom: 0.7rem; padding: 0 0.2rem; font-size: 0.7rem; color: var(--text-dim); }
.sd { display: inline-block; width: 8px; height: 8px; margin-right: 5px; border-radius: 50%; vertical-align: middle; }
.sd.vdl { background: var(--src-vdl); }
/* CFL as a ring (not just a different hue) — a non-colour channel for a11y */
.sd.cfl { background: transparent; border: 2px solid var(--src-cfl); }

/* tappable (i) disclosure: the CFL/VDL meaning must be reachable without hover (touch) */
.srctip { position: relative; }
.srctip summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center;
  white-space: nowrap; user-select: none;
}
.srctip summary::-webkit-details-marker { display: none; }
.srctip .info {
  margin-left: 5px; width: 13px; height: 13px; border-radius: 50%;
  background: var(--surface-alt); color: var(--text-dim);
  font-size: 0.58rem; font-style: normal; font-weight: 700; line-height: 13px;
  text-align: center;
}
.srctip[open] .info { background: var(--accent); color: #fff; }
.srctip-pop {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 5;
  width: max-content; max-width: 230px; padding: 0.5rem 0.6rem;
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-sm);
  font-size: 0.7rem; line-height: 1.35;
}
/* the CFL chip sits mid-panel; anchor its popover to the right so it stays in-panel */
.srctip.r .srctip-pop { left: auto; right: 0; }

/* sort toggle */
.sortbar { display: flex; gap: 4px; margin-bottom: 0.6rem; }
.sortbar button { flex: 1; padding: 0.35rem; border: 1px solid var(--border); border-radius: 9px; background: var(--surface); color: var(--text-dim); font-size: 0.72rem; font-weight: 600; cursor: pointer; transition: color .12s, border-color .12s, background .12s; }
.sortbar button.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.sortbar button.busy { opacity: 0.7; animation: pulse 1.1s infinite; }
/* geolocation failure notice (denied / timed out / outside Luxembourg) — was previously silent */
.geo-err { margin-bottom: 0.6rem; padding: 0.45rem 0.6rem; border-radius: 10px; background: rgba(245, 158, 11, 0.13); color: var(--busy-ink); font-size: 0.72rem; line-height: 1.35; }
/* car-park street address in the detail peek (CFL P+R carry one; VDL don't) */
.peek-addr { margin-top: 0.5rem; font-size: 0.78rem; color: var(--text-dim); line-height: 1.35; }

.free.word { font-size: 0.72rem; }
/* "free" unit under the count makes each row self-explanatory (no list legend needed) */
.prow .free { text-align: right; line-height: 1.06; }
.prow .free .u { display: block; font-size: 0.65rem; font-weight: 600; letter-spacing: .02em; color: var(--text-faint); } /* was 0.58rem = 9.28px; raised to match chart-axis */

/* empty (no-results) state: fills the list void with a message + clear-search affordance */
.empty { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.8rem; padding: 1.5rem; text-align: center; }
.empty-msg { font-size: 0.85rem; color: var(--text-dim); line-height: 1.4; }
.empty-clear { padding: 0.45rem 0.9rem; border: 1px solid var(--border); border-radius: 9px; background: var(--surface); color: var(--accent); font-size: 0.78rem; font-weight: 600; cursor: pointer; transition: background .12s, border-color .12s; }
@media (hover: hover) { .empty-clear:hover { border-color: var(--accent); background: var(--accent-soft); } }
.empty-clear:active { border-color: var(--accent); background: var(--accent-soft); }

/* ranked parking list */
.plist { flex: 1; display: flex; flex-direction: column; margin: 0 -0.4rem; padding: 0 0.4rem; overflow-y: auto; overscroll-behavior: contain; }
.plist::-webkit-scrollbar { width: 6px; }
.plist::-webkit-scrollbar-thumb { background: #0E142222; border-radius: 3px; }
.prow {
  position: relative; display: flex; align-items: center; gap: 0.8rem; min-height: 54px;
  padding: 0.55rem 0.5rem; border-bottom: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; transition: background .12s;
}
@media (hover: hover) { .prow:hover { background: var(--bg); } }
.prow:active { background: var(--bg); }
.prow.sel { background: var(--accent-soft); }
.prow.sel::before { content: ''; position: absolute; left: 0; top: 8px; bottom: 8px; width: 3px; border-radius: 3px; background: var(--accent); }
.prow .nm { flex: 1; min-width: 0; }
.prow .nm .t { font-size: 0.9rem; font-weight: 560; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.15; }
.prow .nm .s { font-size: 0.7rem; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prow .nm .s .sd { vertical-align: baseline; }
.prow .free { font-size: 1.05rem; font-weight: 720; font-variant-numeric: tabular-nums; }
.prow .free.ok { color: var(--free-ink); }
.prow .free.busy { color: var(--busy-ink); }
.prow .free.full { color: var(--full-ink); }
.prow .free.unknown { color: var(--unknown-ink); }
/* magnitude bar — width set inline by Blazor as a percentage of the list max; inherits occupancy color */
.free-bar {
  display: block; height: 3px; border-radius: 2px;
  background: currentColor; opacity: 0.35; margin-top: 2px; margin-left: auto;
}

.cmd .foot { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.8rem; font-size: 0.72rem; color: var(--text-dim); }
.cmd .foot .live { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--free-fill); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.13); animation: pulse 1.6s infinite; }
/* feed gone quiet (newest reading older than the 5-min cadence): a still grey dot, not a pulsing
   green one, so the "live" cue can't lie when data is delayed */
.cmd .foot .live.off { background: var(--text-faint); box-shadow: none; animation: none; }
@keyframes pulse { 50% { opacity: 0.4; } }
/* quiet link to the about/privacy page at the foot of the command panel */
.cmd-about { margin-top: 0.5rem; font-size: 0.64rem; color: var(--text-faint); text-align: center; }
@media (hover: hover) { .cmd-about:hover { color: var(--text-dim); } }

/* --- radial occupancy gauge ---------------------------------------------- */
.gauge { position: relative; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; flex: none; }
.gauge .inner { position: absolute; inset: 4px; border-radius: 50%; background: var(--surface); display: grid; place-items: center; }
.gauge b { position: relative; z-index: 1; font-size: 0.7rem; font-weight: 700; } /* was 0.6rem = 9.6px; raised to 11.2px for low-vision readability */

/* --- detail peek (home, right) ------------------------------------------- */
.peek { position: absolute; top: 1.2rem; right: 1.2rem; width: 320px; z-index: var(--z-overlay); padding: 1.3rem; animation: slidein .25s ease; }
@keyframes slidein { from { opacity: 0; transform: translateX(12px); } to { opacity: 1; transform: none; } }
.peek-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.peek-head .t { flex: 1; min-width: 0; font-size: 1.15rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pk-actions { display: flex; align-items: center; gap: 0.1rem; flex: none; }
/* both header icon buttons share one fixed 28×28 box, glyph centered, so the star and
   ✕ sit on the same baseline at equal size (no ~2px vertical drift between them) */
.pk-actions button { width: 28px; height: 28px; padding: 0; display: grid; place-items: center; }
.peek .close { border: none; background: none; color: var(--text-dim); font-size: 1.1rem; line-height: 1; cursor: pointer; border-radius: 8px; }
@media (hover: hover) { .peek .close:hover { background: var(--surface-alt); color: var(--text); } }
.peek .close:active { background: var(--surface-alt); color: var(--text); }
.peek .badges { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; }
.peek-hero { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-top: 1rem; }
.peek-cap { margin-top: 0.35rem; font-size: 0.82rem; color: var(--text-dim); }
.verdict { margin-top: 0.45rem; font-size: 0.78rem; font-weight: 650; }
.verdict.ok { color: var(--free-ink); }
.verdict.busy { color: var(--busy-ink); }
.verdict.full { color: var(--full-ink); }
.verdict.unknown { color: var(--text-dim); }
.chart-cap { margin-top: 0.7rem; font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.peek-gauge { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.peek-gauge span { font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.peek .stats { display: flex; gap: 1.1rem; margin-top: 0.7rem; font-size: 0.74rem; color: var(--text-dim); }
.peek .stats b { color: var(--text); font-variant-numeric: tabular-nums; }

.badge { padding: 0.22rem 0.5rem; border-radius: 7px; background: var(--accent-soft); color: var(--accent); font-size: 0.64rem; font-weight: 600; letter-spacing: 0.03em; }

/* hero free-count (peek + detail page) */
.big { margin-top: 0; font-size: 3.1rem; font-weight: 780; line-height: 1; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.big small { font-size: 0.85rem; font-weight: 400; letter-spacing: 0; color: var(--text-dim); }
.big.ok { color: var(--free-ink); }
.big.busy { color: var(--busy-ink); }
.big.full { color: var(--full-ink); }
.big.unknown { color: var(--unknown-ink); }

/* --- charts -------------------------------------------------------------- */
.chart { display: block; width: 100%; height: 78px; margin-top: 0.6rem; }
.chart-axis { display: flex; justify-content: space-between; margin-top: 3px; padding: 0 2px; font-size: 0.65rem; color: var(--text-faint); } /* was 0.58rem = 9.28px; raised to 10.4px for low-vision readability */
.chart-axis--abs { display: block; position: relative; height: 1.4em; padding: 0; }
.chart-axis--abs span { position: absolute; transform: translateX(-50%); }

/* --- detail page (deep-link, scrollable) --------------------------------- */
/* z-index is REQUIRED: this full-bleed page overlaps Leaflet's control layer (z 1000); without an
   explicit layer it inherits auto/0 and the live map paints over it (the /parking/{id} + 404 blank-map bug). */
.page { position: absolute; inset: 0; z-index: var(--z-panel); padding: 4.6rem 1.4rem 3rem; overflow-y: auto; overscroll-behavior: contain; }
.panel { padding: 1.6rem; }
.panel-centered { max-width: 560px; margin: 0 auto; }
.panel h1 { font-size: 1.5rem; }
.panel h3 { margin-top: var(--space-5); font-size: 1.05rem; }
.panel .sub { margin-top: 0.2rem; font-size: 0.85rem; color: var(--text-dim); }
.panel p.sub { margin-top: var(--space-2); }
.panel .back { margin-top: var(--space-5); }

/* --- weekly pattern (heatmap + insight cards, on the per-park detail page) --- */
/* collecting note: friendly, distinct from a loading skeleton (no shimmer) */
.collecting { display: flex; align-items: center; gap: 0.6rem; margin-top: 1rem; padding: 0.7rem 0.9rem; border: 1px solid var(--border); border-radius: 12px; background: rgba(79, 70, 229, 0.07); font-size: 0.82rem; color: var(--text-dim); }
.collecting::before { content: '🌱'; font-size: 1.1rem; flex: none; line-height: 1; }
/* In the collecting state we keep the real axes and let the few populated cells show their
   colour (so accumulating data is visible), while empty cells fall back to the .nodata hatch —
   never the shimmering loading skeleton. */

/* custom select */
.select-wrap { position: relative; display: inline-block; margin-top: 1rem; }
.select-wrap::after {
  content: ''; position: absolute; right: 14px; top: 50%; width: 8px; height: 8px;
  border-right: 2px solid var(--text-dim); border-bottom: 2px solid var(--text-dim);
  transform: translateY(-65%) rotate(45deg); pointer-events: none;
}
select {
  appearance: none; -webkit-appearance: none; cursor: pointer; outline: none;
  padding: 0.55rem 2.2rem 0.55rem 0.9rem; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font-size: 0.9rem; font-weight: 560; box-shadow: var(--shadow-sm);
}
select:focus { box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 3px var(--accent); }

/* heatmap */
/* No vertical gap between rows + square cells so a column of data-bearing hours reads as ONE
   contiguous heat stripe — matching the unbroken hatched look of an all-empty column. Horizontal
   day separation stays on .hm-row's column gap. Outer corners are rounded on the container. */
.heatmap { display: flex; flex-direction: column; gap: 0; margin-top: 1.2rem; }
.hm-row { display: grid; grid-template-columns: 38px repeat(7, 1fr); gap: 2px; align-items: center; }
.hm-label { font-size: 0.64rem; color: var(--text); font-weight: 500; }
.hm-cell { height: 16px; border-radius: 0; background: var(--surface-alt); }
/* "no data" cells get a faint cool diagonal hatch so they never read as a low-occupancy colour.
   Overrides the inline faint-grey background the markup sets for null cells. */
.hm-cell.nodata {
  background: repeating-linear-gradient(45deg,
    rgba(120,130,150,0.10) 0 3px, rgba(120,130,150,0.02) 3px 6px) !important;
}
/* "now" marker: a neutral dark hairline ring sits within the cell footprint so it reads as a
   deliberate annotation on the occupancy ramp. (Was an indigo accent ring — off-palette against
   the green→red Free→Full scale and undecodable without a legend; a neutral hairline doesn't
   compete with the heat colours.) */
.hm-cell.now { box-shadow: inset 0 0 0 1.5px rgba(14, 20, 34, 0.85); position: relative; z-index: 1; }
.hm-x { display: grid; grid-template-columns: 38px repeat(7, 1fr); gap: 2px; margin-top: 0.4rem; font-size: 0.64rem; color: var(--text); font-weight: 600; text-align: center; }
/* Center the legend under the 7 data columns (offset by the same 38px label gutter as .hm-row/.hm-x)
   and give it extra top margin so it reads as a deliberate caption beneath the weekday axis. */
.hm-legend { display: flex; align-items: center; justify-content: center; gap: 0.5rem; padding-left: 38px; margin-top: 1.2rem; font-size: 0.66rem; color: var(--text-dim); }
.hm-legend .bar { width: 180px; height: 10px; border-radius: 5px; background: linear-gradient(90deg, #6EE7B7, var(--free-fill), var(--busy-fill), #F97316, var(--full-fill)); }

/* insight cards (frosted, tinted) */
.icard { padding: 1.1rem 1.2rem; border-radius: 16px; }
.icard.glass {
  background: rgba(255, 255, 255, 0.58);
  backdrop-filter: blur(20px) saturate(1.4); -webkit-backdrop-filter: blur(20px) saturate(1.4);
  border: 1px solid var(--glass-border); box-shadow: 0 10px 30px rgba(14, 20, 34, 0.10);
}
.icard.ok { background: rgba(16, 185, 129, 0.16); }
.icard.warn { background: rgba(245, 158, 11, 0.16); }
.icard.info { background: rgba(79, 70, 229, 0.13); }
.icard.hint { background: rgba(255, 255, 255, 0.5); }
.icard.hint .icard-detail { margin: 0; }
.icard-label { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--accent); }
.icard.ok .icard-label { color: var(--free-ink); }
.icard.warn .icard-label { color: var(--busy-ink); }
.icard-value { margin-top: 0.2rem; font-size: 1.9rem; font-weight: 760; letter-spacing: -0.02em; color: var(--text); font-variant-numeric: tabular-nums; }
.icard-detail { margin-top: 0.1rem; font-size: 0.82rem; color: var(--text-dim); }

/* --- Leaflet map --------------------------------------------------------- */
.leaflet-container { background: #EEF1F6 !important; font-family: inherit; }
.leaflet-tile-pane { filter: saturate(1.12) contrast(1.03); } /* gentle vibrancy on the basemap */
.leaflet-control-zoom { border: none !important; border-radius: 12px !important; overflow: hidden; box-shadow: var(--shadow); }
.leaflet-control-zoom a {
  width: 38px !important; height: 38px !important; line-height: 38px !important; font-size: 19px !important;
  background: rgba(255,255,255,0.92) !important; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text) !important; border: none !important; transition: background .12s, color .12s;
}
.leaflet-control-zoom a:first-child { border-bottom: 1px solid var(--border) !important; }
.leaflet-control-zoom a:hover { background: #fff !important; color: var(--accent) !important; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
/* keep the © OSM © CARTO line off the viewport edge so "CARTO" is never clipped */
.leaflet-control-attribution {
    padding: 0 8px !important; border-top-left-radius: 8px;
    background: rgba(255, 255, 255, 0.85) !important;
    color: var(--text-faint) !important;
    font-size: 0.62rem;
  }

/* glass pill markers: source dot + name + trend arrow + free count
   --sc (source colour) and --ink (occupancy ink) are set inline by JS */
.pmark {
  display: flex; align-items: center; gap: 5px; height: 28px; padding: 0 10px;
  width: max-content; transform: translate(-50%, -50%); transform-origin: center;
  border-radius: 14px; background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--glass-border); box-shadow: 0 2px 9px rgba(14, 20, 34, 0.20);
  font-size: 12px; font-weight: 600; white-space: nowrap; cursor: pointer; transition: transform .15s;
}
.pmark-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--sc); box-shadow: 0 0 0 2px color-mix(in srgb, var(--sc) 28%, transparent); }
.pmark-name { color: var(--text); } /* was hardcoded #1F2933 — swap to token so it adapts when .pmark gets dark bg */
.pmark-arrow { color: var(--ink); font-weight: 800; }
.pmark-n { color: var(--ink); font-weight: 800; font-variant-numeric: tabular-nums; }
.pmark.sel { transform: translate(-50%, -50%) scale(1.14); z-index: 1000; background: rgba(255, 255, 255, 0.98); box-shadow: 0 0 0 3px #fff, 0 0 0 5px var(--sc), 0 4px 16px rgba(14, 20, 34, 0.30); }

.pcluster {
  display: flex; align-items: center; justify-content: center; width: 38px; height: 38px;
  border-radius: 50%; background: rgba(255, 255, 255, 0.92); backdrop-filter: blur(6px);
  border: 1px solid var(--border); box-shadow: 0 2px 10px rgba(14, 20, 34, 0.20);
  color: var(--text); font-size: 13px; font-weight: 750; font-variant-numeric: tabular-nums;
}

/* --- favourites, home, navigation --------------------------------------- */
.prow .star, .peek-head .star {
  flex: none; padding: 0 0 0 4px; border: none; background: none;
  color: var(--text-faint); font-size: 1rem; line-height: 1; cursor: pointer; transition: color .12s, transform .1s;
}
@media (hover: hover) { .star:hover { color: #F59E0B; transform: scale(1.15); } }
.star:active { color: #F59E0B; transform: scale(1.15); }
.star.on { color: #F59E0B; }

.cmdbar { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.7rem; }
.cmdbar .srclegend { margin-bottom: 0; }
.homebtn {
  flex: none; width: 30px; height: 30px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--text-dim); font-size: 1rem; line-height: 1; cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
@media (hover: hover) { .homebtn:hover { color: var(--text); } }
.homebtn:active { color: var(--text); background: var(--surface-alt); }
.homebtn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.homebtn.busy { opacity: 0.55; }

.peek-head .star { font-size: 1.25rem; padding: 0; }
.badge.dist { background: var(--surface-alt); color: var(--text-dim); }

.dirlabel { margin-top: 1.1rem; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim); }
.dirrow { display: flex; gap: 8px; margin-top: var(--space-5); }
.peek-more {
  display: block; margin-top: 0.9rem; text-align: center;
  padding: 0.55rem 0.75rem; border-radius: var(--radius-sm);
  background: var(--accent-soft); color: var(--accent);
  font-size: 0.78rem; font-weight: 600; text-decoration: none;
  transition: background .12s, opacity .12s;
}
@media (hover: hover) { .peek-more:hover { background: var(--accent-soft); opacity: 0.82; } }
.peek-more:active { opacity: 0.72; }

/* ← Back to map link on /parking/:id and /about — a gentle filled-chip, not bare text */
.back-link {
  display: inline-flex; align-items: center; gap: 0.4rem;
  margin-top: var(--space-5); padding: 0.45rem 0.8rem;
  border-radius: var(--radius-sm); background: var(--surface-alt);
  color: var(--text-dim); font-size: 0.82rem; font-weight: 560;
  text-decoration: none; transition: background .12s, color .12s;
}
@media (hover: hover) { .back-link:hover { color: var(--text); background: var(--border); } }
.back-link:active { color: var(--text); background: var(--border); }

.dirbtn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  flex: 1; min-width: 0; padding: 0.6rem 0.5rem; border: none; border-radius: 12px;
  color: #fff; font-size: 0.74rem; font-weight: 700; letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden;
  box-shadow: 0 2px 8px rgba(14,20,34,0.16); transition: transform .12s, filter .12s;
}
/* short labels (Maps/Waze/Apple) leave room for a small pin glyph on one line at the
   ~87px button width — icon + label keeps the row scannable and Apple-clean */
.dirbtn svg { width: 14px; height: 14px; flex: none; }
.dirbtn span { overflow: hidden; text-overflow: ellipsis; }
@media (hover: hover) { .dirbtn:hover { transform: translateY(-1px); filter: brightness(1.06); } }
.dirbtn:active { transform: translateY(-1px); filter: brightness(1.06); }
.dirbtn.gm { background: #1A73E8; }
.dirbtn.wz { background: #3BB5E0; color: #053349; }  /* softened cyan, harmonised with the set; dark text keeps AA */
.dirbtn.ap { background: #111827; }

/* --- chart scale labels -------------------------------------------------- */
.chartbox { position: relative; }
/* scale labels live on the LEFT edge so they never collide with the right-anchored
   live "now" dot + label that own the top-right corner of the plot */
.chart-max, .chart-min {
  position: absolute; left: 2px; padding: 0 4px; border-radius: 4px;
  font-size: 0.62rem; color: var(--text-faint); font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, 0.65);
}
.chart-max { top: 2px; }
.chart-max small { font-weight: 500; opacity: 0.8; }
/* lift the min label off the very bottom so it doesn't share a baseline with the
   "00" x-axis label sitting just below the chart box */
.chart-min { bottom: 8px; }
.chart-now-dot { position: absolute; right: 3px; width: 9px; height: 9px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 1px 3px rgba(14,20,34,0.3); transform: translate(0, -50%); }
.chart-now-lbl { position: absolute; right: 14px; transform: translateY(-150%); padding: 1px 5px; border-radius: 5px; background: rgba(255,255,255,0.78); font-size: 0.6rem; font-weight: 600; color: var(--text-dim); white-space: nowrap; }
/* near the top edge the callout would crowd the curve's peak — drop it below the endpoint dot instead */
.chart-now-lbl.below { transform: translateY(60%); }

/* --- skeleton loaders (shimmer) ----------------------------------------- */
@keyframes shimmer { 0% { background-position: -240px 0; } 100% { background-position: 240px 0; } }
.skel-c, .skel-l, .skel-n, .skel-title, .skel-sub, .skel-cell, .skel-line, .skel-big, .chart-empty {
  background: linear-gradient(90deg, #EEF1F6 25%, #E3E8F1 37%, #EEF1F6 63%);
  background-size: 480px 100%; animation: shimmer 1.3s infinite linear; border-radius: 6px;
}
.skel-row { display: flex; align-items: center; gap: 0.8rem; padding: 0.6rem 0.5rem; border-bottom: 1px solid var(--border); }
.skel-c { flex: none; width: 38px; height: 38px; border-radius: 50%; }
.skel-l { flex: 1; height: 14px; }
.skel-n { width: 34px; height: 16px; }
.skel-title { width: 55%; height: 22px; margin-bottom: 8px; }
.skel-sub { width: 78%; height: 12px; margin-bottom: 16px; }
.skel-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 3px; }
.skel-cell { height: 14px; border-radius: 3px; }
.skel-line { width: 50%; height: 10px; margin-bottom: 12px; }
.skel-big { width: 42%; height: 26px; }
.chart-empty { height: 100px; margin-top: 0.6rem; opacity: 0.6; }
.chart-quiet { display: flex; align-items: center; justify-content: center; height: 78px; margin-top: 0.6rem; font-size: 0.78rem; color: var(--text-dim); font-style: italic; }

/* --- responsive ---------------------------------------------------------- */
/* Tablet band (above the mobile bottom-sheet breakpoint): the centered nav pill and the
   right-hand detail panel can overlap, hiding the car-park name and ✕. Drop the panel's top
   below the nav so its header always clears the pill. */
@media (min-width: 761px) and (max-width: 920px) {
  .peek { top: 4.4rem; max-height: calc(100dvh - 5.6rem); overflow-y: auto; }
}
@media (max-width: 760px) {
  /* Compact language pill on mobile — reclaim vertical space for the map; min-height raised to 44px for WCAG 2.5.5 */
  .topnav .lang { font-size: 0.68rem; min-height: 44px; min-width: 40px; padding: 0.3rem 0.4rem; }
  .topnav .nav-sep { height: 14px; }

  /* a parking is selected → list is a top card, detail is a bottom sheet, map shows between */
  .cmd { top: calc(4.4rem + env(safe-area-inset-top)); right: 1.2rem; left: 1.2rem; width: auto; bottom: auto; max-height: 38dvh; }
  /* nothing selected → the list fills the whole screen; keep its bottom clear of the iOS
     home indicator / Safari bottom bar via the safe-area inset */
  .cmd.full { bottom: max(1.2rem, env(safe-area-inset-bottom)); max-height: 52dvh; }
  /* selected state (a sheet is open): the top card becomes a compact park-SWITCHER. Hide the
     heading/legend/sort/key/footer/about chrome so 3–4 list rows stay scrollable — before, chrome
     ate the 38dvh and left ~1 row, so you couldn't change your mind without dismissing the sheet. */
  .cmd:not(.full) .kx,
  .cmd:not(.full) > h1,
  .cmd:not(.full) .cmdbar,
  .cmd:not(.full) .sortbar,
  .cmd:not(.full) .geo-err,
  .cmd:not(.full) .foot,
  .cmd:not(.full) .cmd-about { display: none; }
  .cmd:not(.full) { padding: 0.9rem 1rem; }
  /* strip branding chrome on mobile — .cmd already .full fills itself; title is the browser tab */
  .cmd .kx,
  .cmd > h1 { display: none; }
  /* radial gauge is redundant next to the big free count on the mobile sheet */
  .peek-gauge { display: none; }
  /* with title hidden the search is the first element — reduce its top gap */
  .cmd.full .search { margin-top: 0.5rem; }
  /* detail = edge-to-edge iOS-style bottom sheet: full width (0 side gutters, kills marker
     bleed-through at the seam), rounded top corners only, with a centered drag-grabber pill.
     padding-bottom adds the safe-area inset so the last row / direction buttons never hide
     behind Safari's bottom address bar or the home indicator. */
  .peek {
    top: auto; left: 0; right: 0; bottom: 0; width: auto; max-height: 56dvh; overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: var(--radius) var(--radius) 0 0; padding-top: 1.7rem;
    padding-bottom: calc(1.3rem + env(safe-area-inset-bottom));
  }
  /* shorter chart on the phone so the Directions buttons sit ABOVE the sheet fold (overrides the
     component's inline 140px). Tapping a route was previously scroll-then-tap. */
  .peek .chartbox, .peek .chart { height: 104px !important; }
  /* decorative grab cue only (no drag-to-dismiss gesture wired up — ✕ dismisses); kept light
     so it reads as a sheet style hint rather than an actionable handle */
  .peek::before {
    content: ''; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
    width: 36px; height: 5px; border-radius: 3px; background: rgba(120, 130, 150, 0.28);
  }
  /* Leaflet's control layer (zoom + attribution) is z-index 1000 and paints ABOVE the
     panels' default 900, so on mobile the attribution box paints over sheet content.
     Raise the panels above the control layer (still below .topnav 1200) AND make them
     near-opaque so the now-masked attribution + pills can't ghost through the glass. */
  .cmd, .peek { z-index: var(--z-panel); }
  .cmd, .peek { background: rgba(255, 255, 255, 0.80); }
  /* keep the zoom + attribution controls above the iOS home indicator / Safari bottom bar */
  .leaflet-bottom { margin-bottom: env(safe-area-inset-bottom); }

  /* --- mobile tap targets: enforce a comfortable >=44px thumb hit area ---
     Scoped to mobile so desktop chrome density is untouched. flex-centering keeps
     each glyph/label visually put while the touch box grows. */
  .nav-link { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }
  .lang { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 40px; }
  .homebtn { width: 44px; height: 44px; }
  .seg button { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }
  .sortbar button { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }
  /* expand the star/✕ touch box to 44×44 without enlarging the glyph; the visual offset
     moves to margin so the star stays right-aligned in its row/header */
  .prow .star, .peek-head .star {
    min-width: 44px; min-height: 44px; padding: 0; margin: 0; display: grid; place-items: center;
  }
  .pk-actions button { width: 44px; height: 44px; }
  .dirbtn { min-height: 44px; }
  .empty-clear { min-height: 44px; }
  .search { padding: 0.75rem 0.8rem; }
  /* iOS Safari auto-zooms the viewport when a text field <16px is focused; 16px prevents it */
  .search input { font-size: 16px; }
  /* the two smallest data readouts are at the edge of comfortable phone reading — nudge up */
  .chart-axis, .chart-now-lbl { font-size: 0.65rem; }
}

/* Landscape phones (e.g. 844×390): the max-width:760px sheet breakpoint does NOT match, so
   the device gets the desktop .cmd which is only ~352px tall — header + search + tabs + legend
   eat it and the .plist scroll window collapses to ~one row. Condense the chrome so the list
   gets a usable height. Scoped to short landscape only; portrait-mobile + desktop untouched. */
@media (orientation: landscape) and (max-height: 480px) {
  /* widen the panel and condense the chrome so the list reclaims vertical room... */
  .cmd { top: max(1.2rem, env(safe-area-inset-top)); bottom: 1.2rem; width: auto; right: auto; max-width: 60vw; padding: 0.9rem 1.1rem; }
  .cmd h1 { margin-top: 0.1rem; font-size: 1.15rem; }
  .search { margin: 0.5rem 0; }
  .seg { margin-bottom: 0.4rem; }
  .srclegend { margin-bottom: 0.4rem; }
  .sortbar { margin-bottom: 0.4rem; }
  .cmd .foot { margin-top: 0.5rem; }
  /* ...and lay the rows in two columns so ~4+ car parks are visible despite the short panel
     (height alone can't fit 4 rows at 54px in a ~150px scroll window) */
  .plist { display: grid; grid-template-columns: 1fr 1fr; column-gap: 0.6rem; align-content: start; }
  .empty { grid-column: 1 / -1; }
}

/* --- dark theme: direct-property overrides (MUST be last) ----------------- */
/* These mirror later light rules with hardcoded backgrounds (not tokens), so they have to come AFTER
   them in source order to win at equal specificity. Kept here, at the end of the file, deliberately. */
@media (prefers-color-scheme: dark) {
  /* dark surround behind the dark_all basemap tiles */
  .leaflet-container { background: #13171F !important; }
  .leaflet-tile-pane { filter: saturate(0.78) brightness(1.25); }
  .leaflet-control-attribution { background: rgba(19, 23, 31, 0.85) !important; }
  /* near-opaque centered nav over the dark map (mirrors the light rgba(255,255,255,0.93)) */
  .topnav { background: rgba(22, 28, 40, 0.94); }
  /* skeleton shimmer tuned for dark so it doesn't flash bright */
  .skel-c, .skel-l, .skel-n, .skel-title, .skel-sub, .skel-cell, .skel-line, .skel-big, .chart-empty {
    background: linear-gradient(90deg, #1B2230 25%, #283145 37%, #1B2230 63%);
    background-size: 480px 100%;
  }
  /* the chart's faint white label backings turn dark so axis numbers stay legible */
  .chart-max, .chart-min { background: rgba(11, 15, 23, 0.6); }
  .chart-now-lbl { background: rgba(11, 15, 23, 0.72); }
  /* mobile panels are near-opaque white in light mode — match in dark (this is the rule that the
     later light `.cmd, .peek` mobile rule was overriding, leaving white panels on a dark map) */
  @media (max-width: 760px) { .cmd, .peek { background: rgba(28, 35, 48, 0.82); } }

  /* --- zoom controls: hardcoded white bg makes +/− invisible in dark (contrast 1.30:1 → 11.2:1) --- */
  .leaflet-control-zoom a {
    background: var(--surface) !important;
    color: var(--text) !important;
    border-color: rgba(255, 255, 255, 0.10) !important;
  }
  .leaflet-control-zoom a:first-child {
    border-bottom-color: rgba(255, 255, 255, 0.10) !important;
  }
  .leaflet-control-zoom a:hover {
    background: var(--surface-alt) !important;
    color: var(--accent) !important;
  }

  /* --- map pill markers: hardcoded white pill bg makes ink tokens invisible in dark (contrast <2:1) --- */
  .pmark {
    background: var(--glass-bg);
    border-color: var(--glass-border);
  }
  /* selected marker: white bg + white ring would flash in dark mode after dark pill fix */
  .pmark.sel {
    background: rgba(30, 38, 55, 0.98);
    box-shadow: 0 0 0 3px rgba(40, 50, 70, 1), 0 0 0 5px var(--sc), 0 4px 16px rgba(0, 0, 0, 0.5);
  }

  /* active sort button: reinforce selected state with border thickness, not colour alone (WCAG 1.4.1) */
  .sortbar button.on { border-width: 2px; }

  /* cluster bubbles: same white-bg/light-text contrast failure as zoom controls */
  .pcluster { background: var(--surface); border-color: var(--border); }
}
