diff --git a/static/app.js b/static/app.js
index 2edf8a2..15eb708 100644
--- a/static/app.js
+++ b/static/app.js
@@ -1,6 +1,27 @@
(function () {
"use strict";
+ // === Theme ===
+ var themeToggle = document.getElementById("theme-toggle");
+
+ function applyTheme(dark) {
+ document.documentElement.classList.toggle("dark", dark);
+ themeToggle.textContent = dark ? "\u2600" : "\u263E";
+ themeToggle.title = dark ? "Switch to light mode" : "Switch to dark mode";
+ }
+
+ var savedTheme = localStorage.getItem("theme");
+ var prefersDark = savedTheme === "dark" ||
+ (savedTheme === null && window.matchMedia("(prefers-color-scheme: dark)").matches);
+ applyTheme(prefersDark);
+
+ themeToggle.addEventListener("click", function () {
+ var isDark = document.documentElement.classList.toggle("dark");
+ localStorage.setItem("theme", isDark ? "dark" : "light");
+ themeToggle.textContent = isDark ? "\u2600" : "\u263E";
+ themeToggle.title = isDark ? "Switch to light mode" : "Switch to dark mode";
+ });
+
// === Map Setup ===
var map = L.map("map").setView([52.37, 9.73], 9);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
@@ -247,7 +268,7 @@
function displayRepeaters(repeaters) {
markerLayer.clearLayers();
repeaters.forEach(function (r) {
- var color = r.band === "2m" ? "#2196F3" : "#FF9800";
+ var color = r.band === "2m" ? "#2196F3" : "#D32F2F";
var marker = L.circleMarker([r.lat, r.lng], {
radius: 6,
fillColor: color,
diff --git a/static/index.html b/static/index.html
index 3025797..9f583ed 100644
--- a/static/index.html
+++ b/static/index.html
@@ -15,6 +15,7 @@
+
diff --git a/static/style.css b/static/style.css
index 968816a..496bc12 100644
--- a/static/style.css
+++ b/static/style.css
@@ -4,6 +4,42 @@
box-sizing: border-box;
}
+:root {
+ --bg: #fff;
+ --text: #333;
+ --text-muted: #666;
+ --text-faint: #888;
+ --text-fainter: #999;
+ --border: #ccc;
+ --border-light: #eee;
+ --border-lighter: #f0f0f0;
+ --hover-bg: #f5f5f5;
+ --shadow: rgba(0, 0, 0, 0.3);
+ --shadow-light: rgba(0, 0, 0, 0.15);
+ --input-bg: #fff;
+ --clear-btn-bg: #eee;
+ --clear-btn-hover: #ddd;
+ --popup-heading: #222;
+}
+
+html.dark {
+ --bg: #1e1e2e;
+ --text: #cdd6f4;
+ --text-muted: #a6adc8;
+ --text-faint: #7f849c;
+ --text-fainter: #6c7086;
+ --border: #45475a;
+ --border-light: #313244;
+ --border-lighter: #2a2a3c;
+ --hover-bg: #313244;
+ --shadow: rgba(0, 0, 0, 0.5);
+ --shadow-light: rgba(0, 0, 0, 0.3);
+ --input-bg: #313244;
+ --clear-btn-bg: #45475a;
+ --clear-btn-hover: #585b70;
+ --popup-heading: #cdd6f4;
+}
+
html, body {
height: 100%;
overflow: hidden;
@@ -15,14 +51,47 @@ html, body {
height: 100%;
}
+html.dark .leaflet-tile-pane {
+ filter: brightness(0.7) contrast(1.1) saturate(0.8);
+}
+
+html.dark .leaflet-popup-content-wrapper,
+html.dark .leaflet-popup-tip {
+ background: var(--bg);
+ color: var(--text);
+}
+
+html.dark .leaflet-popup-content-wrapper {
+ box-shadow: 0 3px 14px var(--shadow);
+}
+
+html.dark .leaflet-control-zoom a {
+ background: var(--bg);
+ color: var(--text);
+ border-color: var(--border);
+}
+
+html.dark .leaflet-control-zoom a:hover {
+ background: var(--hover-bg);
+}
+
+html.dark .leaflet-control-attribution {
+ background: rgba(30, 30, 46, 0.8);
+ color: var(--text-fainter);
+}
+
+html.dark .leaflet-control-attribution a {
+ color: var(--text-faint);
+}
+
#controls {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
- background: white;
+ background: var(--bg);
border-radius: 8px;
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 2px 6px var(--shadow);
padding: 10px 14px;
width: 260px;
}
@@ -49,7 +118,15 @@ html, body {
}
.band-70cm {
- color: #F57C00;
+ color: #D32F2F;
+}
+
+html.dark .band-2m {
+ color: #64b5f6;
+}
+
+html.dark .band-70cm {
+ color: #ef5350;
}
#controls input[type="checkbox"] {
@@ -61,10 +138,24 @@ html, body {
.count {
margin-left: auto;
font-size: 12px;
- color: #888;
+ color: var(--text-faint);
white-space: nowrap;
}
+.theme-toggle {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 0;
+ line-height: 1;
+ opacity: 0.6;
+}
+
+.theme-toggle:hover {
+ opacity: 1;
+}
+
.route-row {
margin-top: 6px;
}
@@ -80,14 +171,14 @@ html, body {
left: 0;
right: 0;
z-index: 1001;
- background: white;
- border: 1px solid #ccc;
+ background: var(--bg);
+ border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 5px 5px;
list-style: none;
max-height: 180px;
overflow-y: auto;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 4px 8px var(--shadow-light);
}
.autocomplete-list.open {
@@ -98,7 +189,7 @@ html, body {
padding: 7px 10px;
font-size: 12px;
cursor: pointer;
- border-bottom: 1px solid #f0f0f0;
+ border-bottom: 1px solid var(--border-lighter);
line-height: 1.3;
}
@@ -108,25 +199,27 @@ html, body {
.autocomplete-list li:hover,
.autocomplete-list li.active {
- background: #f5f5f5;
+ background: var(--hover-bg);
}
.autocomplete-list li .ac-main {
- color: #333;
+ color: var(--text);
}
.autocomplete-list li .ac-sub {
- color: #999;
+ color: var(--text-fainter);
font-size: 11px;
}
.route-row input[type="text"] {
width: 100%;
padding: 7px 10px;
- border: 1px solid #ccc;
+ border: 1px solid var(--border);
border-radius: 5px;
font-size: 13px;
outline: none;
+ background: var(--input-bg);
+ color: var(--text);
}
.route-row input[type="text"]:focus {
@@ -158,39 +251,44 @@ html, body {
cursor: default;
}
+html.dark #route-btn:disabled {
+ background: #2e7d32;
+ color: #a5d6a7;
+}
+
#clear-btn {
- background: #eee;
- color: #333;
+ background: var(--clear-btn-bg);
+ color: var(--text);
}
#clear-btn:hover {
- background: #ddd;
+ background: var(--clear-btn-hover);
}
.coords-row {
margin-top: 6px;
font-size: 11px;
- color: #666;
+ color: var(--text-muted);
font-family: "SF Mono", "Menlo", "Monaco", "Consolas", monospace;
line-height: 1.5;
min-height: 1.5em;
}
.coords-row .maidenhead {
- color: #999;
+ color: var(--text-fainter);
}
.attribution {
margin-top: 8px;
padding-top: 8px;
- border-top: 1px solid #eee;
+ border-top: 1px solid var(--border-light);
font-size: 10px;
- color: #999;
+ color: var(--text-fainter);
line-height: 1.4;
}
.attribution a {
- color: #888;
+ color: var(--text-faint);
}
/* Popups */
@@ -198,7 +296,7 @@ html, body {
.rptr-popup h3 {
margin: 0 0 6px 0;
font-size: 15px;
- color: #222;
+ color: var(--popup-heading);
}
.rptr-popup table {
@@ -209,13 +307,14 @@ html, body {
.rptr-popup td {
padding: 2px 6px;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid var(--border-light);
vertical-align: top;
+ color: var(--text);
}
.rptr-popup td:first-child {
font-weight: 600;
- color: #666;
+ color: var(--text-muted);
white-space: nowrap;
width: 80px;
}
@@ -234,7 +333,7 @@ html, body {
}
.rptr-popup .band-70cm {
- background: #FF9800;
+ background: #D32F2F;
}
@media (max-width: 768px) {