(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", {
maxZoom: 19,
attribution:
'© OpenStreetMap contributors',
}).addTo(map);
// === State ===
var markerLayer = L.layerGroup().addTo(map);
var routeLayer = null;
var routePoints = null; // stored [lat, lng] pairs for re-fetching on band change
var isRouteMode = false;
var debounceTimer = null;
var controller = null;
// === DOM ===
var band2m = document.getElementById("band-2m");
var band70cm = document.getElementById("band-70cm");
var countEl = document.getElementById("count");
var fromInput = document.getElementById("route-from");
var toInput = document.getElementById("route-to");
var routeBtn = document.getElementById("route-btn");
var clearBtn = document.getElementById("clear-btn");
clearBtn.style.display = "none";
// === Autocomplete ===
function setupAutocomplete(input, listEl) {
var acTimer = null;
var acController = null;
var activeIdx = -1;
function closeList() {
listEl.classList.remove("open");
listEl.innerHTML = "";
activeIdx = -1;
}
function selectItem(displayName) {
input.value = displayName;
closeList();
}
function updateActive() {
var items = listEl.querySelectorAll("li");
items.forEach(function (li, i) {
li.classList.toggle("active", i === activeIdx);
});
if (activeIdx >= 0 && items[activeIdx]) {
items[activeIdx].scrollIntoView({ block: "nearest" });
}
}
input.addEventListener("input", function () {
clearTimeout(acTimer);
if (acController) acController.abort();
var q = input.value.trim();
if (q.length < 3) {
closeList();
return;
}
acTimer = setTimeout(function () {
acController = new AbortController();
fetch(
"https://nominatim.openstreetmap.org/search?" +
new URLSearchParams({
q: q,
format: "json",
limit: "5",
addressdetails: "0",
"accept-language": navigator.language,
}),
{ signal: acController.signal }
)
.then(function (r) { return r.json(); })
.then(function (results) {
listEl.innerHTML = "";
activeIdx = -1;
if (!results.length) {
closeList();
return;
}
results.forEach(function (item) {
var li = document.createElement("li");
var parts = item.display_name.split(", ");
var main = parts.slice(0, 2).join(", ");
var sub = parts.slice(2).join(", ");
li.innerHTML =
'' + escapeHtml(main) + "" +
(sub ? '
' + escapeHtml(sub) + "" : "");
li.addEventListener("mousedown", function (e) {
e.preventDefault();
selectItem(item.display_name);
});
listEl.appendChild(li);
});
listEl.classList.add("open");
})
.catch(function (err) {
if (err.name !== "AbortError") closeList();
});
}, 300);
});
input.addEventListener("keydown", function (e) {
if (!listEl.classList.contains("open")) return;
var items = listEl.querySelectorAll("li");
if (e.key === "ArrowDown") {
e.preventDefault();
activeIdx = Math.min(activeIdx + 1, items.length - 1);
updateActive();
} else if (e.key === "ArrowUp") {
e.preventDefault();
activeIdx = Math.max(activeIdx - 1, 0);
updateActive();
} else if (e.key === "Enter" && activeIdx >= 0) {
e.preventDefault();
e.stopPropagation();
var parts = items[activeIdx].querySelector(".ac-main").textContent;
var sub = items[activeIdx].querySelector(".ac-sub");
selectItem(parts + (sub ? ", " + sub.textContent : ""));
} else if (e.key === "Escape") {
closeList();
}
});
input.addEventListener("blur", function () {
setTimeout(closeList, 150);
});
}
setupAutocomplete(fromInput, document.getElementById("ac-from"));
setupAutocomplete(toInput, document.getElementById("ac-to"));
// === Coordinates display ===
var coordsEl = document.getElementById("coords");
function toMaidenhead(lat, lng) {
lng = lng + 180;
lat = lat + 90;
var loc = "";
loc += String.fromCharCode(65 + Math.floor(lng / 20));
loc += String.fromCharCode(65 + Math.floor(lat / 10));
lng = (lng % 20);
lat = (lat % 10);
loc += Math.floor(lng / 2);
loc += Math.floor(lat);
lng = (lng % 2) * 60;
lat = (lat % 1) * 60;
loc += String.fromCharCode(97 + Math.floor(lng / 5));
loc += String.fromCharCode(97 + Math.floor(lat / 2.5));
return loc;
}
map.on("mousemove", function (e) {
var lat = e.latlng.lat;
var lng = e.latlng.lng;
var grid = toMaidenhead(lat, lng);
coordsEl.innerHTML =
lat.toFixed(5) + ", " + lng.toFixed(5) +
' ' + grid + "";
});
map.on("mouseout", function () {
coordsEl.innerHTML = "";
});
// === Utilities ===
function getSelectedBand() {
var has2m = band2m.checked;
var has70cm = band70cm.checked;
if (has2m && has70cm) return "all";
if (has2m) return "2m";
if (has70cm) return "70cm";
return "all";
}
function escapeHtml(str) {
if (!str) return "";
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function showCount(count) {
countEl.textContent = count + " repeater" + (count !== 1 ? "s" : "");
}
function showStatus(msg) {
countEl.textContent = msg;
}
// === Popup ===
function buildPopup(r) {
var bandClass = r.band === "2m" ? "band-2m" : "band-70cm";
var html = '
| Freq | " + r.frequency.toFixed(5) + ' MHz ' + escapeHtml(r.band) + " |
| Offset | " + escapeHtml(r.offset) + " MHz |
| CC | " + r.color_code + " |
| Location | " + loc + " |
| Network | " + escapeHtml(r.ipsc_network) + " |
| Trustee | " + escapeHtml(r.trustee) + " |
| Timeslots | " + escapeHtml(r.ts_linked) + " |
| Status | " + escapeHtml(r.status) + " |