// Theme override utilities - shared across apps // User preference or URL hash can force light/dark mode const TRANSITION_ID = 'theme-transition' const STORAGE_KEY = 'paskia-theme' /** Apply theme by setting class on documentElement */ export function applyTheme(theme, element = document.documentElement, animate = false) { // Add temporary transition for smooth theme change if (animate) { let transitionStyle = document.getElementById(TRANSITION_ID) if (!transitionStyle) { transitionStyle = document.createElement('style') transitionStyle.id = TRANSITION_ID transitionStyle.textContent = '*, *::before, *::after { transition: background-color 0.3s, color 0.3s, border-color 0.3s, box-shadow 0.3s !important; }' document.head.appendChild(transitionStyle) } setTimeout(() => document.getElementById(TRANSITION_ID)?.remove(), 350) } // If no explicit theme, check system preference const isDark = theme === 'dark' || (theme !== 'light' && matchMedia('(prefers-color-scheme:dark)').matches) element.classList.toggle('dark', isDark) } /** Get theme from localStorage cache */ export function getCachedTheme() { return localStorage.getItem(STORAGE_KEY) || '' } /** Cache theme in localStorage */ export function setCachedTheme(theme) { if (theme) localStorage.setItem(STORAGE_KEY, theme) else localStorage.removeItem(STORAGE_KEY) } /** Initialize theme from user preference (with localStorage cache for fast load) */ export function initThemeFromCache() { applyTheme(getCachedTheme()) } /** Theme default from the URL hash (restricted iframe/forward pages only) */ function getHashTheme() { const theme = new URLSearchParams(window.location.hash.slice(1)).get('theme') return theme === 'light' || theme === 'dark' ? theme : '' } /** Update theme from session context (call after login/session load) */ export function updateThemeFromSession(ctx, animate = false) { const theme = ctx?.user?.theme || '' // Always keep the cache in sync with the profile: empty override clears it // so stale values never mask future server-provided themes. setCachedTheme(theme) // Without a profile override, stay consistent with the initial paint: a // theme parameter on the URL (e.g. host page color scheme injected by // paskia-js) remains in effect before the browser/desktop default. applyTheme(theme || getHashTheme(), document.documentElement, animate) }