/* ---------------- theme ---------------- */ // A theme, and for those that come in both, light, dark or Auto, chosen in Settings and kept in a // cookie, so each device has its own: Glass on a phone, Dracula on a desktop (issue #69). The // server reads it and writes it onto the page's tag (data-theme, data-choice) so the page // is drawn in it from the start. The // page gets data-mode, light or dark, which is all the CSS reads: Auto is worked out here, from // the system, so no palette is written twice. const THEMES: Record = { adwaita: {name: 'Adwaita', modes: true}, catppuccin: {name: 'Catppuccin', modes: true}, classic: {name: 'Classic', modes: false}, dracula: {name: 'Dracula', modes: true}, flatremix: {name: 'Flat Remix', modes: true}, glass: {name: 'Glass', modes: true}, gruvbox: {name: 'Gruvbox', modes: true}, contrast: {name: 'High contrast', modes: true}, material: {name: 'Material', modes: true}, modern: {name: 'Modern', modes: true}, nordic: {name: 'Nordic', modes: true}, paper: {name: 'Paper', modes: false}, solarized: {name: 'Solarized', modes: true}, }; const MODES: Record = {auto: 'Auto (matches your system)', light: 'Light', dark: 'Dark'}; // Before themes came in light and dark, ipx.theme in localStorage held one of these. const OLD_THEMES: Record = {dark: ['modern', 'dark'], light: ['modern', 'light'], auto: ['modern', 'auto']}; const systemDark = window.matchMedia?.('(prefers-color-scheme: dark)'); const theme = {name: 'modern', mode: 'dark'}; /// `save` for a choice made in Settings, which goes to this browser's cookie; not for applying one. function setTheme(name = theme.name, mode = theme.mode, save = false){ theme.name = THEMES[name] ? name : 'modern'; theme.mode = MODES[mode] ? mode : 'dark'; const both = THEMES[theme.name].modes; const root = document.documentElement; root.dataset.theme = theme.name; // A theme with one palette has it whatever the mode; both of those are light. root.dataset.mode = !both ? 'light' : theme.mode === 'auto' ? (systemDark && !systemDark.matches ? 'light' : 'dark') : theme.mode; // The tab's icon in the same variant as the logo on the page. const fav = $('#favicon'); if(fav) fav.href = fav.dataset[root.dataset.mode]; const sel = $('#stheme'); if(sel) sel.value = theme.name; const ms = $('#smode'); if(ms) ms.value = theme.mode; const mf = $('#smodefield'); if(mf) mf.hidden = !both; if(save) saveTheme(); } /// Kept a year, for the whole site: the admin page is drawn in it too. function saveTheme(){ document.cookie = `ipx_theme=${theme.name}.${theme.mode}; Path=/; Max-Age=31536000; SameSite=Lax`; } systemDark?.addEventListener?.('change', () => { if(theme.mode === 'auto') setTheme(); }); (() => { const root = document.documentElement; // Without a cookie, the server sent the theme the account kept from before; this browser takes // it as its own, once, so nobody has to choose again. if(root.dataset.choice) return setTheme(root.dataset.theme, root.dataset.choice, !/(^|; )ipx_theme=/.test(document.cookie)); // Nothing on the account either. A theme this browser kept in localStorage, from before that, // becomes its cookie, once. let name: string | null = null, mode: string | null = null; try{ name = localStorage.getItem('ipx.theme'); mode = localStorage.getItem('ipx.mode'); }catch{} if(OLD_THEMES[name]) [name, mode] = OLD_THEMES[name]; setTheme(name ?? undefined, mode ?? undefined, !!name); })();