diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index 95e4110..2737e10 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -177,21 +177,33 @@
.popup-metric span { display: block; color: var(--muted); font-size: .62rem; }
.popup-metric strong { display: block; margin-top: 2px; font-size: .86rem; }
.popup-time { margin-top: 9px; color: var(--muted); font-size: .64rem; }
+ #legend-toggle {
+ display: none; pointer-events: auto; background: rgba(255,255,255,.93); border: 1px solid rgba(207,224,218,.9);
+ border-radius: 11px; padding: 8px 12px; font-size: .72rem; font-weight: 800; color: var(--ink);
+ box-shadow: 0 7px 20px rgba(22,58,68,.12);
+ }
@media (max-width: 900px) {
.stats { grid-template-columns: repeat(2, 1fr); }
.workspace { grid-template-columns: 1fr; }
- .side-card { max-height: 400px; }
+ /* No fixed cap: the sensors expander must stay reachable below the RID list */
+ .side-card { max-height: 70vh; }
+ #river-flow { min-height: 160px; }
}
@media (max-width: 560px) {
.shell { padding: 14px; }
header { align-items: flex-start; }
- .subtitle, .live-pill { display: none; }
+ .subtitle { display: none; }
+ .live-pill { padding: 8px; }
+ #live-pill-text { display: none; } /* dot stays: live vs replay/simulation signal */
+ #replay-2024 { white-space: nowrap; padding: 9px 11px; font-size: .72rem; }
.stats { gap: 8px; }
.stat { min-height: 96px; padding: 14px; }
.stat-value { font-size: 1.25rem; }
#station-map { height: 540px; }
.map-overlay { left: 46px; }
+ #legend-toggle { display: inline-flex; }
.legend { display: none; }
+ .legend.open { display: block; max-height: 42vh; overflow-y: auto; }
}
@@ -224,7 +236,9 @@
Station flow mapRiver width, colour & dash speed follow live discharge
-
+
+
+
Flow status
Low < 25 m³/s
Moderate 25–100
@@ -241,6 +255,7 @@
Very heavy 90–150
Extreme > 150
+
Loading river conditions…
Map data could not be loaded.
@@ -252,7 +267,7 @@
- Additional basin stations ▸
Loading ThaiWater/HII stations…
+ Additional basin stations ▸
Loading ThaiWater/HII stations…
@@ -279,13 +294,13 @@
-
+ Click any station on the map or in the list to see its history
@@ -517,6 +532,7 @@
function setLiveIndicator(mode, label) {
const pill = $('live-pill');
$('live-pill-text').textContent = label || 'LIVE DATA';
+ pill.title = label || 'LIVE DATA'; // mobile shows the dot only
pill.classList.toggle('demo', mode !== 'live');
}
@@ -556,6 +572,7 @@
const reqId = state.historyRequestId;
$('history-title').textContent = `${stationCode} · station history`;
$('history-status').textContent = 'Loading historical measurements…';
+ $('history-placeholder').style.display = 'none';
try {
// Hand-edited dates win; dropdown-driven loads use the precise
// trailing-hours window (the pickers just mirror it as dates).
@@ -651,6 +668,7 @@
state.sensorsOpen = open;
$('thaiwater-sensors').style.display = open ? '' : 'none';
$('sensors-arrow').textContent = open ? '▾' : '▸';
+ $('sensors-head').setAttribute('aria-expanded', String(open));
}
// Filters both station lists by any text on the row (code, names, river —
@@ -726,7 +744,7 @@
const row = document.createElement('button');
row.type = 'button'; row.className = 'station-row';
const codeClass = sensor.station_code.length > 5 ? 'station-code long' : 'station-code';
- row.innerHTML = `${escapeHtml(sensor.station_code)}${escapeHtml(sensor.station_name)}${escapeHtml(sensor.river_name || 'Ping basin')} · ThaiWater${percent == null ? '—' : percent.toFixed(0) + '%'}bank level`;
+ row.innerHTML = `${escapeHtml(sensor.station_code)}${escapeHtml(sensor.station_name)}${escapeHtml(sensor.river_name || 'Ping basin')} · ThaiWater${percent == null ? '—' : percent.toFixed(0) + '%'}of bank height`;
row.addEventListener('click', () => { state.map.flyTo(marker.getLatLng(), Math.max(state.map.getZoom(), 11), { duration: .8 }); marker.openPopup(); });
container.appendChild(row);
});
@@ -745,9 +763,7 @@
const p1 = readings.get('P.1');
const p1Flow = p1?.discharge == null ? null : Number(p1.discharge);
$('total-flow').textContent = p1Flow == null ? '—' : p1Flow.toLocaleString(undefined, { maximumFractionDigits: 1 });
- $('cm-flow-note').textContent = p1?.discharge_percent != null
- ? `P.1 Nawarat Bridge · ${Number(p1.discharge_percent).toFixed(0)}% of channel capacity · m³/s`
- : 'P.1 Nawarat Bridge · m³/s';
+ $('cm-flow-note').textContent = 'P.1 Nawarat Bridge · m³/s'; // capacity % lives in the next tile
const stressed = current
.filter((m) => m.discharge_percent != null)
.map((m) => ({ code: m.station_code, percent: Number(m.discharge_percent) }));
@@ -990,9 +1006,11 @@
}
});
// stat tiles follow the replay (restored by loadDashboard on finish)
- $('total-flow').textContent = totalFlow.toLocaleString(undefined, { maximumFractionDigits: 1 });
- $('peak-flow').textContent = peakFlow ? peakFlow.value.toFixed(1) : '—';
- $('peak-station').textContent = peakFlow ? `${peakFlow.code} · m³/s · 2024 replay` : 'No discharge reported';
+ const p1ReplayQ = data.stations['P.1'] ? data.stations['P.1'].discharge[frame] : null;
+ $('total-flow').textContent = p1ReplayQ == null ? '—' : Number(p1ReplayQ).toLocaleString(undefined, { maximumFractionDigits: 1 });
+ $('cm-flow-note').textContent = 'P.1 Nawarat Bridge · m³/s · 2024 replay';
+ $('peak-flow').textContent = peakFlow ? peakFlow.value.toFixed(0) : '—';
+ $('peak-station').textContent = peakFlow ? `${peakFlow.code} · strongest flow · m³/s · 2024 replay` : 'No discharge reported';
restyleRiver(readings);
const p1Level = data.stations['P.1'] ? data.stations['P.1'].level[frame] : null;
if (p1Level != null) state.p1Now = Number(p1Level);
@@ -1209,6 +1227,7 @@
$('forecast-expand').addEventListener('click', () => {
state.forecastExpanded = !state.forecastExpanded;
$('forecast-grid').style.display = state.forecastExpanded ? 'grid' : 'none';
+ $('forecast-expand').setAttribute('aria-expanded', String(state.forecastExpanded));
const count = $('forecast-grid').children.length;
$('forecast-expand').textContent = state.forecastExpanded
? 'Hide station forecasts ▴'
@@ -1226,6 +1245,7 @@
});
$('replay-2024').addEventListener('click', replayFlood2024);
$('history-range').addEventListener('change', () => {
+ if ($('history-range').value === 'custom') return;
state.useDates = false;
const hours = Number($('history-range').value);
if (hours >= 876000) {
@@ -1238,13 +1258,21 @@
$('history-start').value = isoLocal(new Date(now.getTime() - hours * 3600 * 1000));
}
if (state.selectedStation) loadHistory(state.selectedStation);
+ else $('history-status').textContent = 'Select a station first — click one on the map or in the list';
});
['history-start', 'history-end'].forEach((id) => $(id).addEventListener('change', () => {
state.useDates = true;
+ $('history-range').value = 'custom'; // dropdown no longer describes the window
if (state.selectedStation) loadHistory(state.selectedStation);
+ else $('history-status').textContent = 'Select a station first — click one on the map or in the list';
}));
+ $('legend-toggle').addEventListener('click', () => {
+ const open = $('map-legend').classList.toggle('open');
+ $('legend-toggle').setAttribute('aria-expanded', String(open));
+ });
loadDashboard();
- window.setInterval(loadDashboard, 5 * 60 * 1000);
+ // Skip auto-refresh while the 2024 replay is running (it would clobber replay state)
+ window.setInterval(() => { if (!state.replayTimer) loadDashboard(); }, 5 * 60 * 1000);
})();