feat(dashboard): light/dark theme
Sun/moon button next to the language switch. Follows the OS preference until the user picks one (persisted in localStorage, tracks OS changes only while unpinned). Every colour that was a literal white / grey is now a token with a dark counterpart; flood-verdict, live-pill and lang-toggle states got their own bg/ink/border tokens. OSM tiles are inverted with a hue rotate so roads and labels stay legible while the river network, markers and rain dots (SVG, unfiltered) keep their data colours. Chart.js reads grid/tick/legend colours from the tokens and the open chart is rebuilt on toggle. Leaflet popups and controls follow the theme.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 522 KiB |
+116
-27
@@ -33,6 +33,57 @@
|
||||
--red: #cc4b37;
|
||||
--border: #dce7e3;
|
||||
--shadow: 0 16px 40px rgba(23, 57, 67, .10);
|
||||
--surface: #ffffff; /* buttons, inputs, overlays */
|
||||
--surface-2: #f1f7f5; /* hover rows, popup metrics */
|
||||
--surface-3: #f7fbfa; /* outlook box */
|
||||
--overlay: rgba(255,255,255,.93);
|
||||
--overlay-border: rgba(207,224,218,.9);
|
||||
--map-bg: #dcebea;
|
||||
--glow: rgba(56, 180, 213, .12);
|
||||
--map-filter: none;
|
||||
--chart-grid: rgba(19,43,53,.08);
|
||||
--mint-ink: #146644;
|
||||
--mint-border: #b6dfc9;
|
||||
--ok-bg: #e2f3ea; --ok-ink: #0c5138; --ok-border: #bfe3d2;
|
||||
--watch-bg: #fdf1dc; --watch-ink: #6b4a05; --watch-border: #f0d9a8;
|
||||
--danger-bg: #fbe3de; --danger-ink: #7c1d10; --danger-border: #f2c0b6;
|
||||
--demo-bg: #fdeeda; --demo-ink: #9a6200;
|
||||
color-scheme: light;
|
||||
}
|
||||
/* Dark theme: same hues, inverted lightness. Flow/rain/marker colours
|
||||
on the map are data encodings and stay identical in both themes. */
|
||||
[data-theme="dark"] {
|
||||
--ink: #e6eef1;
|
||||
--muted: #97a9b0;
|
||||
--paper: #0f1a1f;
|
||||
--card: #16252c;
|
||||
--river: #3fb3d8;
|
||||
--river-light: #5ccbe8;
|
||||
--mint: #143b2c;
|
||||
--green: #3fb07f;
|
||||
--amber: #e6a83a;
|
||||
--red: #e46a55;
|
||||
--border: #27393f;
|
||||
--shadow: 0 16px 40px rgba(0, 0, 0, .45);
|
||||
--surface: #1c2c33;
|
||||
--surface-2: #213238;
|
||||
--surface-3: #1a2a30;
|
||||
--overlay: rgba(22,37,44,.92);
|
||||
--overlay-border: rgba(64,86,94,.9);
|
||||
--map-bg: #1a262b;
|
||||
--glow: rgba(63, 179, 216, .10);
|
||||
/* OSM tiles are light; invert + rotate keeps roads/labels legible
|
||||
while the water/river overlays (drawn in SVG, unfiltered) keep
|
||||
their real colours. */
|
||||
--map-filter: invert(1) hue-rotate(180deg) brightness(.92) contrast(.9) saturate(.75);
|
||||
--chart-grid: rgba(230,238,241,.10);
|
||||
--mint-ink: #7fd7ac;
|
||||
--mint-border: #2a5c45;
|
||||
--ok-bg: #143b2c; --ok-ink: #9fe3c3; --ok-border: #2a6b4d;
|
||||
--watch-bg: #3d2f0d; --watch-ink: #f3d489; --watch-border: #6b4f14;
|
||||
--danger-bg: #421c15; --danger-ink: #ffb3a6; --danger-border: #7a2f22;
|
||||
--demo-bg: #3d2f0d; --demo-ink: #f3d489;
|
||||
color-scheme: dark;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; min-height: 100%; }
|
||||
@@ -43,7 +94,7 @@
|
||||
color: var(--ink);
|
||||
overflow-x: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 8% 0%, rgba(56, 180, 213, .12), transparent 25rem),
|
||||
radial-gradient(circle at 8% 0%, var(--glow), transparent 25rem),
|
||||
var(--paper);
|
||||
/* Thai families first so Thai text uses a proper Thai face where one
|
||||
is installed (Android/iOS/Windows all ship one); Latin falls
|
||||
@@ -60,7 +111,13 @@
|
||||
html[lang="th"] body { line-break: normal; overflow-wrap: anywhere; }
|
||||
html[lang="th"] .stat-value, html[lang="th"] .flow-value { overflow-wrap: normal; }
|
||||
.lang-toggle { padding: 9px 12px; font-size: .78rem; font-weight: 800; white-space: nowrap; }
|
||||
.lang-toggle[data-active-lang="th"] { background: var(--mint); border-color: #b6dfc9; color: #146644; }
|
||||
.lang-toggle[data-active-lang="th"] { background: var(--mint); border-color: var(--mint-border); color: var(--mint-ink); }
|
||||
.theme-toggle { padding: 9px 11px; font-size: .95rem; line-height: 1; }
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--card); color: var(--ink); }
|
||||
.leaflet-container a.leaflet-popup-close-button { color: var(--muted); }
|
||||
.leaflet-bar a, .leaflet-control-attribution { background: var(--surface); color: var(--ink); border-color: var(--border); }
|
||||
.leaflet-control-attribution a { color: var(--river); }
|
||||
[data-theme="dark"] .leaflet-control-attribution { background: var(--overlay); }
|
||||
.shell { max-width: 1500px; margin: 0 auto; padding: 24px; }
|
||||
header {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 20px;
|
||||
@@ -78,16 +135,16 @@
|
||||
.header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.live-pill {
|
||||
display: flex; gap: 8px; align-items: center; padding: 9px 13px; border-radius: 999px;
|
||||
background: var(--mint); color: #146644; font-weight: 750; font-size: .8rem;
|
||||
background: var(--mint); color: var(--mint-ink); font-weight: 750; font-size: .8rem;
|
||||
}
|
||||
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: #22a66c; box-shadow: 0 0 0 5px rgba(34,166,108,.12); }
|
||||
.live-pill.demo { background: #fdeeda; color: #9a6200; }
|
||||
.live-pill.demo { background: var(--demo-bg); color: var(--demo-ink); }
|
||||
.live-pill.demo .live-dot { background: #e6a23c; box-shadow: 0 0 0 5px rgba(230,162,60,.15); }
|
||||
button {
|
||||
border: 1px solid var(--border); border-radius: 11px; background: white; color: var(--ink);
|
||||
border: 1px solid var(--border); border-radius: 11px; background: var(--surface); color: var(--ink);
|
||||
padding: 10px 14px; cursor: pointer; font-weight: 700; box-shadow: 0 3px 10px rgba(22,52,62,.05);
|
||||
}
|
||||
button:hover { border-color: #a9c4bb; transform: translateY(-1px); }
|
||||
button:hover { border-color: var(--river-light); transform: translateY(-1px); }
|
||||
button:disabled { opacity: .55; cursor: wait; transform: none; }
|
||||
.stats { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 14px; margin-bottom: 14px; }
|
||||
.stat {
|
||||
@@ -102,13 +159,14 @@
|
||||
.workspace { display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 14px; min-height: 640px; }
|
||||
.map-card, .side-card { background: var(--card); border: 1px solid var(--border); border-radius: 19px; box-shadow: var(--shadow); overflow: hidden; }
|
||||
.map-card { position: relative; }
|
||||
#station-map { height: 640px; width: 100%; background: #dcebea; }
|
||||
#station-map { height: 640px; width: 100%; background: var(--map-bg); }
|
||||
.leaflet-tile-pane { filter: var(--map-filter); }
|
||||
.map-overlay {
|
||||
position: absolute; z-index: 500; top: 16px; left: 52px; right: 16px;
|
||||
display: flex; justify-content: space-between; align-items: flex-start; pointer-events: none;
|
||||
}
|
||||
.map-heading, .legend {
|
||||
background: rgba(255,255,255,.93); backdrop-filter: blur(9px); border: 1px solid rgba(207,224,218,.9);
|
||||
background: var(--overlay); backdrop-filter: blur(9px); border: 1px solid var(--overlay-border);
|
||||
border-radius: 13px; padding: 11px 13px; box-shadow: 0 7px 20px rgba(22,58,68,.12);
|
||||
pointer-events: auto; /* .map-overlay disables events; re-enable for the legend's rain toggle */
|
||||
}
|
||||
@@ -130,7 +188,7 @@
|
||||
width: 100%; display: grid; grid-template-columns: 40px minmax(0,1fr) auto; gap: 10px; align-items: center;
|
||||
padding: 11px; border: 0; border-radius: 12px; box-shadow: none; text-align: left; background: transparent;
|
||||
}
|
||||
.station-row:hover { background: #f1f7f5; transform: none; }
|
||||
.station-row:hover { background: var(--surface-2); transform: none; }
|
||||
.station-code { width: 40px; height: 40px; display: grid; place-items: center; border-radius: 11px; color: white; font-size: .68rem; font-weight: 850; }
|
||||
.station-code.long { font-size: .55rem; word-break: break-all; line-height: 1.15; padding: 2px; text-align: center; }
|
||||
.station-name { overflow: hidden; }
|
||||
@@ -139,9 +197,9 @@
|
||||
.station-name span { color: var(--muted); font-size: .68rem; margin-top: 3px; }
|
||||
.flow-value { text-align: right; font-size: .82rem; font-weight: 800; }
|
||||
.flow-value span { display: block; color: var(--muted); font-size: .61rem; font-weight: 650; margin-top: 2px; }
|
||||
.loading-panel, .error-panel { position: absolute; z-index: 600; inset: 0; display: grid; place-items: center; background: rgba(243,247,245,.88); }
|
||||
.loading-card { background: white; padding: 18px 22px; border-radius: 14px; box-shadow: var(--shadow); font-weight: 750; }
|
||||
.error-panel { display: none; color: #8d2f22; text-align: center; padding: 25px; }
|
||||
.loading-panel, .error-panel { position: absolute; z-index: 600; inset: 0; display: grid; place-items: center; background: var(--overlay); }
|
||||
.loading-card { background: var(--card); padding: 18px 22px; border-radius: 14px; box-shadow: var(--shadow); font-weight: 750; }
|
||||
.error-panel { display: none; color: var(--red); text-align: center; padding: 25px; }
|
||||
.marker-wrap { background: none; border: 0; }
|
||||
.flow-marker {
|
||||
--marker-color: #087da5; --marker-size: 26px;
|
||||
@@ -181,7 +239,7 @@
|
||||
.risk-chips { display: flex; gap: 6px; }
|
||||
.risk-chip { flex: 1; text-align: center; border-radius: 8px; padding: 5px 4px; font-size: .64rem; font-weight: 800; color: white; }
|
||||
.risk-chip span { display: block; font-weight: 650; font-size: .58rem; opacity: .85; }
|
||||
.p1-outlook { border: 1px solid var(--border); border-left: 4px solid var(--river); border-radius: 12px; padding: 12px 14px; margin-top: 14px; background: #f7fbfa; }
|
||||
.p1-outlook { border: 1px solid var(--border); border-left: 4px solid var(--river); border-radius: 12px; padding: 12px 14px; margin-top: 14px; background: var(--surface-3); }
|
||||
.p1-outlook-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.p1-outlook-head strong { font-size: .88rem; }
|
||||
.p1-peak { color: var(--muted); font-size: .76rem; }
|
||||
@@ -195,16 +253,16 @@
|
||||
.popup h3 { margin: 4px 0 2px; font-size: 1rem; }
|
||||
.popup-th { color: var(--muted); font-size: .74rem; }
|
||||
.popup-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 12px; }
|
||||
.popup-metric { background: #f1f7f5; padding: 8px; border-radius: 9px; }
|
||||
.popup-metric { background: var(--surface-2); padding: 8px; border-radius: 9px; }
|
||||
.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; }
|
||||
/* Verdict banner: light tinted backgrounds with dark ink (contrast-safe) */
|
||||
#flood-verdict.ok { background: #e2f3ea; color: #0c5138; border-color: #bfe3d2; }
|
||||
#flood-verdict.watch { background: #fdf1dc; color: #6b4a05; border-color: #f0d9a8; }
|
||||
#flood-verdict.danger { background: #fbe3de; color: #7c1d10; border-color: #f2c0b6; }
|
||||
#flood-verdict.ok { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-border); }
|
||||
#flood-verdict.watch { background: var(--watch-bg); color: var(--watch-ink); border-color: var(--watch-border); }
|
||||
#flood-verdict.danger { background: var(--danger-bg); color: var(--danger-ink); border-color: var(--danger-border); }
|
||||
#legend-toggle {
|
||||
display: none; pointer-events: auto; background: rgba(255,255,255,.93); border: 1px solid rgba(207,224,218,.9);
|
||||
display: none; pointer-events: auto; background: var(--overlay); border: 1px solid var(--overlay-border);
|
||||
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);
|
||||
}
|
||||
@@ -277,6 +335,7 @@
|
||||
<div class="live-pill" id="live-pill"><span class="live-dot"></span> <span id="live-pill-text" data-i18n="pill.live">LIVE DATA</span></div>
|
||||
<button id="refresh-button" type="button" data-i18n="action.refresh">↻ Refresh</button>
|
||||
<button id="lang-toggle" class="lang-toggle" type="button" data-active-lang="en" aria-label="Switch to Thai">ไทย</button>
|
||||
<button id="theme-toggle" class="theme-toggle" type="button" data-i18n-aria="theme.toggle" aria-label="Switch to dark mode" title="Switch to dark mode">🌙</button>
|
||||
<button id="replay-2024" type="button" data-i18n="replay.start">▶ Replay Oct 2024 flood</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -323,7 +382,7 @@
|
||||
<div class="legend-row"><i class="swatch" style="background:#2a1668;border-radius:50%"></i> <span data-i18n="legend.rain.extreme">Extreme > 150</span></div>
|
||||
<div class="legend-title" style="margin-top:10px" data-i18n="legend.other">Other markers</div>
|
||||
<div class="legend-row"><i class="swatch" style="background:#7b8f94"></i> <span data-i18n="legend.other.nodata">Gauge · no recent data</span></div>
|
||||
<div class="legend-row"><span style="font-weight:800;color:#0f6844">+</span> <span data-i18n="legend.other.sensor">Water-level sensor · colour = % of bank height</span></div>
|
||||
<div class="legend-row"><span style="font-weight:800;color:var(--green)">+</span> <span data-i18n="legend.other.sensor">Water-level sensor · colour = % of bank height</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -334,7 +393,7 @@
|
||||
|
||||
<aside class="side-card">
|
||||
<div class="side-head"><h2 data-i18n="side.title">Current station flow</h2><p data-i18n="side.subtitle">Select a station to locate it and load its history</p>
|
||||
<input type="search" id="station-search" data-i18n-placeholder="search.placeholder" data-i18n-aria="search.aria" placeholder="Search stations · code, name, river…" aria-label="Search stations" style="margin-top:10px;width:100%;box-sizing:border-box;padding:9px 12px;border:1px solid var(--border);border-radius:10px;background:white;font-size:.8rem">
|
||||
<input type="search" id="station-search" data-i18n-placeholder="search.placeholder" data-i18n-aria="search.aria" placeholder="Search stations · code, name, river…" aria-label="Search stations" style="margin-top:10px;width:100%;box-sizing:border-box;padding:9px 12px;border:1px solid var(--border);border-radius:10px;background:var(--surface);color:var(--ink);font-size:.8rem">
|
||||
</div>
|
||||
<div class="station-list" id="river-flow" aria-live="polite"></div>
|
||||
<div class="side-head" id="sensors-head" style="cursor:pointer" role="button" tabindex="0" aria-expanded="false" aria-controls="thaiwater-sensors" data-i18n-title="sensors.tip" title="Show / hide the ThaiWater/HII station list"><h2><span data-i18n="sensors.title">Additional basin stations</span> <span id="sensors-arrow" style="color:var(--muted);font-size:.8rem">▸</span></h2><p id="thaiwater-count" data-i18n="sensors.loading">Loading ThaiWater/HII stations…</p></div>
|
||||
@@ -365,10 +424,10 @@
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap">
|
||||
<div><h2 id="history-title" style="margin:0;font-size:1rem" data-i18n="history.title">Station history</h2><p id="history-status" class="subtitle" data-i18n="history.status">Select a station to load the last 7 days</p></div>
|
||||
<div class="history-controls" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<select id="history-range" style="padding:9px 12px;border:1px solid var(--border);border-radius:10px;background:white"><option value="24" data-i18n="range.24h">Last 24 hours</option><option value="168" selected data-i18n="range.7d">Last 7 days</option><option value="720" data-i18n="range.30d">Last 30 days</option><option value="2160" data-i18n="range.90d">Last 90 days</option><option value="876000" data-i18n="range.all">All time</option><option value="custom" hidden data-i18n="range.custom">Custom range</option></select>
|
||||
<input type="date" id="history-start" data-i18n-title="range.from" title="From date" style="padding:8px 10px;border:1px solid var(--border);border-radius:10px;background:white">
|
||||
<select id="history-range" style="padding:9px 12px;border:1px solid var(--border);border-radius:10px;background:var(--surface);color:var(--ink)"><option value="24" data-i18n="range.24h">Last 24 hours</option><option value="168" selected data-i18n="range.7d">Last 7 days</option><option value="720" data-i18n="range.30d">Last 30 days</option><option value="2160" data-i18n="range.90d">Last 90 days</option><option value="876000" data-i18n="range.all">All time</option><option value="custom" hidden data-i18n="range.custom">Custom range</option></select>
|
||||
<input type="date" id="history-start" data-i18n-title="range.from" title="From date" style="padding:8px 10px;border:1px solid var(--border);border-radius:10px;background:var(--surface);color:var(--ink)">
|
||||
<span style="color:var(--muted)">–</span>
|
||||
<input type="date" id="history-end" data-i18n-title="range.to" title="To date" style="padding:8px 10px;border:1px solid var(--border);border-radius:10px;background:white">
|
||||
<input type="date" id="history-end" data-i18n-title="range.to" title="To date" style="padding:8px 10px;border:1px solid var(--border);border-radius:10px;background:var(--surface);color:var(--ink)">
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:260px;margin-top:14px;overflow:hidden;position:relative"><canvas id="history-chart" data-i18n-aria="aria.chart" aria-label="Historical water level and discharge chart" style="display:block"></canvas><div id="history-placeholder" data-i18n="history.placeholder" style="position:absolute;inset:0;display:grid;place-items:center;color:var(--muted);font-size:.85rem;text-align:center;padding:20px">Click any station on the map or in the list to see its history</div></div>
|
||||
@@ -409,6 +468,7 @@
|
||||
'app.title': 'Ping River Live Monitor',
|
||||
'app.subtitle': 'Current water level and discharge across Northern Thailand',
|
||||
'pill.live': 'LIVE DATA',
|
||||
'theme.toggle': 'Toggle dark mode',
|
||||
'pill.stale': '⚠ STALE FEED',
|
||||
'pill.replay': '⏪ 2024 REPLAY',
|
||||
'pill.sim': '⚠ SIMULATION',
|
||||
@@ -584,6 +644,7 @@
|
||||
'app.title': 'ติดตามระดับน้ำปิงแบบเรียลไทม์',
|
||||
'app.subtitle': 'ระดับน้ำและอัตราการไหลปัจจุบันทั่วภาคเหนือของประเทศไทย',
|
||||
'pill.live': 'ข้อมูลสด',
|
||||
'theme.toggle': 'สลับโหมดมืด/สว่าง',
|
||||
'pill.stale': '⚠ ข้อมูลไม่อัปเดต',
|
||||
'pill.replay': '⏪ ย้อนเหตุการณ์ 2567',
|
||||
'pill.sim': '⚠ การจำลอง',
|
||||
@@ -809,6 +870,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Theme: explicit choice persists; otherwise follow the OS and track its changes.
|
||||
const THEME_KEY = 'ping-river-theme';
|
||||
const osDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
function currentTheme() { return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'; }
|
||||
function applyTheme(theme, persist) {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
const button = $('theme-toggle');
|
||||
if (button) {
|
||||
button.textContent = theme === 'dark' ? '☀️' : '🌙';
|
||||
}
|
||||
if (persist) { try { localStorage.setItem(THEME_KEY, theme); } catch (e) { /* private mode */ } }
|
||||
// Chart.js reads colours at construction: rebuild the open chart
|
||||
if (state.historyChart && state.selectedStation) loadHistory(state.selectedStation);
|
||||
}
|
||||
(() => {
|
||||
let saved = null;
|
||||
try { saved = localStorage.getItem(THEME_KEY); } catch (e) { /* private mode */ }
|
||||
applyTheme(saved === 'dark' || saved === 'light' ? saved : (osDark.matches ? 'dark' : 'light'), false);
|
||||
osDark.addEventListener('change', (e) => {
|
||||
let pinned = null;
|
||||
try { pinned = localStorage.getItem(THEME_KEY); } catch (err) { /* ignore */ }
|
||||
if (!pinned) applyTheme(e.matches ? 'dark' : 'light', false);
|
||||
});
|
||||
})();
|
||||
function cssVar(name) { return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); }
|
||||
|
||||
function setLang(lang) {
|
||||
state.lang = lang;
|
||||
try { localStorage.setItem(LANG_KEY, lang); } catch (e) { /* private mode */ }
|
||||
@@ -1208,13 +1295,14 @@
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false, animation: { duration: 0 },
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
color: cssVar('--ink'),
|
||||
scales: {
|
||||
flow: { type: 'linear', position: 'left' },
|
||||
level: { type: 'linear', position: 'right', grid: { drawOnChartArea: false } },
|
||||
x: { ticks: { maxTicksLimit: 12 } }
|
||||
flow: { type: 'linear', position: 'left', grid: { color: cssVar('--chart-grid') }, ticks: { color: cssVar('--muted') } },
|
||||
level: { type: 'linear', position: 'right', grid: { drawOnChartArea: false }, ticks: { color: cssVar('--muted') } },
|
||||
x: { ticks: { maxTicksLimit: 12, color: cssVar('--muted') }, grid: { color: cssVar('--chart-grid') } }
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
legend: { display: true, labels: { color: cssVar('--ink') } },
|
||||
floodBands: { enabled: true }
|
||||
}
|
||||
},
|
||||
@@ -1957,6 +2045,7 @@
|
||||
: t('forecast.expand', count);
|
||||
});
|
||||
$('lang-toggle').addEventListener('click', () => setLang(state.lang === 'th' ? 'en' : 'th'));
|
||||
$('theme-toggle').addEventListener('click', () => applyTheme(currentTheme() === 'dark' ? 'light' : 'dark', true));
|
||||
$('station-search').addEventListener('input', applyStationSearch);
|
||||
$('sensors-head').addEventListener('click', () => setSensorsOpen(!state.sensorsOpen));
|
||||
$('sensors-head').addEventListener('keydown', (event) => {
|
||||
|
||||
Reference in New Issue
Block a user