|
/**
|
|
* GeoLite Theme Toggle
|
|
* Provides light/dark theme switching with persistence
|
|
*/
|
|
(function () {
|
|
const storageKey = 'geolite-theme';
|
|
const toggleSelector = '[data-theme-toggle]';
|
|
const toggles = new Set();
|
|
let initialized = false;
|
|
|
|
function getMediaMatcher() {
|
|
if (typeof window === 'undefined' || !window.matchMedia) return null;
|
|
return window.matchMedia('(prefers-color-scheme: dark)');
|
|
}
|
|
|
|
function updateToggleAppearance(button, isDark) {
|
|
if (!button) return;
|
|
const icon = button.querySelector('.theme-toggle-icon');
|
|
const label = button.querySelector('.theme-toggle-label');
|
|
|
|
if (icon) {
|
|
icon.classList.toggle('bi-moon-stars', !isDark);
|
|
icon.classList.toggle('bi-brightness-high', isDark);
|
|
}
|
|
|
|
if (label) {
|
|
label.textContent = isDark ? 'Light' : 'Dark';
|
|
}
|
|
|
|
button.setAttribute('title', isDark ? 'Switch to light mode' : 'Switch to dark mode');
|
|
}
|
|
|
|
function applyTheme(theme, persist = true) {
|
|
if (!document.body) {
|
|
// Defer until body exists
|
|
document.addEventListener('DOMContentLoaded', () => applyTheme(theme, persist), { once: true });
|
|
return;
|
|
}
|
|
|
|
const isDark = theme === 'dark';
|
|
document.body.classList.toggle('dark-mode', isDark);
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
|
|
if (persist) {
|
|
try {
|
|
localStorage.setItem(storageKey, theme);
|
|
} catch (error) {
|
|
console.warn('Theme preference could not be saved:', error);
|
|
}
|
|
}
|
|
|
|
toggles.forEach((btn) => updateToggleAppearance(btn, isDark));
|
|
}
|
|
|
|
function currentTheme() {
|
|
return document.body && document.body.classList.contains('dark-mode') ? 'dark' : 'light';
|
|
}
|
|
|
|
function handleToggleClick(event) {
|
|
event.preventDefault();
|
|
const nextTheme = currentTheme() === 'dark' ? 'light' : 'dark';
|
|
applyTheme(nextTheme);
|
|
}
|
|
|
|
function registerToggle(button) {
|
|
if (!button || toggles.has(button)) return;
|
|
|
|
toggles.add(button);
|
|
button.addEventListener('click', handleToggleClick);
|
|
updateToggleAppearance(button, currentTheme() === 'dark');
|
|
}
|
|
|
|
function initToggles() {
|
|
document.querySelectorAll(toggleSelector).forEach(registerToggle);
|
|
}
|
|
|
|
function initTheme() {
|
|
if (initialized) return;
|
|
initialized = true;
|
|
|
|
let savedTheme = null;
|
|
try {
|
|
savedTheme = localStorage.getItem(storageKey);
|
|
} catch (error) {
|
|
console.warn('Unable to read saved theme preference:', error);
|
|
}
|
|
|
|
if (!savedTheme) {
|
|
const media = getMediaMatcher();
|
|
savedTheme = media && media.matches ? 'dark' : 'light';
|
|
}
|
|
|
|
applyTheme(savedTheme, Boolean(savedTheme));
|
|
|
|
const media = getMediaMatcher();
|
|
if (media) {
|
|
const handleChange = (event) => {
|
|
try {
|
|
if (localStorage.getItem(storageKey)) return;
|
|
} catch (error) {
|
|
// continue
|
|
}
|
|
applyTheme(event.matches ? 'dark' : 'light', false);
|
|
};
|
|
|
|
if (typeof media.addEventListener === 'function') {
|
|
media.addEventListener('change', handleChange);
|
|
} else if (typeof media.addListener === 'function') {
|
|
media.addListener(handleChange);
|
|
}
|
|
}
|
|
|
|
initToggles();
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initTheme);
|
|
} else {
|
|
initTheme();
|
|
}
|
|
|
|
window.GeoliteTheme = {
|
|
applyTheme,
|
|
registerToggle,
|
|
refresh: initToggles,
|
|
};
|
|
})();
|
|
|
|
|