const API = ""; const THEME_KEY = "pts_theme"; function resolveTheme(pref) { if (pref === "light" || pref === "dark") return pref; // system return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; } function getThemePref() { return localStorage.getItem(THEME_KEY) || "system"; } function applyTheme(pref) { const resolved = resolveTheme(pref); document.documentElement.setAttribute("data-theme", resolved); document.documentElement.dataset.themePref = pref; document.documentElement.classList.toggle("dark", resolved === "dark"); document.querySelectorAll("[data-theme-option]").forEach((btn) => { btn.classList.toggle("active", btn.getAttribute("data-theme-option") === pref); }); } function setThemePref(pref) { localStorage.setItem(THEME_KEY, pref); applyTheme(pref); } function initTheme() { applyTheme(getThemePref()); // react to OS changes when using system window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => { if (getThemePref() === "system") applyTheme("system"); }); document.querySelectorAll("[data-theme-option]").forEach((btn) => { btn.addEventListener("click", () => { setThemePref(btn.getAttribute("data-theme-option")); }); }); } async function api(path, options = {}) { const res = await fetch(`${API}${path}`, { headers: { "Content-Type": "application/json", ...(options.headers || {}) }, ...options, }); const data = await res.json().catch(() => ({})); if (!res.ok) { const detail = data.detail; const message = typeof detail === "string" ? detail : Array.isArray(detail) ? detail.map((d) => d.msg || JSON.stringify(d)).join("; ") : res.statusText; throw new Error(message || res.statusText); } return data; } function todayStr() { return new Date().toISOString().slice(0, 10); } function pill(label, value, ok) { const cls = ok === true ? "ok" : ok === false ? "warn" : ""; return `${label}${value}`; } // apply ASAP if script loads after head bootstrap if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initTheme); } else { initTheme(); }