:root {
  --bg: #070a0e;
  --bg2: #0d1319;
  --panel: #121a22;
  --line: #1e2a35;
  --line2: #2b3b48;
  --text: #dfe8f0;
  --muted: #7d8b9a;
  --accent: #24c3ff;
  --live: #22d37a;
  --stale: #f0a52b;
  --err: #ff4d54;
  --rec: #ff3b3b;
  --tile-min: 210px;
  color-scheme: dark;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: radial-gradient(1200px 800px at 70% -10%, #0f1926 0%, var(--bg) 60%);
  color: var(--text); font: 14px/1.4 "IBM Plex Sans Thai", system-ui, sans-serif;
  display: flex; flex-direction: column; min-height: 100vh;
}
.mono { font-family: "JetBrains Mono", ui-monospace, monospace; }
a { color: var(--accent); text-decoration: none; }
button { font: inherit; color: var(--text); cursor: pointer; }

/* ---- top bar ---- */
.bar {
  position: sticky; top: 0; z-index: 10; display: grid; gap: 10px 16px;
  grid-template-columns: auto 1fr auto; align-items: center;
  padding: 10px 16px; background: color-mix(in srgb, var(--bg2) 88%, transparent);
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--line);
}
.ai-progress { position: absolute; left: 0; right: 0; bottom: -1px; height: 3px; background: rgba(255,255,255,.06); overflow: hidden; }
.ai-progress i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--accent), #2f9e5b); transition: width 1s ease; box-shadow: 0 0 6px var(--accent); }
.bar-left { display: flex; align-items: center; gap: 10px; }
/* "report a problem" brand — Facebook link at the far left of the bar */
.fbrand { display: inline-flex; align-items: center; gap: 6px; flex: none; text-decoration: none; color: var(--muted); }
.fbrand .fbic { width: 18px; height: 18px; fill: #1b74e4; flex: none; }
.fbrand .rtxt { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.fbrand .rtxt b { color: var(--accent); font-weight: 600; }
.fbrand:hover .rtxt b { text-decoration: underline; }
.brand-div { width: 1px; height: 20px; background: var(--line2); flex: none; }
.bar h1 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: .14em; }
.rec { width: 10px; height: 10px; border-radius: 50%; background: var(--rec); animation: pulse 1.6s infinite; }
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255,59,59,.55); } 70% { box-shadow: 0 0 0 8px rgba(255,59,59,0); } 100% { box-shadow: 0 0 0 0 rgba(255,59,59,0); } }
.clock { color: var(--muted); font-family: "JetBrains Mono", monospace; font-size: 13px; }
.statbar { display: inline-flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--muted); }
.statbar .st { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.statbar b { color: var(--text); font-family: "JetBrains Mono", monospace; font-variant-numeric: tabular-nums; font-weight: 600; }
.statbar .st:first-child b { color: var(--live); }
.live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--live); box-shadow: 0 0 6px var(--live); animation: pulse 1.8s infinite; margin-right: 2px; }
.bar-mid { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
#q { flex: 0 1 220px; min-width: 0; background: var(--panel); border: 1px solid var(--line2); color: var(--text); border-radius: 8px; padding: 7px 11px; }
#q:focus { outline: none; border-color: var(--accent); }
.chips { display: flex; gap: 6px; flex-wrap: wrap; min-width: 0; }
.chips button { background: var(--panel); border: 1px solid var(--line2); border-radius: 999px; padding: 5px 12px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.chips button:hover { border-color: var(--accent); color: var(--text); }
.chips button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #04121a; font-weight: 600; }
.filter-btn { background: var(--panel); border: 1px solid var(--line2); border-radius: 8px; padding: 7px 14px; color: var(--muted); font-size: 13px; white-space: nowrap; }
.filter-btn:hover { border-color: var(--accent); color: var(--text); }
.filter-btn b { color: var(--accent); font-weight: 600; }
.near-btn { background: var(--panel); border: 1px solid var(--line2); border-radius: 8px; padding: 7px 12px; color: var(--muted); font-size: 13px; white-space: nowrap; cursor: pointer; }
.near-btn:hover { border-color: var(--accent); color: var(--text); }
.near-btn.on { border-color: var(--accent); color: var(--accent); }
.near-btn:disabled { opacity: .5; cursor: default; }
.bar-right { display: flex; align-items: center; gap: 8px; }

/* ---- filter drawer (overlays, never pushes the wall) ---- */
.scrim { position: fixed; inset: 0; z-index: 1500; background: rgba(2,5,8,.55); }
.scrim[hidden] { display: none; }
.drawer {
  position: fixed; top: 0; left: 0; z-index: 1600; width: 320px; max-width: 88vw; height: 100vh;
  background: var(--bg2); border-right: 1px solid var(--line2); box-shadow: 2px 0 24px rgba(0,0,0,.5);
  display: flex; flex-direction: column; padding: 12px; gap: 10px; animation: slideIn .16s ease;
}
.drawer[hidden] { display: none; }
@keyframes slideIn { from { transform: translateX(-100%); } to { transform: translateX(0); } }
.drawer-head { display: flex; justify-content: space-between; align-items: center; }
.drawer-head strong { font-size: 15px; }
.drawer-head button { background: none; border: 0; color: var(--err); font-size: 16px; cursor: pointer; }
#filter-search { background: var(--panel); border: 1px solid var(--line2); color: var(--text); border-radius: 8px; padding: 8px 11px; }
#filter-search:focus { outline: none; border-color: var(--accent); }
.hide-off-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); cursor: pointer; padding: 2px 2px 6px; border-bottom: 1px solid var(--line); }
.hide-off-row input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.filter-tree { overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 2px; }
.filter-tree .all { text-align: left; background: var(--panel); border: 1px solid var(--line2); border-radius: 8px; padding: 8px 12px; color: var(--text); font-weight: 600; margin-bottom: 4px; }
.zrow { display: flex; align-items: stretch; gap: 2px; }
.zrow > button.zname { flex: 1; text-align: left; background: none; border: 0; color: var(--text); padding: 7px 8px; border-radius: 6px; font-size: 13.5px; }
.zrow > button.zname:hover { background: var(--panel); }
.zrow > button.ztog { width: 30px; background: none; border: 0; color: var(--muted); cursor: pointer; border-radius: 6px; }
.zrow > button.ztog:hover { background: var(--panel); color: var(--text); }
.zcount { color: var(--muted); font-size: 11.5px; font-family: "JetBrains Mono", monospace; }
.provs { display: none; flex-direction: column; padding-left: 12px; margin-bottom: 4px; }
.provs.open { display: flex; }
.provs button { text-align: left; background: none; border: 0; color: var(--muted); padding: 5px 8px; border-radius: 6px; font-size: 12.5px; display: flex; justify-content: space-between; }
.provs button:hover { background: var(--panel); color: var(--text); }
/* Bangkok district sub-level */
.provs .dtog { font-size: 11.5px; color: var(--accent); padding: 4px 8px; }
.dists { display: none; flex-direction: column; padding-left: 14px; border-left: 1px solid var(--line); margin: 2px 0 4px 8px; }
.dists.open { display: flex; }
.dists .distbtn { font-size: 12px; color: var(--muted); }
.filter-tree [aria-current="true"] { color: var(--accent) !important; background: color-mix(in srgb, var(--accent) 14%, transparent); }
.seg { display: flex; border: 1px solid var(--line2); border-radius: 8px; overflow: hidden; }
.seg button { background: var(--panel); border: 0; border-right: 1px solid var(--line2); padding: 6px 9px; color: var(--muted); font-size: 13px; }
.seg button:last-child { border-right: 0; }
.seg button.on { background: var(--line2); color: var(--text); }
.bar-right > button { background: var(--panel); border: 1px solid var(--line2); border-radius: 8px; padding: 6px 11px; font-size: 12.5px; letter-spacing: .04em; }
.bar-right > button:hover { border-color: var(--accent); }
#pause.paused { color: var(--stale); border-color: var(--stale); }
#hide-off.on { color: var(--accent); border-color: var(--accent); }

/* ---- stage: holds map + wall ---- */
.stage { flex: 1; display: flex; min-height: 0; position: relative; }
.map-ui { position: absolute; inset: 0; z-index: 600; pointer-events: none; display: none; }
body.mode-map .map-ui { display: block; }
.map-ui > * { pointer-events: auto; }
.map { flex: 1; min-width: 0; background: #0a1016; }
.map[hidden] { display: none; }

/* ---- wall ---- */
.wall {
  flex: 1; display: grid; gap: 3px; padding: 3px; overflow-y: auto;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-min), 1fr));
  align-content: start;
}
/* map mode: map fills, wall becomes a vertical strip on the right */
body.mode-map .wall { flex: 0 0 300px; grid-template-columns: 1fr; border-left: 1px solid var(--line); }
body.mode-map .wall-only { display: none !important; }
@media (max-width: 640px) {
  body.mode-map .stage { flex-direction: column; }
  body.mode-map .wall { flex: 0 0 40vh; grid-template-columns: 1fr 1fr; border-left: 0; border-top: 1px solid var(--line); }
}
/* 16:9 via padding-bottom — always has height even before the image loads (grid/aspect-ratio proved
   unreliable here; this classic ratio box never collapses). */
.tile {
  position: relative; margin: 0; padding: 0 0 56.25% 0; height: 0; border: 1px solid var(--line);
  background: #000; overflow: hidden; border-radius: 4px;
  transition: border-color .12s, transform .12s;
  content-visibility: auto;   /* skip painting off-screen tiles — big scroll win on phones */
  contain-intrinsic-size: auto 150px; /* reserve height when skipped (the `auto` keyword remembers the real
                                         size after first paint) — without this the ratio box collapsed to 0
                                         offscreen, making the scrollbar jump/jank on iOS. */
}
#wall-sentinel { grid-column: 1 / -1; height: 1px; margin: 0; padding: 0; } /* infinite-scroll trigger (no tile slot) */
.tile:hover { border-color: var(--accent); z-index: 2; transform: scale(1.012); }
.tile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
.tile[data-state="err"] img { opacity: 0; }
.tile[data-state="loading"] img { opacity: 0; }
.dot { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); box-shadow: 0 0 6px currentColor; }
.tile[data-state="live"] .dot { background: var(--live); color: var(--live); }
.tile[data-state="stale"] .dot { background: var(--stale); color: var(--stale); }
.tile[data-state="old"] .dot { background: #e0863a; color: #e0863a; }
.tile[data-state="old"] .tstamp { color: #ffb877; }
.tile[data-state="old"] img { filter: grayscale(.4) brightness(.8); }
.tile[data-state="err"] .dot { background: var(--err); color: var(--err); }
/* freshness: stale sinks (dimmed), dead (shown only via the 👁 toggle) is clearly flagged as not-updating */
.tile.is-stale img { filter: grayscale(.45) brightness(.78); }
.tile.is-dead img { filter: grayscale(.85) brightness(.6); }
.tile.is-dead::before { content: "ไม่อัปเดต"; position: absolute; z-index: 2; top: 5px; right: 6px; font-size: 9px; font-weight: 700; color: #ffd9b0; background: rgba(120,50,20,.82); padding: 1px 5px; border-radius: 3px; letter-spacing: .3px; }
.cap {
  position: absolute; inset: auto 0 0 0; padding: 14px 8px 5px; font-size: 11.5px; line-height: 1.25;
  color: #fff; text-align: left; background: linear-gradient(transparent, rgba(0,0,0,.82));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tstamp { position: absolute; top: 5px; left: 6px; font-size: 9.5px; color: #cfe; background: rgba(0,0,0,.55); padding: 1px 5px; border-radius: 3px; opacity: 1; }
.tile[data-state="err"] .tstamp { color: var(--err); background: rgba(0,0,0,.7); }
.tile[data-state="loading"] .tstamp { display: none; }
.tile[data-state="err"] .cap { z-index: 1; } /* keep the name visible above the OFFLINE overlay */
/* loading = spinner (busy), not a black box */
.tile[data-state="loading"] { background: #0a1016; }
.tile[data-state="loading"]::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border: 3px solid rgba(255,255,255,.18); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* first-paint loader — shown immediately (no JS needed) so mobile never sees a blank background while
   /api/cameras (≈900KB) downloads + 2300 tiles build; JS removes it once tiles are in the DOM */
.boot-loader { position: absolute; inset: 0; z-index: 30; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: radial-gradient(1200px 800px at 70% -10%, #0f1926 0%, var(--bg) 60%); color: var(--muted); font-size: 14px; }
.boot-loader .spin { width: 40px; height: 40px; border: 4px solid rgba(255,255,255,.15); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; }
.boot-loader.done { opacity: 0; pointer-events: none; transition: opacity .35s ease; }
.boot-retry { margin-top: 4px; background: var(--accent); color: #041016; border: 0; border-radius: 8px; padding: 9px 20px; font-size: 14px; font-weight: 600; cursor: pointer; }
/* offline = clear label, not a broken-image icon */
.tile[data-state="err"] { background: #17110f; }
.tile[data-state="err"]::after {
  content: "OFFLINE"; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font: 600 12px/1 "JetBrains Mono", monospace; letter-spacing: .12em; color: var(--err); opacity: .85;
}
/* NOTE: no CSS `order` here — reordering on state change makes tiles jump while scrolling. Keep DOM order fixed. */
.wall.hide-offline .tile[data-state="err"] { display: none; }

.empty { grid-column: 1/-1; text-align: center; color: var(--muted); padding: 60px; }

/* ---- map mode ---- */
.mode-seg button.on { background: var(--accent); color: #04121a; }
.leaflet-container { background: #0a1016; font: inherit; }
.leaflet-control-attribution { background: rgba(0,0,0,.5) !important; color: var(--muted) !important; }
.leaflet-control-attribution a { color: var(--muted) !important; }
.cctv-pin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.6), 0 0 8px currentColor; cursor: pointer; }
.cctv-pin.live { background: var(--live); color: var(--live); }
.cctv-pin.snap { background: var(--accent); color: var(--accent); }
.cctv-pin.water { background: #38bdf8; color: #38bdf8; border-radius: 3px; }
/* cluster bubbles: markercluster defaults to green/yellow/red by size, which reads as
   safe/danger. Force one neutral hue for all sizes — the number alone conveys count. */
.marker-cluster,
.marker-cluster-small, .marker-cluster-medium, .marker-cluster-large { background: transparent; }
.marker-cluster div,
.marker-cluster-small div, .marker-cluster-medium div, .marker-cluster-large div {
  background: rgba(36,195,255,.85); color: #04121a; font-weight: 600;
  border: 2px solid rgba(255,255,255,.85); box-shadow: 0 0 6px rgba(0,0,0,.5);
}
.marker-cluster span { color: #04121a; }
.wstation { width: 11px; height: 11px; border-radius: 2px; border: 2px solid #fff; background: var(--stale); color: var(--stale); box-shadow: 0 0 6px currentColor; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--panel); color: var(--text); border: 1px solid var(--line2); }
.leaflet-popup-content { margin: 10px 12px; font-size: 13px; }
.leaflet-popup-content b { color: var(--accent); }
.leaflet-popup-content .go { display: inline-block; margin-top: 6px; color: var(--accent); cursor: pointer; }



.map-toggles { position: absolute; top: 10px; left: 52px; z-index: 500; display: flex; gap: 6px; }
/* when moved into Leaflet's own control corners they must not keep absolute offsets */
.leaflet-control .map-toggles, .leaflet-control .map-legend { position: static; top: auto; left: auto; bottom: auto; z-index: auto; margin: 0; }
.leaflet-control .map-toggles { flex-wrap: wrap; max-width: 60vw; }
.map-toggles button.off { opacity: .5; }
.map-toggles button { background: rgba(10,16,22,.85); border: 1px solid var(--line2); color: var(--text); border-radius: 8px; padding: 6px 11px; font-size: 12.5px; cursor: pointer; }
.map-toggles button.on { border-color: var(--accent); color: var(--accent); }

/* ---- footer ---- */
.foot { border-top: 1px solid var(--line); padding: 8px 16px; font-size: 12px; color: var(--muted); background: var(--bg2); }
.foot #stat { font-family: "JetBrains Mono", monospace; }
/* sources toggle on the left, "powered by SinkAlert CCTV" pinned to the right of the same line */
.foot-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.foot-row .sources { flex: 1 1 auto; min-width: 0; }
.foot-row .pb { flex: none; margin-top: 6px; white-space: nowrap; }
.pb { font-size: 11.5px; color: var(--muted); letter-spacing: .02em; }
.pb a { color: var(--text); font-weight: 700; letter-spacing: .08em; text-decoration: none; }
.pb a:hover { color: var(--accent); }
.sources { margin-top: 6px; }
.sources summary { cursor: pointer; color: var(--accent); }
.src-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 8px 24px; margin-top: 8px; }
.src-groups h4 { margin: 0 0 4px; font-size: 12px; color: var(--text); }
.src-groups ul { margin: 0; padding-left: 16px; }
.src-groups li { margin-bottom: 4px; }

/* ---- viewer ---- */
#viewer { width: 96vw; max-width: 1500px; height: 92vh; padding: 0; border: 1px solid var(--line2); border-radius: 12px; background: var(--panel); color: var(--text); overflow: hidden; }
#viewer::backdrop { background: rgba(2,5,8,.86); backdrop-filter: blur(3px); }
.v-top { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.v-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.v-title strong { font-size: 15px; }
.v-title span { color: var(--muted); font-size: 12px; }
.v-actions { display: flex; align-items: center; gap: 6px; }
.v-actions button, .v-actions a { background: var(--bg2); border: 1px solid var(--line2); border-radius: 8px; padding: 6px 11px; font-size: 14px; line-height: 1; }
.v-actions button:hover, .v-actions a:hover { border-color: var(--accent); }
#v-close { color: var(--err); }
.v-stage { position: relative; height: calc(92vh - 52px); background: #000; display: flex; align-items: center; justify-content: center; }
#v-img, #v-video { width: 100%; height: 100%; object-fit: contain; }
#v-frame { width: 100%; height: 100%; border: 0; }
#v-video[hidden], #v-img[hidden], #v-frame[hidden] { display: none; }
.tile[data-type="youtube"] .dot { background: var(--err); color: var(--err); } /* YT = red */
.v-status { position: absolute; bottom: 10px; left: 12px; font: 12px/1 "JetBrains Mono", monospace; color: #bcd; background: rgba(0,0,0,.6); padding: 5px 9px; border-radius: 6px; }
#v-detect { border-color: #2aa6ff; color: #7cc7ff; }
.v-detect-result { position: absolute; top: 10px; left: 12px; right: 12px; max-width: 520px; font-size: 13px; padding: 8px 12px; border-radius: 8px; background: rgba(0,0,0,.82); border: 1px solid var(--line2); color: var(--text); }
.v-detect-result.loading { color: #9fc0d8; }
.v-detect-result.ok { border-color: #2f9e5b; }
.v-detect-result.flood { border-color: #ff5a5a; background: rgba(60,0,0,.82); }
.v-detect-result.err { border-color: #b46; color: #f6b0b0; }
.v-loader, .v-offline { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
.v-loader[hidden], .v-offline[hidden] { display: none; }
.v-loader { color: var(--muted); font-size: 14px; }
.v-loader .spin { width: 40px; height: 40px; border: 4px solid rgba(255,255,255,.15); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; }
.v-offline { color: var(--err); font: 600 22px/1 "JetBrains Mono", monospace; letter-spacing: .14em; }
.v-offline .ico { font-size: 40px; }
.v-offline .sub { font: 400 12px/1.4 "IBM Plex Sans Thai", sans-serif; color: var(--muted); letter-spacing: 0; max-width: 70%; text-align: center; }

@media (max-width: 720px) {
  /* iOS Safari re-blurs the content under a sticky backdrop-filter EVERY scroll frame, and leaves the
     <dialog>::backdrop blur layer stuck after close — together they freeze scrolling right after you
     close a camera. Drop the blur on phones (opaque bg instead); desktop keeps the glass look. */
  .bar { backdrop-filter: none; background: var(--bg2); }
  #viewer::backdrop { backdrop-filter: none; background: rgba(2,5,8,.92); }
  /* phones: brand + stats alone on row 1; ALL controls (filters, Wall/แผนที่, fullscreen) drop to row 2
     so the top line stays uncluttered. Flex-wrap lets filters + mode buttons share/flow on row 2. */
  .bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; padding: 8px 10px; }
  .bar-left { gap: 7px; flex: 1 1 100%; min-width: 0; }   /* row 1, full width */
  .brand-div { display: none; }                  /* trim divider on phones */
  .fbrand .rtxt { font-size: 12px; }
  .clock { display: none; }                      /* save width on phones */
  .statbar .st-hide-sm { display: none; }         /* keep only "ออนไลน์" on phones */
  .statbar { gap: 6px; }
  /* unwrap the two control groups so their buttons become DIRECT flex items of the bar and flow +
     wrap together as one continuous row (design B), instead of each group grabbing its own line. */
  .bar-mid, .bar-right { display: contents; }
  .filter-btn, .near-btn { flex: 0 1 auto; text-align: left; font-size: 11.5px; padding: 5px 8px; }
  .bar-right > button, .mode-seg button { font-size: 11.5px; padding: 5px 8px; }
  .seg.wall-only { display: none; }             /* hide the density control on phones */
  #full { display: none; }                      /* hide fullscreen on phones (iOS Safari can't really use it) → controls fit one row */
  :root { --tile-min: 46vw; }
  #viewer { width: 100vw; height: 100vh; border-radius: 0; }
  .v-top { padding: 8px 10px; }
  .v-title strong { font-size: 13px; } .v-title span { font-size: 11px; }
  .v-actions button, .v-actions a { padding: 6px 9px; font-size: 13px; }
  .v-stage { height: calc(100vh - 50px); }
  .foot { font-size: 11px; }
  .src-groups { grid-template-columns: 1fr; }
  /* map overlays smaller so they don't cover the phone map */
  .leaflet-control .map-toggles { flex-wrap: nowrap; gap: 4px; max-width: none; }
  .map-toggles button { font-size: 10.5px; padding: 4px 7px; white-space: nowrap; }
  /* legend collapses to just its header on phones; tap to expand */
  .map-legend { font-size: 10px; padding: 0; gap: 0; bottom: 6px; left: 6px; background: none; border: none; }
  .map-legend.open { background: rgba(10,16,22,.88); border: 1px solid var(--line2); }
  .map-legend .legend-head { display: block; }
  .map-legend:not(.open) .row { display: none; }
  .map-legend.open .row { display: flex; }
  .map-legend.open { padding: 6px 8px; gap: 3px; }
  .map-legend .row.rain b.rainbar { width: 48px; }
}
.legend-head { display: none; background: rgba(10,16,22,.9); border: 1px solid var(--line2); color: var(--text); border-radius: 8px; padding: 5px 9px; font-size: 11px; cursor: pointer; font-family: inherit; }
.map-legend.open .legend-head { margin-bottom: 5px; }
/* very small phones: single-column wall */
@media (max-width: 380px) { :root { --tile-min: 96vw; } }

/* pin-selected tile in the map strip */
.tile.focus { outline: 3px solid var(--accent); outline-offset: -3px; z-index: 3; }
.map-toggles #pin-clear { border-color: var(--accent); color: var(--accent); }

/* map legend — clean rows, no overlap */
.map-legend { position: absolute; bottom: 10px; left: 10px; z-index: 500; background: rgba(10,16,22,.88); border: 1px solid var(--line2); border-radius: 8px; padding: 8px 11px; font-size: 12px; display: flex; flex-direction: column; gap: 6px; }
.map-legend .row { display: flex; align-items: center; gap: 7px; color: var(--muted); white-space: nowrap; }
.map-legend .dotc { width: 11px; height: 11px; border-radius: 50%; flex: none; }
.map-legend .dotc.live { background: var(--live); }
.map-legend .dotc.snap { background: var(--accent); }
.map-legend .dotc.bolt { background: #ffd633; border-radius: 2px; }
.map-legend .row.rain b.rainbar { width: 70px; height: 9px; border-radius: 3px; background: linear-gradient(90deg,#3aa0ff,#66c2a5,#e6f598,#fdae61,#f46d43,#d53e4f); flex: none; }
.lightning { width: 12px; height: 12px; border-radius: 50%; background: #ffd633; box-shadow: 0 0 10px #ffd633; }

/* lightning strike bolt (fades + pops) */
.bolt-strike { font-size: 20px; line-height: 1; text-align: center; filter: drop-shadow(0 0 6px #ffd633); animation: strike 2.4s ease-out forwards; }
@keyframes strike { 0% { opacity: 0; transform: scale(.4); } 12% { opacity: 1; transform: scale(1.35); } 30% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(.9); } }

/* flood layer */
.map-legend .dotc.flood { background: #f0a52b; }
.map-legend .dotc.ai { background: #ff2d6f; }
.flood-alert { font-size: 18px; line-height: 1; filter: drop-shadow(0 0 5px #24c3ff); animation: floodpulse 1.8s ease-in-out infinite; }
/* AI flood detection: map pin + wall tile badge */
.ai-flood-pin { font: 700 11px/20px "IBM Plex Sans Thai", sans-serif; text-align: center; color: #fff; background: #ff2d6f; border: 1px solid #fff; border-radius: 10px; box-shadow: 0 0 6px rgba(0,0,0,.6); }
.tile.ai-flood { outline: 2px solid #ff2d6f; outline-offset: -2px; }
.tile.ai-flood::before { content: "💧 น้ำท่วม"; position: absolute; top: 4px; right: 4px; z-index: 3; font: 600 10px/1 "IBM Plex Sans Thai", sans-serif; color: #fff; background: #ff2d6f; padding: 3px 6px; border-radius: 5px; box-shadow: 0 1px 4px rgba(0,0,0,.5); }
/* ⭐ favorite: tap the star to pin a camera above everything (flood too). Persists in localStorage. */
/* dark circular chip behind the star so it reads on ANY frame — bright daytime or dark night — without
   per-tile day/night detection (same trick as the timestamp chip). */
.tile .fav { position: absolute; right: 4px; bottom: 4px; z-index: 4; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1; color: #fff; background: rgba(0,0,0,.45); border-radius: 50%; cursor: pointer; text-shadow: 0 1px 2px rgba(0,0,0,.9); transition: color .12s, background .12s, transform .12s; }
.tile .fav:hover { color: #ffd24a; background: rgba(0,0,0,.62); transform: scale(1.12); }
.tile.is-fav .fav { color: #ffcf33; background: rgba(0,0,0,.5); text-shadow: 0 0 6px rgba(255,180,0,.75), 0 1px 2px rgba(0,0,0,.9); }
.tile.is-fav { outline: 2px solid #ffcf33; outline-offset: -2px; }   /* favorite wins over the flood outline */
#v-fav.on { color: #ffcf33; border-color: #ffcf33; }
/* transient bottom toast (geolocation guidance, etc.) */
.toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%) translateY(12px); z-index: 1000; max-width: min(92vw, 460px); background: var(--panel); color: var(--text); border: 1px solid var(--line2); border-radius: 10px; padding: 10px 14px; font-size: 13px; line-height: 1.45; box-shadow: 0 6px 24px rgba(0,0,0,.5); opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; text-align: center; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
#ai-toggle.on { border-color: #ff2d6f; color: #ff86ab; }
/* satellite cloud layer */
#cloud-toggle.on { border-color: #cfe3ef; color: #eaf2f7; }
.map-legend .dotc.cloud { background: #d5dee6; }
.cloud-tiles { filter: brightness(1.05); }
@keyframes floodpulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.25); } }
