:root {
  color-scheme: dark;
  --ink: #e5e7eb;
  --paper: #0f172a;
  --muted: #94a3b8;
  --known: #34d399;
  --unknown: #fb923c;
  --water: #60a5fa;
  --line: #1f2937;
}

* { box-sizing: border-box; }

/* The page is the screen, so the map below can fill it. */
html, body { height: 100%; }

/* No double-tap zoom on the page; pinch is stopped in app.js. The map sets its
   own touch-action, so its gestures are unaffected. */
html { touch-action: manipulation; }

body {
  margin: 0;
  font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--ink);
}

/* Edge to edge. In an installed iPhone app the viewport stops a status bar's
   height short of the screen; fitMapToScreen in app.js then adds fill-screen and
   sizes the map from the top to the whole window. Placed from the top it paints
   into the band; placed from the bottom, as the first attempt was, it does not. */
#map { position: absolute; inset: 0; }
#map.fill-screen { position: fixed; top: 0; left: 0; right: 0; bottom: auto; }

/* iOS 26 lays out an installed web app as if the screen were a status bar's
   height shorter (WebKit bug 301108, open). Every viewport measure reads short
   except 100lvh, so the map takes its height from that, from the top, whether
   or not the script's fit above has run. Installed apps only: a browser tab is
   laid out correctly and must be left alone. */
@media (display-mode: standalone) {
  #map { bottom: auto; height: 100lvh; }
}

.muted { color: var(--muted); }

/* A car park whose terms are known must not look like one whose terms are a
   blank. The colour is the whole point of the distinction. */
.pin {
  border-radius: 50%;
  border: 2px solid var(--paper);
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.4);
}

.pin-known { background: var(--known); }
.pin-unknown { background: var(--unknown); }
.pin-water { background: var(--water); opacity: 0.85; }

.pop { min-width: 190px; }
.pop h2 { margin: 0 0 2px; font-size: 14px; }
.walk { margin: 0 0 8px; color: var(--muted); font-size: 12.5px; }

.row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 3px 0;
  border-top: 1px solid var(--line);
}

.label { color: var(--muted); }
.value { font-variant-numeric: tabular-nums; }

.warn {
  margin: 8px 0 0;
  font-size: 11.5px;
  color: var(--unknown);
}

.k-lim  { background: var(--water); }
.k-cust { background: var(--unknown); }
.k-paid { background: #475569; }

.zoom-note {
  margin: 0 0 10px;
  padding: 7px 10px;
  background: #eff6ff;
  border-radius: 4px;
  font-size: 12.5px;
}

.tariff {
  margin: 8px 0 0;
  padding: 7px 9px;
  background: #f8fafc;
  border-radius: 4px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* The dock: the filter and the key in one row, along the bottom where a thumb
   reaches. Each chip carries its category's colour, so there is no separate
   legend to drift out of step with it. */
.dock {
  position: fixed;
  z-index: 600;
  left: 50%;
  /* max() rather than a sum. Adding to env(safe-area-inset-bottom) stacks our
     gap on top of a value that already includes the iOS toolbar zone, which
     left the row floating well clear of the bottom edge with dead map beneath
     it. max() keeps it off the home indicator without paying twice. */
  bottom: max(12px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: 2px;
  max-width: calc(100vw - 20px);
  padding: 6px;
  background: rgb(255 255 255 / 0.86);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 2px 16px rgb(0 0 0 / 0.14);
}

.chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-radius: 11px;
  cursor: pointer;
}

.chip:hover { background: #f1f5f9; }

/* Off reads as off at a glance: the dot drains and the label recedes. */
.chip.off { color: var(--muted); }

.addr {
  margin: 0 0 8px;
  font-size: 12.5px;
  color: var(--muted);
}

/* Top right. It was bottom right until an over-eager regex deleted this whole
   block, leaving a button with no position, size or background — which is why
   somebody asked for a locate button while looking at one. Top right is empty,
   conventional, and competes with nothing. */
.locate {
  position: fixed;
  z-index: 600;
  right: calc(12px + env(safe-area-inset-right));
  top: calc(12px + env(safe-area-inset-top));
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  color: var(--ink);
  background: rgb(255 255 255 / 0.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: 50%;
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.16);
  cursor: pointer;
}

.locate:hover { background: #fff; }
.locate svg { width: 20px; height: 20px; }
.locate.seeking { color: var(--water); animation: pulse 1.1s ease-in-out infinite; }
.locate.failed { color: var(--unknown); }
/* Pressing and holding opens diagnostics, which iOS would otherwise answer with
   a selection or a callout. */
.locate { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }

.diagnostics {
  position: fixed;
  z-index: 2000;
  top: calc(env(safe-area-inset-top) + 64px);
  left: 8px;
  right: 8px;
  margin: 0;
  padding: 8px 10px;
  color: #fff;
  background: rgb(0 0 0 / 0.9);
  border-radius: 8px;
  font: 11px/1.45 ui-monospace, Menlo, monospace;
  white-space: pre-wrap;
}
/* Following: the map moves with you. Drag the map and it drops back to plain. */
.locate.following { color: var(--water); border-color: var(--water); }

@keyframes pulse { 50% { opacity: 0.45; } }

/* Says how much the current filters are keeping off the map, because an empty
   map and a filtered one look identical otherwise. */
/* `display: block` beats the browser's own `[hidden] { display: none }`, which
   is how a "0 more here, hidden by the filters" banner came to sit on the map
   permanently. Anything setting display must exempt the hidden state. */
.hidden-note[hidden],
.zoom-note[hidden] { display: none; }

.hidden-note {
  display: block;
  width: 100%;
  margin: 0 0 10px;
  padding: 7px 10px;
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  color: #92400e;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 4px;
  cursor: pointer;
}

.hidden-note:hover { background: #fef3c7; }

/* Phones. The panel was taking the top half of the screen to say things that
   are read once, so everything below the first line collapses behind a tap,
   and the chips shrink to fit two rows rather than four. */
@media (max-width: 560px) {

  .chip {
    padding: 7px 9px;
    font-size: 11.5px;
    gap: 5px;
  }

  .dock {
    bottom: max(6px, env(safe-area-inset-bottom));
    gap: 3px;
    padding: 5px;
  }

  .locate { width: 46px; height: 46px; }
}

/* Tap target for the collapsed panel, shown only where the panel collapses. */

/* Icon only. The label is kept in the markup for the accessible name and for
   anything that reads the page rather than looks at it. */
.chip span { display: none; }

.chip svg {
  width: 20px;
  height: 20px;
  display: block;
}

/* On, the icon carries its category's colour — the same colour as the marker
   it filters, so the row is the key as well as the control. Off, it drains to
   grey, which reads as off at a glance without needing a strikethrough. */
.chip[data-kind="free"]     { color: var(--known); }
.chip[data-kind="limited"]  { color: var(--water); }
.chip[data-kind="customer"] { color: var(--unknown); }
.chip[data-kind="disabled"] { color: #0284c7; }
.chip[data-kind="paid"]     { color: #475569; }
.chip[data-kind="ev"]       { color: #7c3aed; }
.chip[data-kind="unknown"]  { color: #475569; }

.chip.off { color: #cbd5e1; }

.chip[data-kind] { padding: 9px; }

/* Phones: the dock spans the screen and the chips share it equally, with icons
   big enough for a thumb. An accidental pinch-zoom of the compact row looked
   better than the row itself, which settled the size. */
@media (max-width: 560px) {
  .dock {
    left: max(10px, env(safe-area-inset-left));
    right: max(10px, env(safe-area-inset-right));
    transform: none;
    max-width: none;
    justify-content: space-between;
    gap: 2px;
    padding: 6px;
  }
  .chip[data-kind] { flex: 1 1 0; justify-content: center; padding: 11px 0; }
  .chip svg { width: 24px; height: 24px; }
  /* Clear of the taller dock. */
  .chip-tip { bottom: calc(max(6px, env(safe-area-inset-bottom)) + 70px); }
}

/* What is left where the panel was: two short messages that only appear when
   they have something to say. */
.toasts {
  position: fixed;
  z-index: 500;
  top: calc(10px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  /* Narrow enough to clear the locate button, which shares this edge. */
  width: min(320px, calc(100vw - 90px));
}

.toasts :is(p, button) { margin: 0 0 6px; }

/* The attribution has to stay visible — it is the ODbL requirement — but it
   sits bottom right by default, which is where the dock now is. Moved left and
   made quiet so the two do not fight over the same corner. */
.leaflet-bottom.leaflet-right {
  left: 0;
  right: auto;
}

.leaflet-control-attribution {
  font-size: 10px;
  background: rgb(255 255 255 / 0.72);
}

/* Dark theme.

   The basemap is OSM's own light tiles inverted, because the proper dark
   basemap — CARTO Dark Matter — needs the key in issue #1. invert() flips
   lightness and hue-rotate(180deg) swings the hues back, so water stays blue
   and parks stay green, just dark. Markers are drawn on a canvas in the
   overlay pane, not the tile pane, so the filter never touches them.

   Swap this for dark_matter tiles once a key exists; inverted tiles are a
   stopgap and their labels are softer than a purpose-made dark style. */
body,
.leaflet-container { background: #0b1220; }

.leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9) saturate(0.7);
}

.dock {
  background: rgb(15 23 42 / 0.86);
  border-color: var(--line);
  box-shadow: 0 2px 16px rgb(0 0 0 / 0.5);
}

.chip:hover { background: #1e293b; }
/* [data-kind] raises this above the per-kind colours below it. At equal
   specificity the later rule won, so blue badge, paid, EV and unknown stayed
   coloured when switched off while free, timed and customer drained — because
   those three happened to be declared earlier in the file. */
.chip[data-kind].off { color: #334155; }

.chip[data-kind="disabled"] { color: #22d3ee; }
.chip[data-kind="paid"]     { color: #94a3b8; }
.chip[data-kind="ev"]       { color: #facc15; }
.chip[data-kind="unknown"]  { color: #94a3b8; }

.locate {
  color: var(--ink);
  background: rgb(15 23 42 / 0.92);
  border-color: var(--line);
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.5);
}

.locate:hover { background: #1e293b; }

.zoom-note { color: var(--ink); background: #1e293b; }

.hidden-note {
  color: #fcd34d;
  background: #422006;
  border-color: #78350f;
}

.hidden-note:hover { background: #532b0a; }

.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  color: var(--ink);
  background: #111827;
  box-shadow: 0 3px 18px rgb(0 0 0 / 0.6);
}

.leaflet-container a.leaflet-popup-close-button { color: var(--muted); }

.row { border-top-color: var(--line); }
.tariff { background: #1e293b; }

.leaflet-control-attribution {
  color: var(--muted);
  background: rgb(15 23 42 / 0.72);
}

.leaflet-control-attribution a { color: #93c5fd; }

/* Tooltips. The icons carry no words, and a title attribute only appears on
   hover — which a phone does not have, so it never showed there. */

/* Pointer devices: the accessible name, shown above the chip on hover. */
@media (hover: hover) {
  .chip[data-kind] { position: relative; }

  .chip[data-kind]:hover::after {
    /* The option key only exists where hover does, so the hint lives here. */
    content: attr(aria-label) " · ⌥-click to show only this";
    position: absolute;
    bottom: calc(100% + 10px);
    left: 50%;
    transform: translateX(-50%);
    padding: 5px 8px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    color: var(--ink);
    background: #111827;
    border: 1px solid var(--line);
    border-radius: 6px;
    pointer-events: none;
  }
}

/* Touch: a tap says what was toggled and which way, just above the dock. */
.chip-tip {
  position: fixed;
  z-index: 600;
  left: 50%;
  bottom: calc(max(12px, env(safe-area-inset-bottom)) + 58px);
  transform: translateX(-50%);
  margin: 0;
  padding: 6px 10px;
  font-size: 12.5px;
  white-space: nowrap;
  color: var(--ink);
  background: rgb(15 23 42 / 0.92);
  border: 1px solid var(--line);
  border-radius: 8px;
  pointer-events: none;
}

.chip-tip[hidden] { display: none; }

/* Map icons, drawn from ICON_ZOOM in. The fill carries the category colour and
   the icon is drawn dark on it, the same pairing as the chip it matches. */
.mk-wrap { background: none; border: 0; }

.mk {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: #0b1220;
  border: 1.5px solid #0b1220;
  border-radius: 50%;
  box-shadow: 0 1px 5px rgb(0 0 0 / 0.55);
}

.mk svg { width: 62%; height: 62%; }

.mk-free     { background: #34d399; }
.mk-limited  { background: #60a5fa; }
.mk-customer { background: #fb923c; }
.mk-disabled { background: #22d3ee; }
.mk-paid     { background: #94a3b8; }
.mk-ev       { background: #facc15; }

/* Unknown stays the odd one out: a dark disc with a dashed ring, a question
   rather than an answer, as the dot version is. */
.mk-unknown {
  color: #94a3b8;
  background: #0f172a;
  border: 2px dashed #94a3b8;
}

.mk-weak { opacity: 0.75; }

/* In a car, your position is a car pointing the way you are going, rather than
   a dot that says nothing about direction. The glyph turns inside the marker,
   leaving Leaflet's own positioning transform alone. */
.me-wrap { background: none; border: 0; }

.me-car {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: #fff;
  background: #2563eb;
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 1px 6px rgb(0 0 0 / 0.55);
  /* Eased, so a new heading turns rather than jumps. */
  transition: transform 0.3s ease-out;
}

.me-car svg { width: 64%; height: 64%; display: block; }

/* Zoomed-out bubbles. Neutral rather than a category colour, because each one
   counts a mix of categories and green would read as "free". */
.cl-wrap { background: none; border: 0; cursor: pointer; }

.cl {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #0b1220;
  background: rgb(226 232 240 / 0.92);
  border: 2px solid #0b1220;
  border-radius: 50%;
  box-shadow: 0 1px 6px rgb(0 0 0 / 0.55);
}
