// ── Theme + Settings ── import { $, api, getCsrf } from './util.js'; import { state } from './state.js'; import { renderSettingsPanel as renderNotifSettings } from './notifications.js'; const overlay = $('settings-overlay'); const themeList = $('settings-theme-list'); // Render theme list in settings function renderThemes() { if (!themeList) return; const current = (designPending?.theme) || localStorage.getItem('hyprcrd-theme') || 'slate'; themeList.innerHTML = ''; for (const [key, t] of Object.entries(window.themes)) { const btn = document.createElement('button'); const active = key === current; btn.className = `flex items-center gap-3 px-3 py-2.5 text-left text-xs border transition-colors rounded ${active ? 'border-t-accent text-t-bright' : 'border-t-border text-t-muted hover:border-t-accent hover:text-t-bright'}`; btn.innerHTML = ` ${t.label} ${active ? '' : ''}`; btn.onclick = () => { window.applyTheme(key); if (designPending) { designPending.theme = key; window.saveDesign(designPending); flashSaved(); } renderThemes(); window._renderBoard?.(); }; themeList.appendChild(btn); } } // Tab switching overlay?.querySelectorAll('.settings-tab').forEach(tab => { tab.addEventListener('click', () => { overlay.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); const panel = tab.dataset.tab; overlay.querySelectorAll('[id^="settings-panel-"]').forEach(p => p.classList.add('hidden')); $(`settings-panel-${panel}`)?.classList.remove('hidden'); if (panel === 'caldav') { window._loadCaldavSettings?.(); } if (panel === 'relay') { window._loadRelaySettings?.(); } if (panel === 'notifications') renderNotifSettings(); if (panel === 'behavior') initBehaviorSettings(); if (panel === 'theme') initDesignSettings(); if (panel === 'plugins') window._openPluginSettings?.(); if (panel !== 'theme') setPeekUI(false); }); }); // ── Design (fonts, geometry, borders, spacing, effects, presets) ── let designPending = null; const SLIDER_FIELDS = [ ['setting-radius-card', 'radiusCard', 'val-radius-card'], ['setting-radius-button', 'radiusButton', 'val-radius-button'], ['setting-radius-input', 'radiusInput', 'val-radius-input'], ['setting-radius-modal', 'radiusModal', 'val-radius-modal'], ['setting-radius-tag', 'radiusTag', 'val-radius-tag'], ['setting-radius-image', 'radiusImage', 'val-radius-image'], ['setting-border-card', 'borderCard', 'val-border-card'], ['setting-bar-height', 'barHeight', 'val-bar-height'], ['setting-pad-card', 'padCard', 'val-pad-card'], ['setting-gap-board', 'gapBoard', 'val-gap-board'], ['setting-lift-card', 'liftCard', 'val-lift-card'], ['setting-blur-overlay', 'blurOverlay', 'val-blur-overlay'], ]; const SELECT_FIELDS = [ ['setting-font-headline', 'fontHeadline'], ['setting-font-content', 'fontContent'], ['setting-font-ui', 'fontUi'], ['setting-border-style', 'borderStyle'], ['setting-shadow-card', 'shadowCard'], ['setting-shadow-modal', 'shadowModal'], ['setting-shadow-ui', 'shadowUi'], ['setting-anim-speed', 'animSpeed'], ]; function updatePreview() { const preview = $('theme-preview'); if (!preview || !designPending) return; const d = designPending, fonts = window.themeFonts, shadows = window.designShadows; const set = (k, v) => preview.style.setProperty(k, v); set('--tp-font-headline', fonts[d.fontHeadline] || fonts.pixel); set('--tp-font-content', fonts[d.fontContent] || fonts.mono); set('--tp-font-ui', fonts[d.fontUi] || fonts.pixel); set('--tp-radius-card', d.radiusCard + 'px'); set('--tp-radius-button', d.radiusButton + 'px'); set('--tp-radius-input', d.radiusInput + 'px'); set('--tp-radius-tag', d.radiusTag + 'px'); set('--tp-border-card', d.borderCard + 'px'); set('--tp-border-style', d.borderStyle); set('--tp-bar-height', d.barHeight + 'px'); set('--tp-pad-card', d.padCard + 'px'); set('--tp-shadow-card', shadows[d.shadowCard] || 'none'); } function fillFontSelect(id, value) { const el = $(id); if (!el) return; el.innerHTML = ''; for (const [key, label] of Object.entries(window.themeFontLabels)) { const opt = document.createElement('option'); opt.value = key; opt.textContent = label; if (key === value) opt.selected = true; el.appendChild(opt); } } function syncControlsFromPending() { if (!designPending) return; fillFontSelect('setting-font-headline', designPending.fontHeadline); fillFontSelect('setting-font-content', designPending.fontContent); fillFontSelect('setting-font-ui', designPending.fontUi); for (const [id, key] of SELECT_FIELDS) { if (id.startsWith('setting-font-')) continue; const el = $(id); if (el) el.value = String(designPending[key] ?? ''); } for (const [id, key, valId] of SLIDER_FIELDS) { const v = String(designPending[key] ?? 0); const el = $(id); if (el) el.value = v; const valEl = $(valId); if (valEl) valEl.textContent = v; } } function commitDesign() { updatePreview(); window.applyDesign(designPending); window.saveDesign(designPending); flashSaved(); } let saveFlashTimer = null; function flashSaved() { const status = $('design-status'); if (!status) return; status.textContent = 'saved'; status.style.color = 'var(--t-accent)'; clearTimeout(saveFlashTimer); saveFlashTimer = setTimeout(() => { status.textContent = 'auto-saving'; status.style.color = 'var(--t-dim)'; }, 700); } let designWired = false; function initDesignSettings() { designPending = window.loadDesign(); syncControlsFromPending(); updatePreview(); refreshPresetSelect(); const status = $('design-status'); if (status) { status.textContent = 'auto-saving'; status.style.color = 'var(--t-dim)'; } if (designWired) return; designWired = true; // Selects for (const [id, key] of SELECT_FIELDS) { const el = $(id); if (!el) continue; el.addEventListener('change', () => { designPending[key] = el.value; commitDesign(); }); } // Sliders for (const [id, key, valId] of SLIDER_FIELDS) { const el = $(id); if (!el) continue; el.addEventListener('input', () => { const num = Number(el.value); designPending[key] = num; const v = $(valId); if (v) v.textContent = num; commitDesign(); }); } } $('design-reset')?.addEventListener('click', () => { designPending = { ...window.designDefaults }; syncControlsFromPending(); commitDesign(); renderThemes(); }); // ── Peek toggle (hide background dim so the real board is visible behind the popup) ── function setPeekUI(peeking) { document.body.classList.toggle('settings-peeking', peeking); const sw = $('design-peek-toggle'); if (sw) sw.classList.toggle('on', peeking); } const peekRow = $('design-peek-row'); if (peekRow) peekRow.onclick = () => { setPeekUI(!document.body.classList.contains('settings-peeking')); }; // ── Presets ── function loadUserPresets() { try { return JSON.parse(localStorage.getItem('hyprcrd-design-presets') || '{}'); } catch { return {}; } } function saveUserPresets(p) { localStorage.setItem('hyprcrd-design-presets', JSON.stringify(p)); } function refreshPresetSelect() { const sel = $('design-preset-select'); if (!sel) return; // localStorage wins over sel.value: the