:root{--bg:#f4f6f9;--fg:#1c2733;--muted:#6b7a8a;--card:#fff;--line:#d9e0e8;--accent:#1f77b4;
--ok:#2e9e4f;--warn:#e69a00;--off:#8a949e}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#10161d;--fg:#e6edf3;--muted:#93a1b0;--card:#18212b;--line:#2b3744;--accent:#58a6ff}}
:root[data-theme="dark"]{--bg:#10161d;--fg:#e6edf3;--muted:#93a1b0;--card:#18212b;--line:#2b3744;--accent:#58a6ff}

/* the hidden attribute must win over any display rule below (.gate/.setrow/.split set display and would otherwise override it) */
[hidden]{display:none !important}
:root{--top:56px}
*{box-sizing:border-box}
html,body{height:100%;margin:0}
body{font:14px/1.4 system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans Khmer",sans-serif;color:var(--fg);background:var(--bg);overflow:hidden}
/* min-height, not height: the brand block now carries the purpose and the credit, and a
   fixed height clipped the last line. The map offset follows via syncTop(). */
#top{position:absolute;z-index:1000;top:0;left:0;right:0;min-height:44px;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:4px 12px;padding:4px 14px;background:var(--card);border-bottom:1px solid var(--line);box-shadow:0 1px 6px #0002}
/* .controls never shrinks (flex:0 0 auto, below) -- without its own floor .brand took all the squeeze
   when the row ran out of room and got crushed to a sliver (confirmed: Khmer title wrapping one
   character per line). flex-wrap on #top lets brand and controls each take their own full-width row
   instead of fighting over one; the basis keeps brand from being squeezed smaller than that first. */
.brand{display:flex;align-items:center;gap:8px;flex:1 1 260px;min-width:0}
.brand b{display:block;font-size:15px;white-space:nowrap;line-height:1.25}
.brand small{color:var(--muted);font-size:11px}
#sub{display:block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* purpose/sources/credits text moved into the About panel, one click away (see #brandMore) --
   the header keeps only the title and the live station count, so it stays to two lines even on a
   wide desktop screen instead of growing with every extra line of mostly-static text. */
.linklike{background:none;border:0;padding:0;margin-left:4px;color:var(--accent);font-size:11px;
  text-decoration:underline;cursor:pointer}

/* full screen shows only the map -- the header (with every control) is hidden entirely; #map's own
   top offset (--top, set from #top's measured height) collapses to 0 on its own once #top is gone,
   via the same ResizeObserver this app already uses for normal header-height changes. The one way
   back in while the header is hidden is this floating button, positioned clear of Leaflet's own
   zoom control (top-left) -- Esc always works too, as the browser's own built-in fullscreen exit. */
body.map-fullscreen #top{display:none}
#mapExitFs{display:none;position:fixed;z-index:1100;top:10px;right:10px;padding:6px 12px;
  background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:6px;
  font-size:12px;cursor:pointer;box-shadow:0 1px 6px #0003}
body.map-fullscreen #mapExitFs{display:block}
/* wrap, not nowrap: with the auto-refresh/full-screen controls and the per-replay step-info text
   now in this row too, a fixed-width nowrap row started pushing Radar/Stations/Theme/Lang off the
   right edge entirely on anything narrower than a very wide monitor -- wrapping onto a second row
   keeps every control reachable instead of silently missing. (Below 900px this is overridden by its
   own horizontal-scroll pattern, kept deliberately as a single row there -- see the responsive block.) */
.controls{display:flex;align-items:center;gap:8px 12px;flex-wrap:wrap;flex:0 0 auto}
.controls label{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:12px;white-space:nowrap}
select{font:inherit;color:var(--fg);background:var(--card);border:1px solid var(--line);border-radius:6px;padding:5px 8px;max-width:240px}
.chk input{margin-right:2px}
.btn{color:var(--accent);border:1px solid var(--line);padding:5px 10px;border-radius:6px;text-decoration:none;font-size:12px}
.btn.ghost{background:none;cursor:pointer}
/* flex-wrap:wrap so .movesummary's flex-basis:100% actually breaks onto its own line (its whole
   point) instead of just stretching this row very wide with nowhere to wrap to, which is what a
   nowrap parent did -- the movement sentence and the header's own time/date overlapping was that bug. */
.replaybar{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.replaybar #replayPlay{width:28px;height:28px;padding:0;display:flex;align-items:center;justify-content:center;
  font-size:12px;line-height:1}
.replaybar #replayPlay.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.replaytime{font-size:11px;color:var(--muted);white-space:nowrap;min-width:0}
@media (max-width:760px){.replaytime{display:none}}
.replayspeedinfo{font-size:11px;color:var(--muted);white-space:nowrap}
.movesummary{flex-basis:100%;font-size:12px;color:var(--fg);background:var(--card);
  border:1px solid var(--line);border-radius:6px;padding:4px 8px;margin-top:2px}
.movesummary b{color:var(--accent)}

/* "Was this prediction right?" -- the station drawer's full card and the map label's compact version */
.fbcard{margin-top:10px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--card)}
.fblabel{margin:0 0 4px;font-size:12px;color:var(--muted)}
.fbstars{display:flex;gap:2px}
.fbstar,.fbstar-mini{background:none;border:0;cursor:pointer;font-size:20px;line-height:1;padding:2px;
  color:var(--line)}
.fbstar-mini{font-size:15px}
.fbstar:hover,.fbstar.hover,.fbstar-mini:hover{color:#f5a623}
.fbstar.on,.fbstar-mini.on{color:#f5a623}
.fbstar:disabled,.fbstar-mini:disabled{cursor:default}
.fbmsg{margin:4px 0 0;font-size:11px;min-height:14px}
.fbmini{margin-top:4px;font-size:11px;color:var(--muted)}
.fbminimsg{display:block;font-size:10px;margin-top:2px}
#map{position:absolute;top:var(--top);left:0;right:0;bottom:0}
.panel{position:absolute;z-index:900;background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;box-shadow:0 2px 8px #0003;font-size:12px}
/* both floating summaries stack bottom-left together, satellite above the measurement legend, each
   with its own minimize button (below) rather than the legend's old mobile-only fold */
#bottomPanels{position:absolute;z-index:900;left:10px;bottom:24px;display:flex;flex-direction:column;
  gap:8px;max-width:230px;max-height:calc(100vh - var(--top) - 70px);overflow-y:auto;overflow-x:hidden}
.legend{position:static;max-width:230px}
/* a plain compass rose: OpenStreetMap/Leaflet tiles never rotate, so this is a constant, honest check
   that "up" on this map really is north — useful once the page is full of direction arrows */
.compass{right:10px;top:calc(var(--top) + 10px);display:flex;flex-direction:column;align-items:center;
  gap:1px;padding:5px 7px;color:var(--fg);pointer-events:none;user-select:none}
.compass span{font-size:10px;font-weight:700;color:var(--muted)}
.legend .bar{height:10px;border-radius:5px;margin:4px 0}
.legend .scale{display:flex;justify-content:space-between;color:var(--muted)}
.legend .dots{margin-top:6px;display:flex;flex-wrap:wrap;gap:4px 10px}
.legend .dots i{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:4px;vertical-align:-1px}
.status{left:10px;bottom:150px;display:none}
/* markers */
/* Round markers. A fixed square box with a 50% radius keeps every marker the same
   circle whatever it reads; the type shrinks instead of the shape stretching. */
.mk{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;color:#fff;font-weight:700;font-size:14px;letter-spacing:-.02em;border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.35),0 2px 6px rgba(0,0,0,.45);text-shadow:0 1px 1.5px rgba(0,0,0,.45);white-space:nowrap}
.mk.len4{font-size:12px}
.mk.len5{font-size:10.5px}
.mk.s-delayed{opacity:.8;border-color:var(--warn)}
.mk.s-offline,.mk.s-nodata{opacity:.65;border-style:dashed;border-color:#555}
.mk.s-modeled{border-style:dotted;border-color:#8a5cd6}
.mk.s-nofeed{background:#fff!important;color:#555;border:2px dashed #888;width:14px;height:14px;padding:0;border-radius:50%}
.leaflet-popup-content{margin:10px 12px;min-width:210px}
.pop h4{margin:0 0 2px;font-size:15px}
.srcline{font-size:11px;margin:0 0 4px}
.pop .sub{color:#667;font-size:12px;margin-bottom:6px}
.pop table{width:100%;border-collapse:collapse;margin:4px 0 8px}
.pop td{padding:2px 0;border-bottom:1px solid #0001}
.pop td:last-child{text-align:right;font-weight:600}
.pop button{width:100%;border:0;border-radius:6px;background:var(--accent);color:#fff;padding:7px;cursor:pointer;font-size:13px}
.badge{display:inline-block;border-radius:9px;padding:1px 8px;font-size:11px;color:#fff}
.badge.online{background:var(--ok)}.badge.delayed{background:var(--warn)}.badge.offline,.badge.nodata{background:var(--off)}.badge.nofeed{background:#999}.badge.modeled{background:#8a5cd6}
/* drawer */
.drawer{position:absolute;z-index:1100;top:var(--top);right:0;bottom:0;width:min(520px,100%);background:var(--card);border-left:1px solid var(--line);box-shadow:-4px 0 14px #0004;overflow:auto;padding:12px 14px}
.drawer[hidden]{display:none}
#close{position:absolute;top:8px;right:12px;border:0;background:none;font-size:26px;color:var(--muted);cursor:pointer}
.drawer h2{margin:0 24px 2px 0;font-size:17px}
.drawer .sub{color:var(--muted);margin-bottom:8px;font-size:12px}
/* Compact tiles: three or four fit per row of the drawer instead of two, so a station's whole
   set of readings is visible without scrolling. */
.kv{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:6px;margin:8px 0}
.kv div{background:var(--bg);border-radius:7px;padding:5px 7px}
.kv small{display:block;color:var(--muted);font-size:10px;line-height:1.25;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kv b{font-size:15px;line-height:1.2}
.kv i{font-style:normal;color:var(--muted);font-size:10px;margin-left:2px}
.drawer h3{margin:12px 0 5px;font-size:13px}
.tabs{display:flex;gap:6px;margin:8px 0}
.tabs button{border:1px solid var(--line);background:var(--card);color:var(--fg);border-radius:14px;padding:3px 11px;cursor:pointer;font-size:12px}
.tabs button.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.chartbox{height:210px;position:relative}
table.t{width:100%;border-collapse:collapse;font-size:13px}
table.t th,table.t td{padding:4px 6px;border-bottom:1px solid var(--line);text-align:right}
table.t th:first-child,table.t td:first-child{text-align:left}
table.t th{color:var(--muted);font-weight:500;font-size:12px}
@media (max-width:760px){
  #top{height:auto;flex-direction:column;align-items:stretch;padding:8px 12px;gap:6px}
  .controls{flex-wrap:wrap;gap:8px}
  .drawer{top:0;z-index:1200}
  .legend{bottom:10px}
}

/* language switch */
.lang{display:inline-flex;border:1px solid var(--line);border-radius:6px;overflow:hidden}
.lang button{border:0;background:var(--card);color:var(--muted);font:inherit;font-size:12px;
padding:5px 9px;cursor:pointer}
.lang button.on{background:var(--accent);color:#fff}
.lang button+button{border-left:1px solid var(--line)}
html[lang=km] body{font-family:"Noto Sans Khmer",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
html[lang=km] .mk{font-family:system-ui,sans-serif}  /* keep the numeric markers compact */

/* nearest-station card and the permission prompt */
.ask{left:50%;transform:translateX(-50%);top:calc(var(--top) + 14px);max-width:330px;text-align:center}
.ask p{margin:0 0 9px;font-size:13px}
.ask .row{display:flex;gap:8px;justify-content:center;margin-bottom:7px}
.ask small{font-size:11px}
.btn.ghost{color:var(--muted)}
.near{right:10px;top:calc(var(--top) + 14px);width:240px}
.near h4{margin:0 0 1px;font-size:14px;padding-right:16px}
.near .dist{color:var(--muted);font-size:11px;margin-bottom:7px}
.near table{width:100%;border-collapse:collapse;margin-bottom:8px}
.near td{padding:2px 0;font-size:12px;border-bottom:1px solid #0001}
.near td:last-child{text-align:right;font-weight:600}
.near button.go{width:100%;border:0;border-radius:6px;background:var(--accent);color:#fff;
padding:6px;cursor:pointer;font-size:12px}
.near .others{margin-top:9px;font-size:11px;color:var(--muted)}
.near .others a{display:block;color:var(--accent);text-decoration:none;padding:2px 0;cursor:pointer}
#nearClose{position:absolute;top:3px;right:7px;border:0;background:none;font-size:19px;
color:var(--muted);cursor:pointer;line-height:1}
.youmark{width:14px;height:14px;border-radius:50%;background:#1a73e8;border:2px solid #fff;
box-shadow:0 0 0 6px #1a73e833,0 1px 4px #0006}
@media (max-width:760px){.near{right:10px;left:10px;width:auto;top:auto;bottom:10px}
.ask{top:auto;bottom:10px;left:10px;right:10px;transform:none;max-width:none}}

/* date-range history block */
.rangebox{margin-top:6px}

/* the small Open-Meteo-vs-Custom comparison table in the station drawer (app.js:loadCustomPrediction) */
.pdsmall{width:100%;max-width:420px;border-collapse:collapse;font-size:12px;margin:4px 0}
.pdsmall td,.pdsmall th{padding:4px 6px;border-bottom:1px solid var(--line);text-align:left}
.pdsmall td.num,.pdsmall th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}

/* the 5/10/15/30/60-min nowcast strip (app.js:loadNowcast) -- five small boxes in a row, each
   bordered in its own verdict colour so a quick glance at the row shows if/when rain is coming */
.ncstrip{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
.ncbox{flex:1 1 56px;min-width:56px;border:1.5px solid var(--line);border-radius:8px;
  padding:6px 4px;text-align:center;background:var(--card)}
.nch{font-size:11px;color:var(--muted)}
.ncp{font-size:17px;font-weight:700;line-height:1.3}
.ncv{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.02em}
.ncsw{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
.rangectl{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin-bottom:8px}
.rangectl .f{display:flex;flex-direction:column;gap:3px}
.rangectl label{font-size:11px;color:var(--muted)}
.rangectl input,.rangectl select{font:inherit;font-size:12px;padding:5px 7px;border:1px solid var(--line);
border-radius:6px;background:var(--card);color:var(--fg)}
.rangectl button{font:inherit;font-size:12px;padding:6px 12px;border:0;border-radius:6px;
background:var(--accent);color:#fff;cursor:pointer}
.rangectl button.alt{background:var(--card);color:var(--accent);border:1px solid var(--line)}
.charttype{display:flex;align-items:center;gap:6px;margin-bottom:8px}
.charttype label{font-size:11px;color:var(--muted)}
.charttype select{font:inherit;font-size:12px;padding:5px 7px;border:1px solid var(--accent);
border-radius:6px;background:var(--card);color:var(--fg)}
.quick{display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap}
.quick button{font:inherit;font-size:11px;padding:3px 10px;border:1px solid var(--line);
border-radius:12px;background:var(--card);color:var(--fg);cursor:pointer}
.quick button.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.rangemeta{font-size:11px;color:var(--muted);margin-bottom:6px}
.rangechart{height:230px;position:relative}

/* rain state icon: drops fall in a staggered loop while it is raining */
.wx{display:inline-block;position:relative;width:1.5em;height:1.5em;vertical-align:-.35em;flex:none}
.wx svg{width:100%;height:100%;display:block;overflow:visible}
.wx .drop{transform-origin:center;animation:wxfall 1.15s linear infinite}
.wx .drop:nth-of-type(2){animation-delay:.38s}
.wx .drop:nth-of-type(3){animation-delay:.76s}
@keyframes wxfall{
  0%{opacity:0;transform:translateY(-2px) scaleY(.7)}
  18%{opacity:1}
  70%{opacity:1;transform:translateY(5px) scaleY(1.15)}
  100%{opacity:0;transform:translateY(8px) scaleY(1.25)}
}
/* marker badge */
.mk{position:relative}
.rainbadge{position:absolute;top:-9px;right:-9px;width:17px;height:17px;border-radius:50%;
background:#1565c0;border:2px solid #fff;box-shadow:0 1px 3px #0007;display:flex;
align-items:center;justify-content:center}
.rainbadge .wx{width:11px;height:11px;vertical-align:0}
/* chips in the popup and drawer */
.rainchip{display:inline-flex;align-items:center;gap:6px;border-radius:13px;padding:3px 11px 3px 7px;
font-size:12px;font-weight:600;margin:5px 0}
.rainchip.on{background:#1565c0;color:#fff}
.rainchip.off{background:var(--bg);color:var(--muted);border:1px solid var(--line)}
.rainchip small{font-weight:400;opacity:.85}
@media (prefers-reduced-motion:reduce){.wx .drop{animation:none;opacity:1}}

/* wind mode: a small compass badge on the marker, shown only while "Wind" is the chosen measurement.
   The direction itself is drawn on the map as a real line (.flowline / .flowhead, below), not here. */
.mkwrap{position:relative;width:100%;height:100%}
.mkwrap .mk{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) scale(var(--wx-zoom-scale,1))}
.windlbl{position:absolute;left:-2px;top:-9px;font-size:9px;font-weight:700;
  color:#1c2733;background:#fff;border-radius:6px;padding:0 3px;line-height:13px;box-shadow:0 1px 2px #0005;
  pointer-events:none;white-space:nowrap;z-index:1}
:root[data-theme="dark"] .windlbl{color:#eef2f6;background:#1c2733}

/* wind streamlines: a real geo-anchored line from each station toward where the air is heading, with
   a flowing dash pattern (speed sets how fast the dashes move) and a small arrowhead at the far end.
   Being actual map geometry, not marker-icon pixels, they keep the right length and bearing at every
   zoom and read as one connected flow across the network instead of 50 separate little arrows. */
.flowline{stroke:#1f77b4;stroke-dasharray:7 8;filter:drop-shadow(0 0 2px rgba(31,119,180,.55));
  animation-name:flowdash;animation-timing-function:linear;animation-iteration-count:infinite}
.flowline.slow{animation-duration:1.8s}
.flowline.med{animation-duration:1.1s}
.flowline.fast{animation-duration:.6s}
@keyframes flowdash{to{stroke-dashoffset:-30}}
:root[data-theme="dark"] .flowline{stroke:#58a6ff}
.flowhead{filter:drop-shadow(0 1px 1px rgba(0,0,0,.5))}
@media (prefers-reduced-motion:reduce){.flowline{animation:none;stroke-dasharray:none}}

/* rain replay: falling streaks around the marker, count and speed set by the step's intensity band */
.rainanim{position:absolute;left:50%;top:50%;width:64px;height:64px;transform:translate(-50%,-50%);
  pointer-events:none;z-index:0}
.rainanim svg{display:block;width:100%;height:100%;overflow:visible}
.rn-drop{stroke:#5fb8e0;stroke-width:2;stroke-linecap:round;opacity:0;
  filter:drop-shadow(0 0 1px rgba(0,40,70,.5));
  animation-name:raindrop;animation-timing-function:linear;animation-iteration-count:infinite}
@keyframes raindrop{
  0%{opacity:0;transform:translateY(-4px)}
  15%{opacity:.95}
  85%{opacity:.8}
  100%{opacity:0;transform:translateY(58px)}
}
.rn-flash{position:absolute;inset:-4px;border-radius:50%;background:radial-gradient(circle,#fff9 0%,transparent 70%);
  opacity:0;animation-name:rnflash;animation-timing-function:ease-out;animation-iteration-count:infinite}
@keyframes rnflash{0%,88%{opacity:0}90%{opacity:1}94%{opacity:0}97%{opacity:.6}100%{opacity:0}}
:root[data-theme="dark"] .rn-drop{stroke:#8fd3ff}
@media (prefers-reduced-motion:reduce){.rn-drop{animation:none;opacity:.7}.rn-flash{animation:none;opacity:0}}

/* theme switcher: auto / light / dark, same visual family as the language switch */
.theme{display:inline-flex;border:1px solid var(--line);border-radius:6px;overflow:hidden}
.theme button{border:0;background:var(--card);color:var(--muted);font:inherit;font-size:12px;
padding:5px 9px;cursor:pointer;line-height:1}
.theme button.on{background:var(--accent);color:#fff}
.theme button+button{border-left:1px solid var(--line)}

/* forecast card
   Laid out like a weather service's own card: a "today" band from the forecast model, then a
   "current" block whose big reading and rows are the station's measurements. The two are kept
   visually distinct on purpose - a prediction must never be mistaken for a measurement. */
.wxcard{border:1px solid var(--line);border-radius:10px;overflow:hidden;margin:6px 0 10px;
background:var(--card)}
.wxhead{display:flex;justify-content:space-between;align-items:center;gap:8px;
padding:7px 11px;background:var(--bg);border-bottom:1px solid var(--line);
font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
.wxhead span:last-child{letter-spacing:.04em;color:var(--fg);font-weight:600}
.wxtoday{padding:8px 11px;border-bottom:1px solid var(--line)}
.wxtoday .tline{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}
.wxtoday .tline span{flex:1;min-width:0}
.wxtoday .tline b{white-space:nowrap;font-size:14px}
.wxnow{display:grid;grid-template-columns:minmax(118px,auto) 1fr;gap:10px;padding:10px 11px}
.wxnow .big{text-align:center;display:flex;flex-direction:column;align-items:center;gap:1px}
.wxnow .deg{font-size:40px;font-weight:700;line-height:1;letter-spacing:-.02em}
.wxnow .deg sup{font-size:20px;vertical-align:super;font-weight:600}
.wxnow .deg em{font-style:normal;font-size:16px;color:var(--muted);margin-left:2px}
.wxnow .feels,.wxnow .cond{font-size:12px;color:var(--muted)}
.wxnow .cond{font-weight:600;color:var(--fg)}
.wxrows{display:flex;flex-direction:column;justify-content:center}
.wxrows .r{display:flex;justify-content:space-between;gap:10px;align-items:baseline;
padding:4px 0;border-bottom:1px solid var(--line);font-size:13px}
.wxrows .r:last-child{border-bottom:0}
.wxrows .r span{color:var(--muted)}
.wxrows .r b{font-variant-numeric:tabular-nums;white-space:nowrap}

/* chance of rain, next 1/2/6 hours */
.rainout{border-top:1px solid var(--line);padding:8px 11px}
.rohead{display:flex;align-items:center;gap:5px;font-size:11px;text-transform:uppercase;
letter-spacing:.06em;color:var(--muted);margin-bottom:6px}
.rorow{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
.ro{background:var(--bg);border-radius:7px;padding:5px 7px;text-align:center}
.ro small{display:block;color:var(--muted);font-size:10px}
.ro b{font-size:16px;font-variant-numeric:tabular-nums}
.ro i{display:block;font-style:normal;font-size:10px;color:var(--muted)}
.ro.wet{background:#1f77b41f}
.ro.wet b{color:#1f6fb0}
:root[data-theme="dark"] .ro.wet b{color:#7cc0f5}

/* one-line verdict on the map popup */
.verdict{display:flex;align-items:center;gap:6px;border-radius:7px;padding:4px 8px;margin:6px 0;
font-size:12px;background:var(--bg)}
.verdict span{flex:1}
.verdict em{font-style:normal;font-weight:700;font-variant-numeric:tabular-nums}
.verdict.wet{background:#1f77b41f;color:#13558c}
.verdict.maybe{background:#e69a0020}
:root[data-theme="dark"] .verdict.wet{color:#8ec9f7}
.pophl{font-size:12px;color:var(--muted);margin:4px 0 6px}
.pophl b{color:var(--fg)}
.pophl small{float:right;text-transform:uppercase;letter-spacing:.05em;font-size:10px}

/* hourly strip */
.fcbox{margin-bottom:4px}
.fstrip{display:flex;gap:5px;overflow-x:auto;padding-bottom:5px;scrollbar-width:thin}
.fh{flex:0 0 58px;text-align:center;background:var(--bg);border-radius:8px;padding:6px 3px;
display:flex;flex-direction:column;align-items:center;gap:1px}
.fh small{font-size:10px;color:var(--muted)}
.fh .p{font-size:12px;color:#1f6fb0;font-variant-numeric:tabular-nums}
.fh .t{font-size:13px;font-weight:600}
.fh i{font-style:normal;font-size:10px;color:var(--muted);min-height:12px}
.fh.wet{background:#1f77b418}
:root[data-theme="dark"] .fh .p{color:#7cc0f5}

/* 16-day list */
.fdays{display:flex;flex-direction:column}
.fd{display:grid;grid-template-columns:62px 24px 38px 52px 1fr 76px;gap:6px;align-items:center;
padding:5px 2px;border-bottom:1px solid var(--line);font-size:12px}
.fd:last-child{border-bottom:0}
.fd.wet{background:#1f77b40d}
.fd .dd{font-weight:600}
.fd .dd small{display:block;font-weight:400;color:var(--muted);font-size:10px}
.fd .pp{color:#1f6fb0;font-variant-numeric:tabular-nums;text-align:right}
.fd .mm{color:var(--muted);font-variant-numeric:tabular-nums;text-align:right;font-size:11px}
.fd .bar{position:relative;height:5px;background:var(--bg);border-radius:3px}
.fd .bar i{position:absolute;top:0;height:5px;border-radius:3px;
background:linear-gradient(90deg,#4a9fe0,#e0a64a)}
.fd .hl{text-align:right;font-variant-numeric:tabular-nums;color:var(--muted)}
.fd .hl b{color:var(--fg)}
:root[data-theme="dark"] .fd .pp{color:#7cc0f5}

/* month: climate bars, kept visually apart from the forecast above them */
.fsub{margin:14px 0 6px;font-size:13px}
.fsub small{display:block;font-weight:400;font-size:11px;color:var(--muted)}
.clim{display:grid;grid-template-columns:repeat(12,1fr);gap:3px;align-items:end;
border-top:1px dashed var(--line);padding-top:8px}
.cm{text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:flex-end}
.cm i{display:block;width:70%;background:#8aa6c0;border-radius:2px 2px 0 0}
.cm b{font-size:9px;font-weight:600;font-variant-numeric:tabular-nums}
.cm small{font-size:9px;color:var(--muted)}

.fcfoot{margin:8px 0 0;font-size:11px;color:var(--muted)}
.wxi{vertical-align:middle;flex:none}

/* place lookup */
.placebox{position:relative}
.placebox input{font:inherit;font-size:13px;padding:5px 9px;border:1px solid var(--line);
border-radius:7px;background:var(--card);color:var(--fg);width:190px}
.hits{position:absolute;top:calc(100% + 4px);left:0;z-index:1200;width:260px;max-height:320px;
overflow:auto;background:var(--card);border:1px solid var(--line);border-radius:8px;
box-shadow:0 6px 20px #0003}
.hits button{display:block;width:100%;text-align:left;border:0;background:none;color:var(--fg);
font:inherit;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--line)}
.hits button:last-child{border-bottom:0}
.hits button:hover{background:var(--bg)}
.hits small{display:block;color:var(--muted);font-size:11px}
.applies{margin:8px 11px 10px;font-size:11px;color:var(--muted);line-height:1.4}

/* MORE DETAILS, air quality, sun & moon */
.moredet{border-top:1px solid var(--line)}
.moredet summary{cursor:pointer;padding:7px 11px;font-size:11px;letter-spacing:.07em;
text-transform:uppercase;color:var(--accent);list-style:none}
.moredet summary::-webkit-details-marker{display:none}
.moredet summary::after{content:' \25be'}
.moredet[open] summary::after{content:' \25b4'}
.dgrid{display:grid;grid-template-columns:1fr 1fr;gap:0 14px;padding:0 11px 6px}
.dgrid .r{display:flex;justify-content:space-between;gap:8px;align-items:baseline;padding:4px 0;
border-bottom:1px solid var(--line);font-size:12px}
.dgrid .r span{color:var(--muted)}
.dgrid .r b{font-variant-numeric:tabular-nums;white-space:nowrap}
.dnote{margin:6px 11px 9px;font-size:10px;color:var(--muted)}
.fcmark{font-style:normal;opacity:.6;cursor:help}
.aq{border-radius:9px;padding:1px 7px;font-size:11px;color:#fff}
.aq.good{background:#2e9e4f}.aq.fair{background:#8bbf2a}.aq.moderate{background:#e0a900}
.aq.poor{background:#e2662b}.aq.very_poor{background:#d6333f}.aq.extremely_poor{background:#8a1030}
.sunmoon{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;border-top:1px solid var(--line);
padding:8px 11px}
.sunmoon div{text-align:center}
.sunmoon small{display:block;color:var(--muted);font-size:10px;text-transform:uppercase;
letter-spacing:.05em}
.sunmoon b{font-size:12px}
/* The 10-day rows carry the day and night wording on a second line rather than a seventh
   column: the drawer is ~520px wide, so a column would have squeezed it out of sight. */
.fd{grid-template-columns:62px 24px 38px 52px 1fr 76px}
.fd .cond2{grid-column:1/-1;font-size:11px;color:var(--muted);padding:2px 0 0 62px;
display:flex;gap:10px;flex-wrap:wrap}
.fd .cond2 small{font-size:11px;opacity:.85}

/* Two cards, never one.
   The station's own readings and the forecast model sit in separate cards with different
   accents, because a reader must never have to guess which number came from an instrument at
   this site and which came from a model grid. */
.stacard{border:1px solid var(--line);border-left:3px solid var(--ok);border-radius:10px;
overflow:hidden;margin:6px 0 10px;background:var(--card)}
.stacard .wxhead span:first-child{color:var(--ok);font-weight:600}
.fccard{border-left:3px solid var(--accent)}
.fccard .wxhead span:first-child{color:var(--accent);font-weight:600}

.stabody{display:grid;grid-template-columns:auto 1fr;gap:12px;padding:11px;align-items:center}
/* the circular medallion holding this station's reading */
.orb{width:116px;height:116px;border-radius:50%;background:var(--bg);
border:2px solid var(--ok);display:flex;flex-direction:column;align-items:center;
justify-content:center;gap:0;flex:none}
.orb .deg{font-size:30px;font-weight:700;line-height:1;letter-spacing:-.02em}
.orb .deg sup{font-size:15px;vertical-align:super;font-weight:600}
.orb small{font-size:11px;color:var(--muted);line-height:1}
.orb i{font-style:normal;font-size:10px;color:var(--muted);margin-top:3px;text-align:center;
padding:0 6px}
.stavals{display:grid;grid-template-columns:1fr;gap:0}
.stavals .r{display:flex;justify-content:space-between;gap:10px;align-items:baseline;
padding:4px 0;border-bottom:1px solid var(--line);font-size:13px}
.stavals .r:last-child{border-bottom:0}
.stavals .r span{color:var(--muted)}
.stavals .r b{font-variant-numeric:tabular-nums;white-space:nowrap}
@media (max-width:420px){
  .stabody{grid-template-columns:1fr;justify-items:center}
  .stavals{width:100%}
}

/* "See more" hangs off the station card and carries the same green accent, so the charts it
   holds are unmistakably the station's own measurements rather than anything forecast. */
.seemore{border:1px solid var(--line);border-left:3px solid var(--ok);border-top:0;
border-radius:0 0 10px 10px;margin:-10px 0 10px;background:var(--card)}
.seemore summary{cursor:pointer;padding:8px 11px;font-size:11px;letter-spacing:.07em;
text-transform:uppercase;color:var(--ok);font-weight:600;list-style:none}
.seemore summary::-webkit-details-marker{display:none}
.seemore summary::after{content:' \25be'}
.seemore[open] summary::after{content:' \25b4'}
.seebody{padding:0 11px 12px}
.seebody h3:first-child{margin-top:0}

.stavals .r span small{display:block;font-size:10px;opacity:.85}

/* ---------------------------------------------------------------- responsive
   Tablet and phone. The header height is measured, never assumed, because Khmer wraps
   differently from English and the controls reflow at widths no breakpoint can predict. */
@media (max-width:900px){
  .drawer{width:min(440px,100%)}
}
/* The control row scrolls sideways from tablet width down. Between 760 and 900 it used to do
   neither: too wide to fit, not allowed to wrap, which pushed the whole page sideways. */
@media (max-width:900px){
  /* flex:1 1 auto AND min-width:0 are both required: without them the row refuses to shrink
     below its content and overflow-x never engages, which pushed the page sideways. */
  .controls{flex:1 1 auto;min-width:0;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;
    gap:8px;padding-bottom:2px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .controls::-webkit-scrollbar{display:none}
  .brand{flex:0 1 auto;min-width:0}
  /* Every control keeps its natural width and the row scrolls instead. Without this the theme
     switcher was squeezed to a sliver and "Weather near me" wrapped onto two lines. */
  .controls > *{flex:0 0 auto}
  .controls .btn,.controls button,.controls select,.controls label{white-space:nowrap}
}
@media (max-width:760px){
  #top{padding:7px 12px;gap:5px}
  .brand svg{width:24px;height:24px}
  .brand b{font-size:15px}
  .brand small{display:none}           /* the subtitle is the first thing to go */
  /* one scrollable row rather than three wrapped ones: the map keeps its screen */
  .controls{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;gap:8px;padding-bottom:2px;
    scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .controls::-webkit-scrollbar{display:none}
  .controls label span{display:none}   /* "Group"/"Show" labels; the values speak for themselves */
  select{max-width:150px;padding:6px 8px}
  .placebox input{width:150px}
  .hits{width:min(280px,calc(100vw - 24px))}
  .drawer{width:100%;border-left:0}
  .legend{left:8px;right:8px;bottom:8px;max-width:none}
  .legend .dots{gap:3px 12px}
  /* touch targets: 36px minimum, which is what a thumb needs */
  .controls .btn,.controls button,.theme button,.lang button{min-height:34px}
  .leaflet-touch .leaflet-bar a{width:34px;height:34px;line-height:34px}
}
@media (max-width:560px){
  .wxnow{grid-template-columns:1fr}
  .dgrid{grid-template-columns:1fr}
  .sunmoon{grid-template-columns:1fr 1fr;gap:8px 6px}
  .rorow{gap:4px}
  .ro b{font-size:15px}
  /* the 16-day rows drop the rain-total column before they start wrapping */
  .fd{grid-template-columns:54px 22px 36px 1fr 68px}
  .fd .mm{display:none}
  .fd .cond2{padding-left:54px}
  .fstrip{gap:4px}
  .fh{flex:0 0 52px}
  .orb{width:96px;height:96px}
  .orb .deg{font-size:26px}
  .chartbox{height:180px}
  .near{right:8px;left:8px;width:auto}
}
/* The legend can be minimized on any screen, not just folded away on phones as before: with the
   satellite summary now stacked above it by default, both together took real map space. */
.legend .lgtoggle{display:flex;align-items:center;justify-content:space-between;width:100%;
  border:0;background:none;color:var(--fg);font:inherit;font-size:12px;font-weight:600;
  padding:2px 0;cursor:pointer}
.legend .lgtoggle::after{content:'\25b4'}
.legend.folded .lgtoggle::after{content:'\25be'}
.legend.folded .lgbody{display:none}
.legend .lgbody{margin-top:5px}

.legend .lgtitle{display:none}

/* statement of purpose beside the logo */
.btext{min-width:0}
/* The statement of purpose uses the empty space the header already has, and wraps rather than
   truncating: a sentence cut off at "Open..." tells the reader nothing. */
.brand{flex:1 1 auto}
.brand .objective{display:block;color:var(--muted);font-size:11px;line-height:1.3;
white-space:normal;max-width:none;padding-right:12px}
@media (max-width:900px){.brand .objective{display:none}}

/* About panel */
.about{position:fixed;z-index:1300;top:50%;left:50%;transform:translate(-50%,-50%);
width:min(520px,calc(100vw - 24px));max-height:calc(100vh - 40px);overflow:auto;padding:18px 20px}
.about h3{margin:0 0 8px;font-size:16px}
.about h4{margin:14px 0 5px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;
color:var(--muted)}
.about p{margin:0 0 8px;font-size:13px;line-height:1.5}
.about a{color:var(--accent)}
.about .smallprint{font-size:11px;color:var(--muted);margin-top:12px}
#aboutClose{position:absolute;top:6px;right:10px;border:0;background:none;font-size:22px;
line-height:1;color:var(--muted);cursor:pointer}

.brand .credit{display:block;color:var(--muted);font-size:11px;line-height:1.3}
.brand .credit a{color:var(--accent);text-decoration:none}
.brand .credit a:hover{text-decoration:underline}
@media (max-width:900px){.brand .credit{display:none}}

/* Phone parameter bar, thumb height, in the pattern field apps use: pick the measurement at
   the bottom of the screen and read which one is showing just above it. */
.paramstrip,.parambar{display:none}
@media (max-width:760px){
  .paramstrip{display:block;position:absolute;left:0;right:0;bottom:56px;z-index:1000;
    background:var(--card);border-top:1px solid var(--line);padding:5px 12px;
    font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted)}
  .parambar{display:grid;grid-template-columns:repeat(4,1fr);position:absolute;left:0;right:0;
    bottom:0;z-index:1000;height:56px;background:var(--card);border-top:1px solid var(--line)}
  .parambar button{border:0;background:none;color:var(--muted);display:flex;align-items:center;
    justify-content:center;cursor:pointer;padding:0}
  .parambar button.on{color:#fff;background:var(--accent)}
  /* the map and everything floating over it stop above the bar */
  #map{bottom:84px}
  .legend{bottom:92px}
  .near{bottom:92px;top:auto}
  .drawer{bottom:0;z-index:1200}
  /* the location prompt clears the legend chip, which clears the parameter bar */
  .ask{bottom:148px;left:8px;right:8px}
  .leaflet-control-attribution{margin-bottom:2px}
}

/* how close the forecast has been, at the foot of the forecast card */
.accbox{border-top:1px solid var(--line);padding:8px 11px}
.acchead{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);
margin-bottom:5px}
.accrow{display:grid;grid-template-columns:auto auto 1fr auto;gap:8px;align-items:baseline;
padding:3px 0;font-size:12px}
.accrow .k{color:var(--muted)}
.accrow .v{font-weight:700}
.accrow .v.close{color:#2e9e4f}
.accrow .v.fair{color:#c8860d}
.accrow .v.poor{color:#d6333f}
.accrow .d{color:var(--muted);font-variant-numeric:tabular-nums}
.accrow i{font-style:normal;color:var(--muted);font-size:10px}
.accnote{margin:5px 0 0;font-size:10px;color:var(--muted);line-height:1.35}

/* basemap switcher, bottom-right over the map */
.basemaps{position:absolute;z-index:900;right:10px;bottom:24px;display:flex;
border:1px solid var(--line);border-radius:7px;overflow:hidden;box-shadow:0 2px 8px #0003}
.basemaps button{border:0;background:var(--card);color:var(--muted);font:inherit;font-size:12px;
padding:6px 10px;cursor:pointer;line-height:1;white-space:nowrap}
.basemaps button+button{border-left:1px solid var(--line)}
.basemaps button.on{background:var(--accent);color:#fff}
/* Satellite tiles are dark and detailed: give the badges a halo so they stay readable. */
#map.on-imagery .mk{box-shadow:0 0 0 2px rgba(0,0,0,.55),0 2px 8px rgba(0,0,0,.6)}
#map.on-imagery .leaflet-control-attribution{background:rgba(255,255,255,.85)}
@media (max-width:760px){
  /* top-right on a phone: the bottom strip is already shared by the legend, the location
     prompt and the parameter bar, and zoom owns the top-left */
  .basemaps{right:8px;bottom:auto;top:calc(var(--top) + 8px)}
  .basemaps button{padding:7px 9px;font-size:11px}
}

/* export menu, stacked right above the basemap switcher */
.exportwrap{position:absolute;z-index:900;right:10px;bottom:62px}
.exportbtn{border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--muted);
  font:inherit;font-size:12px;padding:6px 10px;cursor:pointer;box-shadow:0 2px 8px #0003;white-space:nowrap}
.exportbtn:hover{color:var(--fg)}
.exportbtn[disabled]{opacity:.6;cursor:wait}
.exportmenu{position:absolute;right:0;bottom:calc(100% + 4px);display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:7px;background:var(--card);box-shadow:0 2px 8px #0003;overflow:hidden}
.exportmenu button{border:0;background:none;color:var(--fg);font:inherit;font-size:12px;
  padding:8px 12px;cursor:pointer;text-align:left;white-space:nowrap}
.exportmenu button:hover{background:rgba(0,0,0,.06)}
.exportmenu button+button{border-top:1px solid var(--line)}
@media (max-width:760px){.exportwrap{right:8px;bottom:auto;top:calc(var(--top) + 46px)}
  .exportbtn{font-size:11px;padding:7px 9px}
  .exportmenu{bottom:auto;top:calc(100% + 4px)}}

/* the capture-time stamp burned into an exported image (app.js:exportMapImage); never shown outside
   of that brief capture window, so it has no business competing with the live UI the rest of the time */
#exportStamp{position:fixed;left:10px;bottom:6px;z-index:2000;background:rgba(0,0,0,.6);color:#fff;
  font:600 12px/1.3 system-ui,sans-serif;padding:3px 8px;border-radius:5px;pointer-events:none}

/* chance of rain learned from the station's own history */
#rcbox:empty{display:none}
.rccard{margin:10px 0}
.rcchart{display:flex;align-items:stretch;gap:2px;height:78px;margin:10px 0 2px;border-bottom:1px solid var(--line)}
.rcb{position:relative;flex:1;display:flex;align-items:flex-end;min-width:0}
.rcv{width:100%;background:#1f77b4;opacity:.8;border-radius:2px 2px 0 0}
.rcm{position:absolute;left:50%;width:7px;height:7px;margin:0 0 -3px -3px;border-radius:50%;background:#e8943a;border:1px solid var(--bg)}
.rcb small{position:absolute;top:100%;left:0;right:0;text-align:center;font-size:9px;color:var(--muted);margin-top:2px}
.rckey{display:flex;gap:14px;font-size:10px;color:var(--muted);margin-top:16px}
.rckey u{display:inline-block;width:9px;height:9px;margin-right:4px;vertical-align:-1px;text-decoration:none;border-radius:2px}
.rckey u.a{background:#1f77b4}.rckey u.b{background:#e8943a;border-radius:50%}
.rcnote{font-size:11px;color:var(--muted);margin:8px 0 0}
.rcexp{margin-top:10px;font-size:12px}
.rcexp summary{cursor:pointer;color:#1f6fb0;font-weight:600}
:root[data-theme="dark"] .rcexp summary{color:#7cc0f5}
.rcexp ul{margin:8px 0 0;padding-left:18px;color:var(--muted);line-height:1.5}
.rcexp li{margin-bottom:5px}

/* satellite layer toggle button */
#satBtn.on{background:var(--accent);color:#fff;border-color:var(--accent)}
#fullscreenBtn.on{background:var(--accent);color:#fff;border-color:var(--accent)}

/* satellite overlay legend: explains the grey image and the province/district dots in words, since
   a grayscale cloud image has no scale a reader can be expected to already know */
.satlegend{position:static;max-width:230px}
.satleg-row{display:flex;align-items:center;gap:7px;margin:4px 0}
.satleg-row b{margin-left:auto;font-variant-numeric:tabular-nums}
.satleg-sw{display:inline-flex;width:14px;height:14px;flex:0 0 auto}
.satleg-grad{background:linear-gradient(90deg,#1c2733,#fff);border:1px solid var(--line)}
.satleg-note{margin:8px 0 0;color:var(--muted);font-size:11px}

/* satellite coverage markers: a plain small dot where it's clear, a real weather icon (cloud /
   dark-cloud-with-rain) where there's something to see, pulsing to invite a tap since there is no
   image overlay to draw the eye otherwise. */
.satmk{background:none;border:0}
.satdot-plain{display:block;border-radius:50%;border:1.5px solid #fff;
  filter:drop-shadow(0 0 2px rgba(0,0,0,.35))}
/* a non-circular icon glyph (cloud or raindrop shape, see cloudGlyph/dropGlyph in app.js), tinted by
   its own legend colour -- no border-radius/border to clip it, unlike the plain circular .satdot-plain */
.satglyph-plain{display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.35))}
.satblink{display:block;animation:satpulse 1.4s ease-in-out infinite;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.5))}
@keyframes satpulse{
  0%,100%{opacity:1;transform:scale(1)}
  50%{opacity:.6;transform:scale(1.18)}
}
@media (prefers-reduced-motion:reduce){.satblink{animation:none}}

/* every marker on the map grows when zooming in and shrinks zooming out, instead of staying a fixed
   screen-pixel size regardless of how close the view is -- updateZoomScale() in app.js keeps this
   custom property current on every 'zoomend'. Wraps satellite/radar icons one level out from their
   own blink/highlight transform, so the two compose instead of one replacing the other. */
.wx-zoomscale{display:inline-block;transform:scale(var(--wx-zoom-scale,1));transform-origin:center}

/* satellite/radar legend rows: clickable to fly to and highlight that reading's dots on the map */
.satleg-click{cursor:pointer;border-radius:6px;margin:2px -4px;padding:2px 4px}
.satleg-click:hover{background:rgba(0,0,0,.05)}

/* individual "Raining near ..." place names: each one taps to fly/blink to that spot */
.rainloc-click{cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.rainloc-click:hover{color:var(--accent,#1e6fe0)}

/* satellite/radar legend's grid-squares on/off checkbox row */
.griddtoggle{display:flex;align-items:center;gap:8px;cursor:pointer}
.griddtoggle input{margin:0;cursor:pointer}

/* the brief "these ones" pulse highlightMarkers() applies on click: faster and bigger than the
   normal idle blink, applied to the inner glyph span only (never the outer leaflet-positioned
   marker element, whose own transform is its map position) */
.satblink.wx-hl{animation-duration:.4s!important;transform:scale(1.6);transform-origin:center}
.satdot-plain.wx-hl,.satglyph-plain.wx-hl{transform:scale(1.9);transform-origin:center}
@media (prefers-reduced-motion:reduce){.satblink.wx-hl{animation:none}}

/* satellite legend: minimize toggle */
.satleg-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.satleg-fold{border:0;background:none;color:var(--muted);cursor:pointer;width:20px;height:20px;
  flex:0 0 auto;padding:0;font-size:13px;line-height:1}
.satleg-fold::after{content:'\25b4'}
.satlegend.folded .satleg-fold::after{content:'\25be'}
.satlegend.folded .satleg-body{display:none}

/* the pinned place-name label opened by tapping a satellite dot: plain text on the map, not a
   popup bubble, so it reads like a map label rather than something to dismiss */
.satnamelabel{background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:6px;
  padding:3px 8px;font-size:12px;font-weight:600;box-shadow:0 2px 6px #0003}
.satnamelabel::before{border-top-color:var(--card)}

/* a tiny glyph on every marker naming which measurement its number is (thermometer, droplet,
   turbine, raindrop, gauge, sun), so the circle still reads correctly without checking "Show" */
.mkicon{position:absolute;left:-6px;bottom:-6px;width:15px;height:15px;border-radius:50%;
  background:#fff;color:#1c2733;border:1.5px solid #fff;box-shadow:0 1px 2px #0005;
  display:flex;align-items:center;justify-content:center;z-index:1}
:root[data-theme="dark"] .mkicon{background:#1c2733;color:#eef2f6;border-color:#1c2733}
/* "Stations" network picker (app.js:wireStationGroups) */
.stwrap{position:relative;display:inline-block}
.stmenu{position:absolute;top:calc(100% + 4px);left:0;z-index:1200;min-width:150px;padding:6px 4px;
  background:var(--card);border:1px solid var(--line);border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,.18)}
.stmenu-row{display:flex;align-items:center;gap:8px;padding:6px 10px;font-size:13px;cursor:pointer;white-space:nowrap;color:var(--fg)}
.stmenu-row:hover{background:rgba(0,0,0,.06);border-radius:6px}

/* ---------------------------------------------------------------- phone: less, but useful
   Only what a phone visitor actually reaches for stays in the top row (time range, near me,
   satellite, radar, stations, language); CSV, auto-refresh, full screen, theme, cloud-top-temp,
   Education and About move behind the "⋯" button. The bottom bar already picks the measurement,
   so the "Show" dropdown is redundant there too. */
.mobonly{display:none}
@media (max-width:760px){
  html,body{max-width:100vw;overflow-x:hidden}   /* nothing may widen the page: that is what
                                                     made the browser zoom the whole site out */
  .btext{overflow:hidden}
  .brand b{overflow:hidden;text-overflow:ellipsis;font-size:14px}
  #brandMore{display:none}
  .mobonly{display:inline-flex}
  #moreBtn{font-size:18px;line-height:1;padding:4px 12px}
  .controls .mobmore{display:none}
  #top.more .controls{flex-wrap:wrap;overflow:visible}
  #top.more .controls .mobmore{display:inline-flex}
  #moreBtn[aria-expanded="true"]{background:var(--accent);color:#fff}
  /* floating cards: smaller, above the bottom bar, never covering most of the map */
  #bottomPanels{left:8px;bottom:92px;max-width:min(62vw,230px);max-height:34vh}
  .near{max-height:42vh;overflow-y:auto}
  .near .others{display:none}
  .near table td{padding:2px 0}
}

/* Nearest-station card, compact: four donut rings (app.js:showNearest) instead of a table */
.near{width:208px;padding:7px 9px}
.near h4{font-size:13px;line-height:1.25;padding-right:14px}
.near .dist{font-size:10.5px;margin-bottom:5px}
.near .rainchip{padding:3px 8px;font-size:11px;margin:0 0 4px}
.near .rainchip small{display:none}
.nrings{display:flex;justify-content:space-between;margin:4px 0 6px}
.nr{display:flex;flex-direction:column;align-items:center;color:var(--fg)}
.nr small{font-size:9.5px;color:var(--muted);margin-top:-1px}
.near button.go{padding:4px;font-size:11.5px}
@media (max-width:760px){
  .near{width:auto;max-width:300px;left:8px;right:8px;margin:0 auto}
}

/* ---------------------------------------------------------------- header never loses a control
   The control row used to be sized to its full single-line width and then, below 900px, made to
   scroll sideways with its scrollbar hidden: on a narrow window the right-hand buttons (theme,
   language) were simply out of sight. It now takes whatever width is left beside the title and
   WRAPS onto extra rows -- every button stays visible at every window width; the header just grows
   taller (the map's top offset follows it, see app.js:syncTop). */
#top .controls{flex:1 1 360px;min-width:0;flex-wrap:wrap;overflow:visible;justify-content:flex-end;
  row-gap:6px}
#top .controls > *{flex:0 0 auto}
@media (max-width:900px){
  #top .controls{flex-wrap:wrap;overflow:visible}
}
@media (max-width:760px){
  #top .controls{flex-wrap:wrap;overflow:visible;justify-content:flex-start}
  select{max-width:130px}
}
.snaprow{display:flex;align-items:center;gap:6px;font-size:10.5px;color:var(--muted);margin:0 0 6px;cursor:pointer}

/* station card: small donuts only (wx.js:stationCard) */
.stacard-sm .srings{display:flex;flex-wrap:wrap;justify-content:space-around;gap:6px 4px;padding:8px 6px 6px}
.stacard-sm .sr{display:flex;flex-direction:column;align-items:center;color:var(--fg)}
.stacard-sm .sr small{font-size:10px;color:var(--muted);margin-top:-1px}
.stacard-sm .srnote{padding:0 10px 8px;font-size:11px;color:var(--muted);text-align:center}
.stacard-sm .srings{padding:4px 4px 2px;gap:0 2px}
.stacard-sm .sr small{font-size:9px;line-height:1;margin-top:-2px}
.stacard-sm .srnote{padding:0 8px 4px;font-size:10.5px}
/* same ring size and spacing as the nearest-station card (.nrings / .nr) */
.stacard-sm{border:0;background:none;box-shadow:none}
.stacard-sm .srings{display:flex;flex-wrap:nowrap;justify-content:space-between;gap:0;padding:4px 0 6px}
.stacard-sm .sr small{font-size:9.5px;margin-top:-1px}
/* phone: the donuts shrink further (both the station card and the nearest-station card) */
@media (max-width:760px){
  .stacard-sm .sr svg,.nr svg{width:36px;height:36px}
  .stacard-sm .sr small,.nr small{font-size:8.5px}
  .stacard-sm .srings,.nrings{padding-top:2px;padding-bottom:2px}
}

/* station popup: same compact size as the nearest-station card -- the long verdict box and the
   hi/lo line live in the full detail drawer ("View detail"), not in the quick tap popup */
.leaflet-popup-content-wrapper{padding:0}
.leaflet-popup-content{margin:8px 10px;min-width:0;width:auto!important}
.pop h4{font-size:13px;line-height:1.25;padding-right:12px}
.pop > [id^="popv-"],.pop .pophl,.pop .srnote{display:none}
.pop .rainchip{padding:3px 8px;font-size:11px;margin:2px 0 4px}
.pop .rainchip small{display:none}
.pop button{padding:4px;font-size:11.5px}

/* ---- phone header fix: on phones #top is a COLUMN, so the controls' flex-basis (360px, meant as a
   min row width on desktop) became a 360px-tall box -- the big empty gaps between the control
   rows. In a column the controls just take their own height. */
@media (max-width:760px){
  #top .controls{flex:0 0 auto;width:100%;align-content:flex-start;row-gap:6px;justify-content:flex-start}
  #top .controls > *{margin:0}
  #top{padding:6px 10px}
  /* a phone's top row: play, range, speed, then the buttons -- compact, one size */
  #top .controls .btn,#top .controls button,#top .controls select{font-size:12px}
  #top .controls .btn{padding:5px 9px}
}

/* popup title: Khmer glyphs are tall and were touching/clipped at the popup's top edge */
.leaflet-popup-content{margin-top:16px!important}
.pop h4{line-height:1.55;padding-top:2px}
html[lang=km] .pop h4{line-height:1.7}
.pop .badge{margin-top:2px}

/* "Mobile view" preview: a phone-sized window of the real page, centred over a dimmed backdrop */
@media (max-width:760px){.deskonly{display:none!important}}
.mvover{position:fixed;inset:0;z-index:5000;background:rgba(10,16,24,.62);display:flex;
  align-items:center;justify-content:center}
.mvframe{position:relative;width:min(412px,96vw);height:min(860px,94vh);background:#111;
  border-radius:26px;padding:10px;box-shadow:0 12px 50px rgba(0,0,0,.55)}
.mvframe iframe{width:100%;height:100%;border:0;border-radius:18px;background:var(--bg)}
.mvclose{position:absolute;top:-14px;right:-14px;width:32px;height:32px;border-radius:50%;
  border:0;background:#fff;color:#111;font-size:20px;line-height:1;cursor:pointer;
  box-shadow:0 2px 8px rgba(0,0,0,.4)}
.mvover{flex-direction:column;gap:10px}
.mvbar{display:flex;gap:6px;align-items:center;background:rgba(255,255,255,.12);padding:5px;border-radius:10px;position:relative;padding-right:46px}
.mvsz{border:0;background:transparent;color:#e8eef5;font:inherit;font-size:13px;padding:6px 14px;border-radius:7px;cursor:pointer}
.mvsz small{opacity:.6;font-size:10px;margin-left:3px}
.mvsz.on{background:var(--accent);color:#fff}
.mvbar .mvclose{top:50%;right:8px;transform:translateY(-50%);position:absolute}
.mvframe{padding:0;border-radius:6px;background:#111}
.mvframe.mvround{padding:10px;border-radius:26px}
.mvframe.mvround iframe{border-radius:18px}
.mvframe iframe{border-radius:4px}

/* ---- phone: hide the menus (chevron button, or double-tap the title bar) for a full-screen map */
#uiToggle{display:none}
@media (max-width:760px){
  #uiToggle{display:flex;position:fixed;z-index:1300;right:8px;top:6px;width:30px;height:30px;
    align-items:center;justify-content:center;border:1px solid var(--line);border-radius:50%;
    background:var(--card);color:var(--fg);font-size:17px;line-height:1;padding:0;
    box-shadow:0 1px 5px rgba(0,0,0,.25)}
  .brand{padding-right:38px}
  body.ui-hidden #top .controls,body.ui-hidden #top .brand small{display:none}
  body.ui-hidden .parambar,body.ui-hidden .paramstrip{display:none}
  body.ui-hidden #map{bottom:0}
  body.ui-hidden #bottomPanels{bottom:34px}
  body.ui-hidden .near{bottom:34px}
}

/* ---- thunderstorm layer: a flashing bolt per cell (app.js:stormBoltHtml) */
.stormmk{background:none;border:0}
.stormbolt{display:flex;align-items:center;justify-content:center;width:100%;height:100%;
  filter:drop-shadow(0 0 4px var(--c));animation:stormflash 3.2s infinite}
.stormbolt.lv3{animation-duration:2.1s}
@keyframes stormflash{
  0%,62%,100%{opacity:.55;filter:drop-shadow(0 0 2px var(--c))}
  64%{opacity:1;filter:drop-shadow(0 0 14px #fff) drop-shadow(0 0 8px var(--c))}
  67%{opacity:.4}
  70%{opacity:1;filter:drop-shadow(0 0 18px #fff) drop-shadow(0 0 10px var(--c))}
  76%{opacity:.6}}
#stormBtn b{background:#d6333f;color:#fff;border-radius:9px;padding:0 6px;font-size:11px;margin-left:2px}
#stormBtn.alert{border-color:#f0b429;animation:stormpulse 2s infinite}
@keyframes stormpulse{0%{box-shadow:0 0 0 0 rgba(240,180,41,.7)}70%{box-shadow:0 0 0 7px rgba(240,180,41,0)}100%{box-shadow:0 0 0 0 rgba(240,180,41,0)}}
.stormdot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:7px}
.stormview{margin:6px 0 4px;width:100%}
@media (prefers-reduced-motion:reduce){.stormbolt,#stormBtn.alert{animation:none}}

/* imagery picker (app.js:wireImagery) */
.stmenu-note{padding:4px 10px 6px;font-size:10.5px;color:var(--muted);max-width:220px;white-space:normal;line-height:1.35}
.imgquick{font-size:12px;color:var(--muted)}
.imgquick a{color:var(--accent);text-decoration:none}
.wx-chimg{mix-blend-mode:normal;image-rendering:auto}

/* thunderstorm bolts: drawn in "reverse RGB" -- mix-blend-mode:difference inverts whatever is
   underneath, so a white bolt is always the colour opposite to the map/cloud behind it and can
   never disappear into it. The severity colour moves to a small dot beside the bolt. */
.stormbolt{mix-blend-mode:difference;filter:none;position:relative}
.stormbolt svg path{fill:#fff;stroke:#fff}
.stormbolt::after{content:"";position:absolute;right:-2px;bottom:-2px;width:7px;height:7px;border-radius:50%;
  background:var(--c);border:1px solid #fff;mix-blend-mode:normal}
@keyframes stormflash{
  0%,62%,100%{opacity:.6}
  64%{opacity:1;transform:scale(1.18)}
  67%{opacity:.35;transform:scale(1)}
  70%{opacity:1;transform:scale(1.28)}
  76%{opacity:.65;transform:scale(1)}}

/* ---- lightning look (photo reference: white-hot jagged core, blue glow, forks) ---------------
   mix-blend-mode sits on the MARKER element itself (.stormmk): a blend inside a Leaflet marker
   only sees the marker's own empty box (the marker has a transform, so it is its own stacking
   context), which is why the earlier version never actually inverted against the map. */
.stormmk{mix-blend-mode:difference;overflow:visible}
.stormbolt{position:relative;display:block;width:100%;height:100%;filter:none;mix-blend-mode:normal;
  animation:stormflash 3.2s infinite}
.stormbolt.lv3{animation-duration:2.1s}
.stormbolt::after{display:none}
.stormbolt svg{overflow:visible;position:absolute;left:50%;bottom:0;transform:translateX(-50%)}
.stormbolt svg path{fill:none;stroke-linecap:round;stroke-linejoin:round}
.stormbolt .lb-glow{stroke:#4f7bff;stroke-width:9;opacity:.55;filter:blur(3px)}
.stormbolt .lb-mid{stroke:#9db8ff;stroke-width:3.6}
.stormbolt .lb-core{stroke:#fff;stroke-width:1.7}
.stormbolt::before{content:"";position:absolute;left:50%;top:5%;width:150%;height:90%;
  transform:translateX(-50%);border-radius:50%;opacity:0;
  background:radial-gradient(closest-side,rgba(120,150,255,.55),rgba(80,110,255,.18) 60%,transparent);
  animation:stormglow 3.2s infinite;animation-delay:inherit}
.stormbolt.lv3::before{animation-duration:2.1s}
@keyframes stormflash{
  0%,60%,100%{opacity:.18}
  62%{opacity:1}
  65%{opacity:.25}
  68%{opacity:1}
  74%{opacity:.5}
  84%{opacity:.2}}
@keyframes stormglow{
  0%,60%,100%{opacity:0}
  62%,68%{opacity:1}
  80%{opacity:.25}}
@media (prefers-reduced-motion:reduce){.stormbolt,.stormbolt::before{animation:none;opacity:.8}}

.imgrow{align-items:flex-start}
.imgrow input{margin-top:3px}
.imgrow span{display:flex;flex-direction:column;white-space:normal;max-width:230px;line-height:1.3}
.imgrow small{font-size:10.5px;color:var(--muted);font-weight:400}
.stmenu .stmenu-row{white-space:normal}

.cttstats{display:flex;gap:6px;margin:2px 0 6px}
.cttstats div{flex:1;background:rgba(0,0,0,.05);border-radius:6px;padding:4px 6px;text-align:center}
.cttstats small{display:block;font-size:9.5px;color:var(--muted)}
.cttstats b{font-size:15px}
.cttcold{display:flex;justify-content:space-between;padding:3px 0;font-size:12px;color:var(--accent);cursor:pointer}

/* ---- "JMA predict" ensemble card (app.js:renderJma) */
.jmapanel{right:10px;top:calc(var(--top) + 14px);width:344px;max-height:calc(100vh - var(--top) - 100px);
  overflow-y:auto;z-index:950;padding:10px 12px}
.jmahead{display:flex;justify-content:space-between;align-items:center;font-size:14px}
.jmax{border:0;background:none;font-size:20px;line-height:1;color:var(--muted);cursor:pointer;padding:0 2px}
.jmaplace{font-size:12.5px;margin:2px 0}
.jmabtns{color:var(--muted);font-size:11px;margin-left:6px}
.jmabtns .linklike{font-size:11px}
.jmahint{font-size:10.5px;color:var(--muted);margin-bottom:6px}
.jmabig{display:flex;gap:12px;align-items:center;margin:6px 0 8px}
.jmapct{font-size:34px;font-weight:700;line-height:1}
.jmabig .muted{font-size:11px}
.jmachart{display:block;color:var(--fg);margin-top:2px}
.jmakey{display:flex;flex-wrap:wrap;gap:3px 10px;font-size:10px;color:var(--muted);margin:4px 0 8px}
.jmakey i{display:inline-block;width:10px;height:6px;border-radius:2px;margin-right:4px}
.jmatab{width:100%;font-size:11.5px}
.jmatab th{font-size:10.5px;font-weight:600}
.jmatab small{color:var(--muted)}
.jmaag{letter-spacing:-1px;font-size:9px;white-space:nowrap}
.jmaag.high{color:#2e9e4f}.jmaag.medium{color:#c8860d}.jmaag.low{color:#d6333f}
.jmahow{margin-top:8px;font-size:11.5px}
.jmahow summary{cursor:pointer;color:var(--accent)}
.jmahow ol{margin:6px 0 0;padding-left:18px;line-height:1.45}
.jmasrc{font-size:10px;margin:8px 0 0}
@media (max-width:760px){
  .jmapanel{left:8px;right:8px;width:auto;top:auto;bottom:92px;max-height:62vh}
}

.jmapin-b{background:#1e6fe0;color:#fff;border:2px solid #fff;border-radius:8px;text-align:center;font:700 13px/1.1 system-ui;padding:2px 4px;box-shadow:0 1px 4px rgba(0,0,0,.4)}
.jmapin-b small{display:block;font-size:9px;font-weight:600;opacity:.85}
.jmapop{min-width:170px;line-height:1.35}


/* ---- every screen: hide the menus and panels for a full map (button top right, key H, double-click the title bar) */
#uiToggle{display:flex;position:fixed;z-index:1300;right:8px;top:7px;width:30px;height:30px;align-items:center;justify-content:center;
  border:1px solid var(--line);border-radius:50%;background:var(--card);color:var(--fg);font-size:17px;line-height:1;padding:0;
  box-shadow:0 1px 5px rgba(0,0,0,.25);cursor:pointer}
#top{padding-right:46px}
body.ui-hidden #top .controls,body.ui-hidden #top .brand small,body.ui-hidden #brandMore{display:none}
body.ui-hidden #bottomPanels,body.ui-hidden .placebox,body.ui-hidden #compass,body.ui-hidden #status,body.ui-hidden #basemaps,
body.ui-hidden .parambar,body.ui-hidden .paramstrip{display:none !important}
body.ui-hidden #map{bottom:0}
/* the legends can be folded on their own */
#bottomPanels .foldbtn{align-self:flex-start;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--fg);
  font:600 12px/1 system-ui,sans-serif;padding:5px 9px;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.2)}
#bottomPanels.folded > :not(.foldbtn){display:none !important}

/* ---- header controls grouped by topic (2026-10-17): each topic a small titled card, its buttons in an even grid, so
   everything about one subject sits in one place. From 900px down the cards dissolve (display:contents) and the buttons
   flow exactly as before, in the one sideways-scrolling row and the phone's "..." menu. */
.controls{flex-direction:column;align-items:flex-end;gap:6px}
.ctlgrid{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:stretch;gap:8px}
.ctlgroup{display:flex;flex-direction:column;gap:5px;border:1px solid var(--line);border-radius:10px;padding:5px 7px 7px;
  background:var(--card)}
.ctlhead{font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);line-height:1.2;
  padding-left:2px}
.ctlbtns{display:grid;grid-template-columns:repeat(2,minmax(0,auto));gap:5px;align-items:center}
.ctlgroup[data-group="map"] .ctlbtns{grid-template-columns:repeat(3,minmax(0,auto))}
.ctlbtns > .btn,.ctlbtns > .stwrap > .btn{width:100%;justify-content:center;text-align:center}
.ctlbtns > .stwrap{display:block}
.ctlbtns > label{justify-content:space-between}
@media (max-width:900px){
  .controls{flex-direction:row;align-items:center}
  .ctlgrid,.ctlgroup,.ctlbtns{display:contents}
  .ctlhead{display:none}
}

/* ---- header, 2026-10-17: the title on top with the update intervals under it, the grouped menu below as one full-width
   row of cards (desktop); the replay bar sits at the right of the title row. */
.srcinfo{display:block;color:var(--muted);font-size:11px;line-height:1.35;margin-top:1px;white-space:normal}
@media (min-width:901px){
  #top{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"brand replay" "ctl ctl";
    align-items:center;gap:6px 12px;padding:6px 14px}
  #top > .brand{grid-area:brand}
  #top .controls{display:contents}
  #replayBar{grid-area:replay;justify-self:end}
  .ctlgrid{grid-area:ctl;justify-content:flex-start}
  .ctlbtns{grid-template-columns:repeat(2,minmax(0,auto))}
  .ctlgroup[data-group="sat"] .ctlbtns,.ctlgroup[data-group="map"] .ctlbtns{grid-template-columns:repeat(3,minmax(0,auto))}
  .ctlbtns .btn{font-size:12.5px;padding:4px 9px}
}
@media (max-width:900px){
  .ctlbtns > .btn,.ctlbtns > .stwrap > .btn{width:auto}
}
@media (max-width:760px){
  .srcinfo{display:none}
}

/* ---- header, exactly three rows on desktop: the title row, then one row of topic cards whose buttons fill two rows
   column by column (a card never grows a third row; the card row scrolls sideways on a narrow window instead of
   wrapping). The fold button folds the cards away and leaves the title row. */
@media (min-width:901px){
  #top{grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:"brand replay fold" "ctl ctl ctl"}
  #menuFold{grid-area:fold;align-self:center;min-width:34px;padding:3px 9px;font-size:13px}
  .ctlgrid{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;padding-bottom:2px}
  .ctlgroup{flex:0 0 auto}
  .ctlbtns,.ctlgroup[data-group="sat"] .ctlbtns,.ctlgroup[data-group="map"] .ctlbtns{grid-auto-flow:column;
    grid-template-rows:repeat(2,auto);grid-template-columns:none;grid-auto-columns:minmax(0,auto)}
  body.menu-folded .ctlgrid{display:none}
}
@media (max-width:900px){
  #menuFold{display:none}
}

/* header fixes: a menu wrapper hidden on phones stays hidden (the card rule above must not win over .mobmore); room for
   the round full-map toggle in the corner; a slightly tighter card row on narrower desktops so it fits without scrolling */
@media (max-width:900px){
  .ctlbtns > .stwrap{display:inline-block}
}
/* the satellite layer drawn one picture pixel per 0.02-degree (~2 km) cell (app/sat2km.py): crisp squares, not a blur */
.leaflet-image-layer.px{image-rendering:crisp-edges;image-rendering:pixelated}
/* the More card's square controls (colour depth, names on top, Cambodia only): one column of the two-row card */
.gridopts{display:flex;flex-direction:column;justify-content:center;gap:4px;grid-row:span 2;font-size:12px}
.gridop{display:flex;align-items:center;gap:6px;white-space:nowrap;cursor:pointer}
.gridop input[type=range]{width:92px;margin:0;accent-color:var(--accent);cursor:pointer}
.gridop b{min-width:3.4ch;font-weight:600;font-variant-numeric:tabular-nums;color:var(--muted)}
.gridchk{display:flex;gap:10px;white-space:nowrap}
.gridchk label{display:inline-flex;align-items:center;gap:4px;cursor:pointer}
.gridchk input{margin:0}
/* the names drawn above the squares (app.js syncNames) */
.namelbl{pointer-events:none}
.namelbl span{position:absolute;left:0;top:0;transform:translate(-50%,-50%);white-space:nowrap;color:#16202a;
  font-family:"Noto Sans Khmer",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  text-shadow:0 0 2px #fff,0 0 3px #fff,0 0 5px #fff,0 0 7px rgba(255,255,255,.9)}
.namelbl .nm-prov{font-size:12.5px;font-weight:700;letter-spacing:.02em}
.namelbl .nm-dist{font-size:10.5px;font-weight:600;color:#33404e}
#map.on-imagery .namelbl span{color:#fff;text-shadow:0 0 2px #000,0 0 3px #000,0 0 6px rgba(0,0,0,.85)}
/* one 2 km square: the pointer's reading and the tap's card */
.leaflet-tooltip.cellhover{font-size:12px;line-height:1.35;padding:4px 7px;white-space:nowrap}
.cellpopup{font-size:13px;line-height:1.4;width:300px;max-width:calc(100vw - 70px)}   /* the popup content is width:auto here */
.cellpop{border-collapse:collapse;font-size:12.5px;margin:6px 0 4px}
.cellpop th{text-align:left;font-weight:600;color:var(--muted);padding:2px 10px 2px 0;vertical-align:top;white-space:nowrap}
.cellpop td{padding:2px 0}
.cellfoot{display:block;font-size:11px;line-height:1.35}
.cellfb{margin-top:8px;padding-top:7px;border-top:1px solid var(--line);font-size:12.5px}
.cellfb-b{display:flex;gap:6px;margin:5px 0 3px}
.cellfb-b .btn{padding:3px 10px;font-size:12.5px}
.cellfb-b .btn.on{background:var(--accent, #2a78d6);color:#fff;border-color:transparent}
.cellfb-msg{display:block;font-size:11px;line-height:1.35}
/* MOWRAM radar / satellite legends: the colour key as one bar */
.mowbar{display:flex;height:10px;border-radius:3px;overflow:hidden;margin:6px 0 2px;border:1px solid var(--line)}
.mowbar i{flex:1}
.mowsel{font:inherit;font-size:12px;max-width:100%}
.mowlegimg{display:block;max-width:100%;margin:6px 0 2px;border-radius:3px}
/* the satellite panel's GK2A / JMA switch */
.satsrc{flex-wrap:wrap}
.satsrc-b{font:inherit;font-size:12px;padding:2px 10px;border:1px solid var(--line);border-radius:12px;background:transparent;color:var(--fg);cursor:pointer}
.satsrc-b.on{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:700}
@media (max-width:760px){
  .controls .ctlbtns > .stwrap.mobmore{display:none}
  #top.more .controls .ctlbtns > .stwrap.mobmore{display:inline-block}
}
@media (min-width:901px){
  #top{padding-right:58px}
}
@media (min-width:901px) and (max-width:1500px){
  .ctlbtns > label > span{display:none}
  .ctlbtns .btn{padding:4px 7px;font-size:12px}
  .ctlgroup{padding:4px 6px 6px}
}
@media (min-width:901px) and (max-width:1500px){
  .ctlbtns select{max-width:128px}
  .ctlgrid{gap:6px}
}

/* a storm heading into Cambodia: one line over the map, blinking faster the sooner it reaches the border; a tap opens the
   storm view (18 October 2026) */
/* a compact card at the top right of the map, one line per storm (it covered the middle of the map and the square popups) */
.stormline{position:fixed;right:12px;top:calc(var(--top,60px) + 10px);z-index:890;width:min(340px,calc(100vw - 24px));
  display:block;text-align:left;padding:7px 11px;border:0;border-radius:10px;
  background:#d6333f;color:#fff;font:inherit;font-weight:700;font-size:12.5px;line-height:1.45;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.3);
  animation:stormblink var(--blink,2.2s) ease-in-out infinite}
.stormline .sl{display:block}
.stormline .sl + .sl{margin-top:4px;padding-top:4px;border-top:1px solid rgba(255,255,255,.35)}
.stormline small{display:block;margin-top:3px;font-weight:500;opacity:.92}
.stormline.away{display:none!important}           /* while a station or square popup, or the station drawer, is open */
@media (max-width:640px){.stormline{left:8px;right:8px;width:auto;font-size:12px}}
@keyframes stormblink{0%,100%{opacity:1}50%{opacity:.4}}
/* under 2 hours: the alarm (the same as the prediction page's) */
.stormpop{position:fixed;left:50%;top:16%;transform:translateX(-50%);z-index:5000;background:#fff;color:#4a0d0d;border:3px solid #d6333f;
  border-radius:14px;box-shadow:0 12px 40px rgba(0,0,0,.4);padding:16px 18px;width:min(92vw,460px);font-size:15px;line-height:1.5}
.stormpop b.t{display:block;color:#d6333f;font-size:18px;margin-bottom:6px}
.stormpop button{margin-top:12px;font:inherit;font-weight:700;padding:6px 18px;border-radius:8px;border:0;background:#d6333f;color:#fff;cursor:pointer}
.stormpop button.ok{background:#6b7280}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .stormpop{background:#2a1212;color:#ffe2e2}}
:root[data-theme="dark"] .stormpop{background:#2a1212;color:#ffe2e2}
@media (prefers-reduced-motion:reduce){.stormline{animation:none}}
/* the layer or view that is on, in the header: bold on blue, so the chosen one is plain to see (18 October 2026) */
.btn.on,.ctlbtns .btn.on,.ctlbtns > .stwrap > .btn.on{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:700}
.btn.on:hover{filter:brightness(1.08)}
#stormBtn.on b{background:#fff;color:#d6333f}

/* the summary legend: every class of the picture in its own colours, its 2 km squares; a tap blinks them (18 October 2026) */
/* each class on two lines: its colours on the left, the name over its squares (a long count never squeezes the name) */
.lsum .lsum-row{display:grid;grid-template-columns:42px minmax(0,1fr);column-gap:8px;row-gap:0;align-items:center;cursor:pointer;
  border-radius:6px;padding:3px 4px;margin:2px -4px;line-height:1.3}
.lsum .lsum-row:hover{background:rgba(127,127,127,.12)}
.lsum .lsum-row.on{background:rgba(255,0,255,.16);outline:1px solid rgba(255,0,255,.5)}
.lsum .lsum-row > .lsw{grid-row:span 2;justify-self:start}
.lsum .lsum-row > .lsum-l{grid-column:2;overflow-wrap:anywhere}
.lsum .lsum-row > b{grid-column:2;font-size:11.5px;font-weight:600;opacity:.8;overflow-wrap:anywhere}
.lsw{display:inline-flex;flex:none;height:12px;border-radius:3px;overflow:hidden;border:1px solid rgba(0,0,0,.28)}
.lsw i{display:block;width:5px;height:12px}
.lsw.lsw-none{width:16px;background:repeating-linear-gradient(45deg,rgba(127,127,127,.55) 0 2px,transparent 2px 4px)}
.leaflet-image-layer.wx-blink{animation:wxBlink 1s step-end infinite}
@keyframes wxBlink{50%{opacity:0}}

/* the phone page (/mobile, 18 October 2026): the map fills the screen; only what is useful around the visitor, in one bottom
   sheet that a tap or a swipe up opens */
.m-mode #top,.m-mode #bottomPanels,.m-mode .placebox,.m-mode .basemaps,.m-mode #uiToggle,.m-mode #compass,.m-mode #status,
.m-mode #nearClose,.m-mode #paramstrip{display:none!important}
.m-mode .msheet .parambar{position:static;display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);border-radius:10px;margin:0 0 10px;box-shadow:none}
.m-mode #map{top:0;bottom:0}
.msheet{position:fixed;left:0;right:0;bottom:0;z-index:1100;display:flex;flex-direction:column;max-height:78vh;background:var(--card);
  color:var(--fg);border-radius:14px 14px 0 0;box-shadow:0 -4px 18px #0004;transform:translateY(calc(100% - 70px));transition:transform .25s ease}
.msheet.open{transform:translateY(0)}
.mhandle{flex:none;padding:6px 14px 8px;cursor:pointer;touch-action:none;user-select:none}
.mgrip{display:block;width:44px;height:5px;border-radius:3px;background:var(--line);margin:0 auto 5px}
.mtitle{display:block;font-size:13px;line-height:1.2}
.mline{display:block;font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mbody{overflow:auto;padding:2px 12px 18px;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
.m-mode .msheet #near{position:static;width:auto;box-shadow:none;margin:0 0 10px}
.mcard{border:1px solid var(--line);border-radius:10px;padding:8px 10px;margin:0 0 10px;font-size:13px}
.mcard p{margin:4px 0}
.mbar{display:flex;height:8px;border-radius:3px;overflow:hidden;margin-top:6px}
.mbar i{flex:1}
.mbarl{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}
.mchips{display:flex;gap:8px;margin:0 0 10px}
.mchips button{flex:1;font:inherit;font-size:12.5px;padding:7px 4px;border-radius:8px;border:1px solid var(--line);background:var(--card);color:var(--fg)}
.mchips button.on{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
.mchk{display:flex;gap:6px;align-items:center;font-size:13px;margin:0 0 10px}
.mlinks{display:flex;flex-wrap:wrap;gap:8px}
.mlinks .btn{flex:1 1 45%;text-align:center}
.m-mode .stormline{top:8px}
