// 主题偏好:默认跟随系统;手动切换后写入 localStorage 并固定。 // 在 中同步执行,因此在首屏绘制前就写好了 data-theme,不会闪白或闪黑。 // 服务端 CSP 为 script-src 'self',不允许内联脚本,所以单独成文件。 (function () { const KEY = "reaction-board:theme"; const root = document.documentElement; const system = window.matchMedia("(prefers-color-scheme: dark)"); const ICONS = { light: '', dark: '', }; function stored() { try { const value = window.localStorage.getItem(KEY); return value === "dark" || value === "light" ? value : null; } catch (error) { return null; // 隐私模式等禁用 localStorage 时按跟随系统处理 } } function resolve() { return stored() || (system.matches ? "dark" : "light"); } function syncMeta(theme) { // 无 media 的 theme-color 位于最前,优先于 index.html 里按系统匹配的两条, // 这样手动切换后浏览器界面颜色也会跟着变。 const fallback = theme === "dark" ? "#1b1411" : "#f7f2ea"; const value = getComputedStyle(root).getPropertyValue("--bg").trim() || fallback; let meta = document.querySelector('meta[name="theme-color"]:not([media])'); if (!meta) { meta = document.createElement("meta"); meta.name = "theme-color"; document.head.prepend(meta); } meta.content = value; } function syncButtons(theme) { const target = theme === "dark" ? "浅色" : "深色"; document.querySelectorAll("[data-theme-toggle]").forEach((button) => { button.innerHTML = '`; button.setAttribute("aria-label", `切换到${target}主题`); button.title = `切换到${target}主题`; }); } function apply() { const theme = resolve(); root.dataset.theme = theme; syncMeta(theme); syncButtons(theme); } function toggle() { const next = resolve() === "dark" ? "light" : "dark"; try { window.localStorage.setItem(KEY, next); } catch (error) { // 无法持久化时仍切换当前页面,下次进入回到跟随系统 } apply(); } // 此时按钮还没解析出来,先锁定首屏配色。 apply(); function bind() { document.querySelectorAll("[data-theme-toggle]").forEach((button) => { button.addEventListener("click", toggle); }); apply(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bind, { once: true }); } else { bind(); } // 未手动选择时跟随系统主题变化实时切换。 system.addEventListener("change", () => { if (!stored()) apply(); }); })();