// Handles user-facing settings (theme, motion, refresh cadence) and persistence // across reloads. Keeps side effects isolated so main.js simply wires callbacks. const DEFAULT_REFRESH_SEC = 10; const MIN_REFRESH_SEC = 5; const MAX_REFRESH_SEC = 120; const THEME_KEY = "pikit-theme"; const ANIM_KEY = "pikit-anim"; const REFRESH_KEY = "pikit-refresh-sec"; export function initSettings({ refreshHintMain, refreshHintServices, refreshFlagTop, refreshIntervalInput, refreshIntervalSave, refreshIntervalMsg, themeToggle, themeToggleIcon, animToggle, onTick, toast = null, defaultIntervalSec = DEFAULT_REFRESH_SEC, }) { let refreshIntervalMs = defaultIntervalSec * 1000; let refreshTimer = null; const prefersReduce = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; function updateRefreshHints(seconds) { const text = `${seconds} second${seconds === 1 ? "" : "s"}`; if (refreshHintMain) refreshHintMain.textContent = text; if (refreshHintServices) refreshHintServices.textContent = text; if (refreshFlagTop) refreshFlagTop.textContent = `Refresh: ${seconds}s`; if (refreshIntervalInput) refreshIntervalInput.value = seconds; } function setRefreshInterval(seconds, { silent = false } = {}) { const sec = Math.max( MIN_REFRESH_SEC, Math.min(MAX_REFRESH_SEC, Math.floor(seconds)), ); // Clamp to safe bounds; store milliseconds for setInterval refreshIntervalMs = sec * 1000; if (refreshTimer) clearInterval(refreshTimer); if (onTick) { refreshTimer = setInterval(onTick, refreshIntervalMs); } updateRefreshHints(sec); try { localStorage.setItem(REFRESH_KEY, String(sec)); } catch (e) { console.warn("Refresh persistence unavailable", e); } if (!silent && refreshIntervalMsg) { refreshIntervalMsg.textContent = ""; } } refreshIntervalSave?.addEventListener("click", () => { if (!refreshIntervalInput) return; const sec = Number(refreshIntervalInput.value); if (Number.isNaN(sec)) { if (refreshIntervalMsg) refreshIntervalMsg.textContent = ""; toast?.("Enter seconds.", "error"); return; } setRefreshInterval(sec); onTick?.(); // immediate refresh on change if (refreshIntervalMsg) refreshIntervalMsg.textContent = ""; toast?.("Refresh interval updated", "success"); }); function applyTheme(mode) { const theme = mode === "light" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", theme); if (themeToggleIcon) themeToggleIcon.textContent = theme === "light" ? "☀️" : "🌙"; try { localStorage.setItem(THEME_KEY, theme); } catch (e) { console.warn("Theme persistence unavailable", e); } } themeToggle?.addEventListener("click", () => { const current = document.documentElement.getAttribute("data-theme") || "dark"; applyTheme(current === "dark" ? "light" : "dark"); }); function applyAnim(enabled) { const on = enabled !== false; // default true // Using a data attribute lets CSS fully disable motion when off document.documentElement.setAttribute("data-anim", on ? "on" : "off"); if (animToggle) animToggle.checked = on; try { localStorage.setItem(ANIM_KEY, on ? "on" : "off"); } catch (e) { console.warn("Anim persistence unavailable", e); } } animToggle?.addEventListener("change", () => { applyAnim(animToggle.checked); }); // Initialize defaults let storedRefresh = defaultIntervalSec; try { const saved = localStorage.getItem(REFRESH_KEY); if (saved) { const n = Number(saved); if (!Number.isNaN(n)) storedRefresh = n; } } catch (e) { console.warn("Refresh persistence unavailable", e); } updateRefreshHints(storedRefresh); setRefreshInterval(storedRefresh, { silent: true }); applyTheme(localStorage.getItem(THEME_KEY) || "dark"); const storedAnim = localStorage.getItem(ANIM_KEY); const animDefault = storedAnim === "on" || storedAnim === "off" ? storedAnim === "on" : !prefersReduce; // respect system reduce-motion if no user choice applyAnim(animDefault); return { setRefreshInterval, applyTheme }; }