const STORAGE_KEY = 'theme';
const CUSTOM_THEMES_KEY = 'custom-themes';
const LIGHT_QUERY = window.matchMedia('(prefers-color-scheme: light)');
// All CSS variable names managed by the theme system.
// Must stay in sync with COLOR_VARS in account.js.
export const MANAGED_VARS = [
'--accent', '--accent2', '--bg', '--surface', '--surface2', '--border',
'--text', '--muted', '--waveform', '--waveform-progress',
'--danger', '--success', '--rec',
];
/**
* Returns the stored theme ID, or 'auto' if none saved.
* @returns {string}
*/
export function getTheme() {
return localStorage.getItem(STORAGE_KEY) || 'auto';
}
/**
* @typedef {object} CustomTheme
* @property {string} id
* @property {string} name
* @property {'light'|'dark'} base
* @property {object} colors
*/
/**
* Returns all saved custom theme objects.
* @returns {CustomTheme[]}
*/
export function getCustomThemes() {
try { return JSON.parse(localStorage.getItem(CUSTOM_THEMES_KEY) || '[]'); } catch { return []; }
}
/**
* Persists the custom themes array.
* @param {Array} themes - The custom themes array to persist.
*/
export function saveCustomThemes(themes) {
localStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(themes));
}
/**
* Resolves any theme ID to 'light' or 'dark'.
* @param {string} themeId - The theme ID to resolve ('auto', 'light', 'dark', or custom ID).
* @returns {'light'|'dark'}
*/
function resolveBase(themeId) {
if (themeId === 'auto') return LIGHT_QUERY.matches ? 'light' : 'dark';
if (themeId === 'light') return 'light';
if (themeId === 'dark') return 'dark';
const custom = getCustomThemes().find(t => t.id === themeId);
return custom ? custom.base : (LIGHT_QUERY.matches ? 'light' : 'dark');
}
/**
* Clears all managed CSS variables then applies the saved colors for `themeId`.
* @param {string} themeId - The theme ID whose colors to apply.
*/
function applyThemeColors(themeId) {
MANAGED_VARS.forEach(v => document.documentElement.style.removeProperty(v));
let colors = {};
if (themeId === 'auto' || themeId === 'light' || themeId === 'dark') {
try { colors = JSON.parse(localStorage.getItem('custom-colors-' + resolveBase(themeId)) || '{}'); } catch {}
} else {
const custom = getCustomThemes().find(t => t.id === themeId);
if (custom) colors = custom.colors;
}
Object.entries(colors).forEach(([k, v]) => document.documentElement.style.setProperty(k, v));
}
/**
* Switches to `themeId`, applies data-theme + color overrides, and persists the choice.
* Accepts 'auto', 'light', 'dark', or a custom theme ID.
* @param {string} themeId - The theme ID to switch to.
*/
export function setTheme(themeId) {
localStorage.setItem(STORAGE_KEY, themeId);
document.documentElement.dataset.theme = resolveBase(themeId);
applyThemeColors(themeId);
window.dispatchEvent(new CustomEvent('themechange', { detail: themeId }));
}
/** Reads the saved preference and applies it. Call as early as possible. */
export function initTheme() {
const themeId = getTheme();
document.documentElement.dataset.theme = resolveBase(themeId);
applyThemeColors(themeId);
LIGHT_QUERY.addEventListener('change', () => {
if (getTheme() === 'auto') initTheme();
});
}