import { syncAriaRadioGroup } from "../shared/dom.js"; import { clampNum } from "../shared/math.js"; import { subscribePrefersColorSchemeChange } from "../shared/media.js"; import { PICK, S_RANGE, L_RANGE, RADIUS_IDS, THEME_MODE_IDS, effectiveThemeMode, FONT_IDS, MESH_BG_IDS, applyThemeState, drawSLPlane, readThemeState, updateKnob, writeThemeState, } from "./theme-core.js"; import { ensureThemeFontLoaded } from "./theme-fonts.js"; export function initThemeControls() { const picker = document.querySelector("[data-theme-picker]"); const panel = document.querySelector("[data-theme-panel]"); const panelBtns = document.querySelectorAll("[data-theme-panel-btn]"); const themeModeBtns = document.querySelectorAll("[data-theme-mode]"); const canvas = document.querySelector("[data-sl-canvas]"); const knob = document.querySelector("[data-sl-knob]"); const hueSlider = document.querySelector("[data-hue-slider]"); const radiusBtns = document.querySelectorAll("[data-radius-option]"); const fontBtns = document.querySelectorAll("[data-font-option]"); const meshBgBtns = document.querySelectorAll("[data-mesh-bg-option]"); const themeOk = picker && panel && panelBtns.length > 0 && themeModeBtns.length === 3 && canvas && hueSlider; if (!themeOk) { window.siteThemePanel = { open: () => {}, close: () => {}, toggle: () => {} }; return; } const state = readThemeState(); applyThemeState(state); ensureThemeFontLoaded(state.font); function acc() { return state[effectiveThemeMode(state)] || state.light; } function syncHueSlider() { hueSlider.value = String(Math.round(acc().h)); hueSlider.setAttribute("aria-valuenow", String(Math.round(acc().h))); } function persistAndApply() { applyThemeState(state); ensureThemeFontLoaded(state.font); writeThemeState(state); } function bindOptionButtons(btns, attr, ids, stateKey) { btns.forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = btn.getAttribute(attr); if (!ids.includes(id)) return; state[stateKey] = id; syncAriaRadioGroup(btns, attr, state[stateKey]); persistAndApply(); }); }); } syncHueSlider(); drawSLPlane(canvas, acc().h); updateKnob(knob, acc().s, acc().l); syncAriaRadioGroup(radiusBtns, "data-radius-option", state.radius); syncAriaRadioGroup(themeModeBtns, "data-theme-mode", state.mode); syncAriaRadioGroup(fontBtns, "data-font-option", state.font); if (meshBgBtns.length >= 2) syncAriaRadioGroup(meshBgBtns, "data-mesh-bg-option", state.meshBg); bindOptionButtons(radiusBtns, "data-radius-option", RADIUS_IDS, "radius"); bindOptionButtons(fontBtns, "data-font-option", FONT_IDS, "font"); bindOptionButtons(themeModeBtns, "data-theme-mode", THEME_MODE_IDS, "mode"); themeModeBtns.forEach((btn) => btn.addEventListener("click", () => { if (!panel.hidden) { syncHueSlider(); drawSLPlane(canvas, acc().h); updateKnob(knob, acc().s, acc().l); } }), ); if (meshBgBtns.length >= 2) bindOptionButtons(meshBgBtns, "data-mesh-bg-option", MESH_BG_IDS, "meshBg"); function setPanelOpen(open) { panel.hidden = !open; panelBtns.forEach((btn) => { btn.setAttribute("aria-expanded", open ? "true" : "false"); btn.setAttribute("aria-label", open ? "Закрыть настройки стиля" : "Настройки стиля"); }); if (open) { drawSLPlane(canvas, acc().h); updateKnob(knob, acc().s, acc().l); syncAriaRadioGroup(themeModeBtns, "data-theme-mode", state.mode); syncAriaRadioGroup(fontBtns, "data-font-option", state.font); if (meshBgBtns.length >= 2) syncAriaRadioGroup(meshBgBtns, "data-mesh-bg-option", state.meshBg); } } subscribePrefersColorSchemeChange(() => { if (state.mode !== "system") return; applyThemeState(state); if (!panel.hidden) { syncHueSlider(); drawSLPlane(canvas, acc().h); updateKnob(knob, acc().s, acc().l); } }); panelBtns.forEach((btn) => { btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); setPanelOpen(panel.hidden); }); }); picker.addEventListener("click", (e) => e.stopPropagation()); document.addEventListener("click", (e) => { const t = e.target; if (!(t instanceof Node)) return; const el = t instanceof Element ? t : t.parentElement; if (el?.closest("[data-theme-panel-btn]")) return; if (panel.contains(t) || picker.contains(t) || panel.hidden) return; setPanelOpen(false); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !panel.hidden) { setPanelOpen(false); e.preventDefault(); } }); try { const params = new URLSearchParams(window.location.search); if (params.get("themePanel") === "open") requestAnimationFrame(() => setPanelOpen(true)); } catch { /* ignore */ } window.siteThemePanel = { open: () => setPanelOpen(true), close: () => setPanelOpen(false), toggle: () => setPanelOpen(!!panel.hidden), }; let canvasRect = null; function pickFromClientXY(clientX, clientY) { const r = canvasRect || canvas.getBoundingClientRect(); if (r.width <= 0 || r.height <= 0) return; const x = clampNum(((clientX - r.left) / r.width) * 100, 0, 100, 0) / 100; const y = clampNum(((clientY - r.top) / r.height) * 100, 0, 100, 0) / 100; const a = acc(); a.s = Math.round(PICK.sMin + x * S_RANGE); a.l = Math.round(PICK.lMin + (1 - y) * L_RANGE); updateKnob(knob, a.s, a.l); persistAndApply(); } canvas.addEventListener("pointerdown", (e) => { e.preventDefault(); canvasRect = canvas.getBoundingClientRect(); canvas.setPointerCapture(e.pointerId); pickFromClientXY(e.clientX, e.clientY); }); canvas.addEventListener("pointermove", (e) => { if (!canvas.hasPointerCapture(e.pointerId)) return; e.preventDefault(); pickFromClientXY(e.clientX, e.clientY); }); canvas.addEventListener("pointerup", (e) => { if (canvas.hasPointerCapture(e.pointerId)) canvas.releasePointerCapture(e.pointerId); canvasRect = null; }); hueSlider.addEventListener("input", () => { const a = acc(); a.h = clampNum(hueSlider.value, 0, 360, a.h); syncHueSlider(); drawSLPlane(canvas, a.h); updateKnob(knob, a.s, a.l); persistAndApply(); }); }