/* depok-map — light/dark themed over an OpenFreeMap basemap.
   Tokens live on :root[data-theme]; every surface reads from them so the
   basemap and the chrome can never drift out of sync. */

*, *::before, *::after { box-sizing: border-box; }

:root {
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --shadow-1: 0 1px 2px rgba(16, 18, 15, .06), 0 2px 8px rgba(16, 18, 15, .06);
  --shadow-2: 0 4px 12px rgba(16, 18, 15, .10), 0 12px 32px rgba(16, 18, 15, .10);

  --dur: 160ms;
  --ease: cubic-bezier(.32, .72, 0, 1);
}

:root[data-theme="light"] {
  --bg: #f7f6f3;
  --surface: rgba(255, 255, 255, .93);
  --surface-solid: #fff;
  --surface-2: #f2f1ec;
  --border: rgba(24, 26, 22, .12);
  --border-strong: rgba(24, 26, 22, .22);
  --ink: #1b1d19;
  --ink-2: #4a4d46;
  --muted: #6d716a;
  --faint: #8f948b;
  --accent: #1f6b48;
  --accent-ink: #fff;
  --accent-soft: rgba(31, 107, 72, .10);
  --hover: rgba(24, 26, 22, .05);
  --theme-color: #f7f6f3;
  --map-filter: none;
}

:root[data-theme="dark"] {
  --bg: #12140f;
  --surface: rgba(22, 25, 18, .93);
  --surface-solid: #171a13;
  --surface-2: #23271c;
  --border: rgba(255, 255, 255, .13);
  --border-strong: rgba(255, 255, 255, .24);
  --ink: #f0efe9;
  --ink-2: #c9ccc1;
  --muted: #969a8d;
  --faint: #7c8175;
  --accent: #6fd39b;
  --accent-ink: #0f1a12;
  --accent-soft: rgba(111, 211, 155, .14);
  --hover: rgba(255, 255, 255, .07);
  --theme-color: #12140f;
  --map-filter: none;
}

html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--sans);
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

#map { position: absolute; inset: 0; background: var(--bg); }

/* MapLibre chrome inherits our tokens so it doesn't stay light in dark mode. */
.maplibregl-ctrl-group {
  background: var(--surface) !important;
  border: 1px solid var(--border) !important;
  box-shadow: var(--shadow-1) !important;
  border-radius: var(--r-sm) !important;
  overflow: hidden;
}
.maplibregl-ctrl-group button + button { border-top: 1px solid var(--border) !important; }
.maplibregl-ctrl-group button .maplibregl-ctrl-icon { filter: var(--ctrl-icon-filter, none); }
:root[data-theme="dark"] { --ctrl-icon-filter: invert(1) brightness(1.6); }
.maplibregl-ctrl-attrib {
  display: none; /* replaced by our own attribution line, which carries the licence */
}

.skip {
  position: absolute; left: 8px; top: -60px; z-index: 2000;
  background: var(--accent); color: var(--accent-ink);
  padding: 10px 15px; border-radius: var(--r-sm); font-size: 13.5px; font-weight: 600;
  text-decoration: none; transition: top var(--dur) var(--ease);
}
.skip:focus { top: 8px; }

/* ---------- search ---------- */
/* Top bar: the sidebar toggle sits beside the search box on the same level.
   A 3-column grid (toggle | search | tail) keeps the search centred on the
   viewport while the toggle occupies the left -- the tail mirrors the toggle's
   width so the middle column stays visually centred. Previously the toggle was
   absolutely positioned at top-left and the search was independently centred,
   which put them on the same line by accident and made the back button below
   the search collide with it at 721-1024px. */
/* Two columns: an icon-only toggle, then the search filling the rest. The
   search is no longer centred -- it spans the row, because the toggle lost its
   label and the theme toggle moved to the sidebar, so there is nothing to
   balance. Centring it again would waste the width it just gained. */
.topbar {
  position: absolute; z-index: 1000;
  top: max(16px, env(safe-area-inset-top));
  left: max(18px, env(safe-area-inset-left));
  right: max(18px, env(safe-area-inset-right));
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 8px;
  pointer-events: none; /* the bar itself must not eat map drags */
}
.topbar > * { pointer-events: auto; }

.search {
  justify-self: stretch;
  width: 100%;
  min-width: 0;
}
.search-box {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 0 8px 0 14px; height: 46px;
  box-shadow: var(--shadow-2);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.search-box:focus-within {
  border-color: var(--accent);
  box-shadow: var(--shadow-2), 0 0 0 3px var(--accent-soft);
}
.i-search { color: var(--faint); flex: none; }
#q {
  flex: 1; min-width: 0; height: 100%;
  background: none; border: 0; outline: 0;
  font: inherit; font-size: 15px; color: var(--ink);
}
#q::placeholder { color: var(--faint); }
#q::-webkit-search-cancel-button { display: none; }

/* .theme now lives in the sidebar header; only the clear button shares this. */
.clear {
  flex: none; display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: none; border: 0; cursor: pointer;
  color: var(--muted); font-family: inherit;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.clear:hover { background: var(--hover); color: var(--ink); }


.results {
  list-style: none; margin: 8px 0 0; padding: 5px;
  max-height: min(52vh, 380px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
}
.results li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px; border-radius: var(--r-sm); cursor: pointer;
  font-size: 14px; color: var(--ink);
}
.results li:hover, .results li[aria-selected="true"] { background: var(--hover); }
.results li[aria-selected="true"] { box-shadow: inset 2px 0 0 var(--accent); }
.results .r-name { font-weight: 520; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.results li.is-district .r-name { font-weight: 640; }
/* Two kelurahan share a name ("Curug"). Their rows would look identical, so
   the kecamatan is emphasised — that is the only thing distinguishing them. */
.results li.is-dup .r-meta { color: var(--ink-2); font-weight: 600; }
.results .r-meta { font-size: 11.5px; color: var(--faint); flex: none; font-variant-numeric: tabular-nums; }
.results mark { background: var(--accent-soft); color: inherit; border-radius: 3px; padding: 0 2px; font-weight: 650; }
.results .empty { color: var(--muted); cursor: default; justify-content: center; }
.results .empty:hover { background: none; }

/* ---------- answer card ---------- */
.card {
  /* fixed, not absolute: the card is anchored to the viewport by its `bottom`.
     As an absolute box it resolved against <body>, whose height is content
     driven (everything else on the page is absolutely positioned). When the
     card collapses, body's height collapses with it and the card gets dragged
     off the top of the screen -- minimising it made it vanish entirely. */
  position: fixed; z-index: 900;
  left: max(18px, env(safe-area-inset-left));
  /* Clear the attribution strip, which sits bottom-right and is ~26px tall.
     Without this the card overlapped it between 721px and ~1024px, where the
     mobile rule does not apply but both elements are still near each other. */
  bottom: calc(max(18px, env(safe-area-inset-bottom)) + 30px);
  width: min(384px, calc(100vw - 36px));
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  overflow: hidden;
}
.card.placeholder {
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  padding: 26px 22px; color: var(--muted); font-size: 13.5px; text-align: center;
}
.card.placeholder svg { color: var(--faint); opacity: .7; }
.card.placeholder p { margin: 0; }
.card[hidden] { display: none; }

.card-top {
  padding: 16px 18px 13px;
  border-bottom: 1px solid var(--border);
  /* Anchors .card-min, which is absolutely positioned in the top-right. */
  position: relative;
}

/* --- minimise the answer card --- */
/* Sits in the card's title row. Collapsing the card is how you get the map
   back on a phone, where the card covers a third of the screen. */
.card-min {
  position: absolute; top: 10px; right: 10px;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
  transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.card-min:hover { color: var(--ink); border-color: var(--border-strong); }
.card-min svg { transition: transform var(--dur) var(--ease); }
body.card-min .card-min svg { transform: rotate(180deg); }
/* Collapsed: keep only the title row. The card-foot/ladder/kids are hidden
   rather than the whole card so the selection is still identifiable. */
body.card-min .card .ladder,
body.card-min .card .kids,
body.card-min .card .card-foot { display: none; }
body.card-min .card { padding-bottom: 10px; }
.eyebrow {
  font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint);
}
.card-name { font-size: 21px; font-weight: 650; letter-spacing: -.02em; margin-top: 4px; line-height: 1.2; }

.ladder { padding: 12px 18px 14px; }
.rung {
  display: flex; align-items: baseline; gap: 9px;
  padding: 5px 0; font-size: 13.5px;
}
.rung .lvl {
  color: var(--faint); font-size: 11px; font-weight: 600;
  min-width: 96px; flex: none; text-transform: uppercase; letter-spacing: .04em;
}
.rung .nm { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rung:last-child .nm { color: var(--ink); font-weight: 640; }
.rung .dot {
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
  flex: none; position: relative; top: -1px;
}

/* ---------- kelurahan chips (open kecamatan) ---------- */
.kids {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 0 18px 13px;
}
.kids[hidden] { display: none; }
.chip {
  font: inherit; font-size: 12.5px; color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 999px; padding: 5px 11px; cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.chip:hover { color: var(--accent); border-color: var(--accent); }

.card-foot {
  padding: 10px 18px; border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--surface-2);
}
.card-foot .hint { font-size: 12px; color: var(--muted); }
.code {
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  background: var(--surface-solid); border: 1px solid var(--border);
  padding: 3px 8px; border-radius: 6px;
}
.copy {
  font: inherit; font-size: 11.5px; font-weight: 600; color: var(--muted);
  background: none; border: 1px solid var(--border-strong);
  border-radius: 6px; padding: 5px 11px; cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.copy:hover { color: var(--ink); border-color: var(--ink-2); }
.copy.done { color: var(--accent); border-color: var(--accent); }

/* ---------- sidebar ---------- */
/* The sidebar is a full-height column on the left. It displaces the search box
   and the answer card rather than overlapping them: both are also left-anchored,
   and overlaying them would hide the card the moment a kecamatan is opened. The
   map is padded to match via VIEW_PADDING.left so nothing sits under the panel. */
.sidebar {
  position: absolute; z-index: 1100;
  /* Clears the top bar: 16px inset + 46px row + 12px gap. The panel previously
     started at 12px and ran under the search box and toggle. */
  top: calc(max(16px, env(safe-area-inset-top)) + 58px);
  bottom: max(12px, env(safe-area-inset-bottom));
  left: max(12px, env(safe-area-inset-left));
  width: min(316px, calc(100vw - 24px));
  display: flex; flex-direction: column;
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  /* Collapsed by default; JS flips [hidden] off. Kept out of the a11y tree while
     closed so tab order does not walk a panel nobody can see. */
}
.sidebar[hidden] { display: none; }

.side-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 8px 8px 10px;
  border-bottom: 1px solid var(--border);
  flex: none;
}
.side-tabs { display: flex; gap: 4px; flex: 1; min-width: 0; }
.side-tab {
  flex: 1; height: 30px; padding: 0 8px;
  font: inherit; font-size: 12.5px; font-weight: 580; color: var(--muted);
  background: transparent; border: 1px solid transparent; border-radius: 7px;
  cursor: pointer;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.side-tab:hover { color: var(--ink-2); }
.side-tab.is-on {
  color: var(--ink); background: var(--bg);
  border-color: var(--border);
}
.side-close {
  flex: none; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); background: transparent;
  border: 1px solid transparent; border-radius: 7px; cursor: pointer;
}
.side-close:hover { color: var(--ink); border-color: var(--border); }

.side-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.side-panel[hidden] { display: none; }
/* The scroll container for BOTH panels. Previously only the area list had it,
   so the 77-row road list overflowed and was silently clipped by the panel's
   overflow:hidden -- the bottom rows were unreachable, on any device.
   -webkit-overflow-scrolling + touch-action matter on iOS, where a scroll
   region inside a transformed/positioned overlay otherwise rubber-bands the
   whole page instead of the list. */
.side-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  /* Room for the scrollbar so text never sits under it on desktop. */
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.side-scroll::-webkit-scrollbar { width: 9px; }
.side-scroll::-webkit-scrollbar-track { background: transparent; }
.side-scroll::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: 999px;
  border: 3px solid transparent; background-clip: content-box;
}
.side-scroll::-webkit-scrollbar-thumb:hover { background-clip: content-box; background-color: var(--faint); }
/* The note is sticky so the source citation stays visible while scrolling the
   77 ruas. This only works because it lives INSIDE the .side-scroll container
   (see index.html) -- sticky positions against the nearest scrolling ancestor,
   so as a sibling of the scroller it never stuck at all. */
.side-note {
  position: sticky; top: 0; z-index: 2;
  margin: 0; padding: 9px 13px;
  font-size: 11.5px; line-height: 1.45; color: var(--muted);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  flex: none;
}
.side-note b { color: var(--ink-2); font-weight: 600; }

/* --- GPS row (replaces the removed floating .geo-note banner) --- */
/* Button and result share a row so the answer stays next to the control that
   produced it, and so the row cannot push the map down like the banner did. */
.side-locate {
  flex: none;
  display: flex; align-items: center; gap: 8px;
  padding: 8px 13px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.locate-btn {
  flex: none;
  height: 30px; padding: 0 11px;
  font: inherit; font-size: 12px; font-weight: 560;
  color: var(--ink-2); background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.locate-btn:hover { color: var(--ink); border-color: var(--border-strong); }
.locate-btn:disabled { opacity: .55; cursor: default; }
.locate-text {
  font-size: 11.5px; line-height: 1.35; color: var(--muted);
  min-width: 0; /* let the text wrap instead of stretching the row */
  overflow-wrap: anywhere;
}
/* The area meta note was .search-meta under the search box. */
#area-note { border-bottom: 1px solid var(--border); }

/* --- area rows --- */
.side-group {
  padding: 7px 13px 3px;
  font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint);
}
.side-row {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 13px;
  font: inherit; font-size: 13px; text-align: left;
  color: var(--ink-2); background: transparent;
  border: 0; border-left: 2px solid transparent;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.side-row:hover { background: var(--bg); color: var(--ink); }
.side-row.is-on {
  color: var(--ink); font-weight: 620;
  background: var(--bg); border-left-color: var(--accent);
}
.side-row .sr-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.side-row .sr-meta {
  flex: none; font-size: 11px; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.side-row.is-on .sr-meta { color: var(--accent); }

/* --- road rows --- */
.road-group { border-bottom: 1px solid var(--border); }
.road-group:last-child { border-bottom: 0; }
.road-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 13px;
  font: inherit; text-align: left;
  color: var(--ink); background: var(--bg);
  border: 0; cursor: pointer;
}
.road-head:hover { background: var(--bg-2, var(--bg)); }
.road-chev {
  flex: none; color: var(--faint); font-size: 10px; line-height: 1;
  transition: transform var(--dur) var(--ease);
}
.road-group.is-open .road-chev { transform: rotate(90deg); }
.road-title { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 620; }
.road-basis {
  flex: none; font-size: 9.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; padding: 2px 5px; border-radius: 4px;
  color: var(--muted); border: 1px solid var(--border);
}
.road-basis[data-conf="high"] { color: var(--accent); border-color: var(--accent); }
.road-count {
  flex: none; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums;
}
.road-body { display: none; }
.road-group.is-open .road-body { display: block; }

.road-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 13px 6px 26px;
  font: inherit; font-size: 12.5px; text-align: left;
  color: var(--ink-2); background: transparent; border: 0; cursor: pointer;
}
.road-row:hover { background: var(--bg); color: var(--ink); }
.road-row.is-on { color: var(--ink); font-weight: 620; background: var(--bg); }
.road-row .rr-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.road-row .rr-badge {
  flex: none; font-size: 9.5px; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 5px; border-radius: 4px;
  color: var(--muted); background: var(--bg); border: 1px solid var(--border);
}
.road-row .rr-ref {
  flex: none; font-size: 10.5px; font-weight: 700; color: var(--accent);
  font-variant-numeric: tabular-nums; min-width: 20px; text-align: right;
}
.road-row.is-dead { opacity: .5; }
.road-row.is-dead .rr-name { font-style: italic; }
.road-empty { padding: 10px 13px 12px 26px; font-size: 12px; color: var(--faint); }

/* Selected-road banner, above the answer card. Inline rather than a toast, so it
   stays put while you keep using the map. */
.road-note-card {
  /* fixed for the same reason as .card: it stacks off --card-h above the card,
     and must anchor to the viewport, not to a body whose height tracks the map
     chrome. */
  position: fixed; z-index: 920;
  left: max(18px, env(safe-area-inset-left));
  bottom: calc(max(18px, env(safe-area-inset-bottom)) + var(--card-h, 0px));
  max-width: 384px; width: calc(100vw - 36px);
  display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 12px;
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-left: 3px solid var(--road-c, #c2410c);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
}
.road-note-card[hidden] { display: none; }
.road-note-card .rnc-text { flex: 1; min-width: 0; }
.road-note-card .rnc-klas {
  display: block; font-size: 10px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--road-c, #c2410c); margin-bottom: 2px;
}
.road-note-card .rnc-name {
  display: block; font-size: 13px; font-weight: 560; color: var(--ink);
  line-height: 1.35;
}
.road-note-card .rnc-x {
  flex: none; width: 22px; height: 22px; display: flex; align-items: center;
  justify-content: center; color: var(--muted);
  background: transparent; border: 0; border-radius: 5px; cursor: pointer;
}
.road-note-card .rnc-x:hover { color: var(--ink); background: var(--bg); }
body.side-open .road-note-card { left: calc(max(12px, env(safe-area-inset-left)) + min(316px, calc(100vw - 24px)) + 12px); }
@media (max-width: 760px) {
  body.side-open .road-note-card { left: max(18px, env(safe-area-inset-left)); }
}

/* --- toggle --- */
/* Icon only, square, matching .search-box height so the row reads as one unit.
   The label lived in a <span> that JS rewrote to Daftar/Tutup/Buka; it is gone,
   so the accessible name on aria-label is the only label now. */
.side-toggle {
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; padding: 0;
  font: inherit; color: var(--ink-2);
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: var(--shadow-1);
  cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.side-toggle:hover { color: var(--ink); border-color: var(--border-strong); }
/* When the panel is open the toggle becomes the panel's own minimise/restore
   control rather than disappearing, so the left-hand slot never changes width
   and the search box cannot jump sideways on every open/close. */
.side-toggle[hidden] { display: none; }
body.side-open .side-toggle {
  border-color: var(--border-strong);
  color: var(--ink);
  background: var(--bg);
}

/* --- theme, in the sidebar header --- */
/* Moved out of the search box: it was eating ~44px of input width, and the
   input is the primary control on a phone. */
.side-theme {
  flex: none; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); background: transparent;
  border: 1px solid transparent; border-radius: 7px; cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.side-theme:hover { color: var(--ink); border-color: var(--border); }
.side-theme .i-moon { display: none; }
:root[data-theme="dark"] .side-theme .i-sun { display: none; }
:root[data-theme="dark"] .side-theme .i-moon { display: block; }

/* There is no sidebar minimise: it meant "get the list out of the way", which
   the answer card's own minimise button does better, from the element that
   actually covers the map. The panel has one shrink action -- close. */

/* Layout shift while the sidebar is open.
   Only .back and .card move. The search box is centred with translateX(-50%),
   so assigning it a `left` would shift its centre and skew it off-screen; it is
   already clear of the panel at any width where both fit (>700px), and below
   that the panel overlays the map anyway. The toggle is hidden rather than moved,
   since the panel's own close button replaces it. */
body.side-open .back,
body.side-open .card {
  left: calc(max(12px, env(safe-area-inset-left)) + min(316px, calc(100vw - 24px)) + 12px);
}

@media (max-width: 760px) {
  /* The panel is full-width and overlays the map, so there is nothing to sit
     beside. The .card shift must be undone explicitly here: left-anchored rules
     are inherited by the narrower block above, and without this the card is
     pushed off the right edge and collapses to a few px wide. */
  .sidebar { width: calc(100vw - 24px); }
  body.side-open .card { left: max(12px, env(safe-area-inset-left)); }
}

/* On a phone the panel has to be shorter than the viewport or the list cannot
   be scrolled at all: top + height must clear the top bar above and the
   attribution below. 78dvh leaves room for both and leaves a visible strip of
   map, so the user can tell the panel is an overlay and not the whole screen. */
@media (max-width: 760px) {
  .sidebar {
    top: calc(max(10px, env(safe-area-inset-top)) + 56px);
    /* bottom unset: a fixed height is what makes the inner list scrollable. */
    bottom: auto;
    height: min(78dvh, calc(100dvh - 150px));
    /* Fallback for browsers without dvh (older Safari): vh is wrong when the
       URL bar shows, but it is better than no height at all. */
    max-height: calc(100vh - 150px);
  }
  /* Match the 44px search box so the top row stays one level. */
  .side-toggle { width: 44px; height: 44px; }
  /* Taller tap targets on touch. */
  .side-tab, .side-theme, .side-close { height: 34px; }
  .side-row { padding: 10px 13px; }
  .road-head { padding: 11px 13px; }
}

.back {
  position: absolute; z-index: 950;
  left: max(18px, env(safe-area-inset-left));
  display: flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 14px 0 10px;
  font: inherit; font-size: 13px; font-weight: 560; color: var(--ink-2);
  background: var(--surface);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: var(--shadow-1);
  cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.back:hover { color: var(--ink); border-color: var(--border-strong); }
.back[hidden] { display: none; }

/* The back button sits below the top bar. It cannot share the top row any more:
   that row is the toggle + search grid, and there is no free third slot at
   721-1024px where the search box is already 520px wide. Below the row is clear
   at every width, and it only appears from kecamatan level onward. */
.back {
  top: calc(max(16px, env(safe-area-inset-top)) + 58px);
}

/* Between 721px and 1024px the search box is still centred and its
   "11 kecamatan · 63 kelurahan · Kota Depok" line sits right where the back
   button goes. Measured collision at 768px and 900px. In that band only, drop
   the summary line — the card already states the counts. */
@media (min-width: 721px) and (max-width: 1024px) {
}

/* ---------- legend ---------- */
.legend {
  position: absolute; z-index: 800; right: max(18px, env(safe-area-inset-right));
  bottom: max(18px, env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 6px;
  background: var(--surface);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 9px 12px; font-size: 11.5px; color: var(--ink-2);
  box-shadow: var(--shadow-1);
}
.legend[hidden] { display: none; }
.legend span { display: flex; align-items: center; gap: 7px; }
.sw { width: 11px; height: 11px; border-radius: 3px; border: 1px solid; }
.sw.sel { background: var(--accent); border-color: var(--accent); }
.sw.same { background: var(--accent-soft); border-color: var(--accent); }



/* ---------- attribution ---------- */
.attrib {
  position: absolute; z-index: 700;
  right: max(18px, env(safe-area-inset-right));
  bottom: 0; max-width: min(560px, calc(100vw - 36px));
  padding: 5px 10px; margin: 0 0 2px;
  font-size: 10.5px; line-height: 1.5; color: var(--ink-2); text-align: right;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 6px 0 0 0;
}
.attrib a { color: var(--ink-2); text-decoration: underline; text-underline-offset: 2px; }
.attrib a:hover { color: var(--accent); }

/* ---------- mobile ---------- */
@media (max-width: 720px) {
  /* The top bar owns the inset and the search just fills its grid column.
     The old `width: calc(100vw - 20px)` overrode the column width and made the
     search box overlap the toggle at 430px. */
  .topbar { top: max(10px, env(safe-area-inset-top)); gap: 8px; }
  .search { width: 100%; }
  .search-box { height: 44px; padding-left: 12px; }
  #q { font-size: 16px; /* 16px avoids iOS Safari's focus-zoom */ }

  .card {
    left: 10px; right: 10px; width: auto;
    /* Measured: card bottom 808 vs attribution top 804 at 390x844 — the card
       overlapped the licence line by 4px. The attribution is ~38px tall, so
       clear it plus a gap rather than assuming a fixed height. */
    bottom: calc(max(10px, env(safe-area-inset-bottom)) + 46px);
  }
  .card-name { font-size: 19px; }
  .rung .lvl { min-width: 84px; }

  /* The legend would sit on top of the card on a phone; the card already
     names the kecamatan, so the legend is redundant there. */
  .legend { display: none; }
  .attrib {
    left: 10px; right: 10px; max-width: none;
    text-align: center; border-radius: 6px;
    font-size: 9.5px; padding: 4px 8px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}
