// Theme & color system with "undefined" tokens and safe fallbacks (() => { const DEFAULT_PRIMARY = [59, 130, 246]; // blue-500 const DEFAULT_SECONDARY = [236, 72, 153]; // pink-500 const docEl = document.documentElement; function toRgbArray(input) { // Accepts: "59 130 246", "59,130,246", "rgb(59 130 246)", "#3b82f6" if (!input) return null; const trimmed = String(input).trim(); // Hex (#RRGGBB or #RGB) if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(trimmed)) { let hex = trimmed.replace('#', ''); if (hex.length === 3) { hex = hex.split('').map(c => c + c).join(''); } const int = parseInt(hex, 16); return [(int >> 16) & 255, (int >> 8) & 255, int & 255]; } // rgb(...) or space-separated const rgbMatch = trimmed.match(/^rgb\(\s*([0-9]{1,3})\s*[, ]\s*([0-9]{1,3})\s*[, ]\s*([0-9]{1,3})\s*\)$/i) || trimmed.match(/^([0-9]{1,3})\s+([0-9]{1,3})\s+([0-9]{1,3})$/); if (rgbMatch) { const r = Math.max(0, Math.min(255, parseInt(rgbMatch[1], 10))); const g = Math.max(0, Math.min(255, parseInt(rgbMatch[2], 10))); const b = Math.max(0, Math.min(255, parseInt(rgbMatch[3], 10))); return [r, g, b]; } // Comma-separated "R,G,B" const csv = trimmed.split(',').map(n => parseInt(n, 10)); if (csv.length === 3 && csv.every(n => Number.isFinite(n))) { return csv.map(n => Math.max(0, Math.min(255, n))); } return null; } function setColorVar(name, rgbArr) { const val = rgbArr ? `${rgbArr[0]} ${rgbArr[1]} ${rgbArr[2]}` : ''; docEl.style.setProperty(name, val); } function applyPrimaryColor(input) { const arr = toRgbArray(input) || DEFAULT_PRIMARY; setColorVar('--color-primary', arr); try { localStorage.setItem('primary', arr.join(' ')); } catch {} return arr; } function applySecondaryColor(input) { const arr = toRgbArray(input) || DEFAULT_SECONDARY; setColorVar('--color-secondary', arr); try { localStorage.setItem('secondary', arr.join(' ')); } catch {} return arr; } function applyThemeMode(mode) { // Accepts "light" or "dark" const m = mode === 'dark' ? 'dark' : 'light'; docEl.setAttribute('data-theme', m); try { localStorage.setItem('theme', m); } catch {} return m; } function currentTheme() { const m = docEl.getAttribute('data-theme') || 'light'; return m; } function toggleTheme() { const next = currentTheme() === 'dark' ? 'light' : 'dark'; applyThemeMode(next); } function boot() { // Apply theme preference from URL or localStorage or default to light ("undefined mode") const params = new URLSearchParams(location.search); const themeFromUrl = params.get('mode') || params.get('theme'); const theme = themeFromUrl === 'dark' ? 'dark' : 'light'; applyThemeMode(theme); // Apply colors from URL -> localStorage -> defaults const primaryFromUrl = params.get('primary') || params.get('primaryColor') || params.get('p'); const secondaryFromUrl = params.get('secondary') || params.get('secondaryColor') || params.get('s'); let primary, secondary; if (primaryFromUrl) { primary = applyPrimaryColor(primaryFromUrl); } else { try { const stored = localStorage.getItem('primary'); if (stored) primary = applyPrimaryColor(stored); } catch {} if (!primary) primary = applyPrimaryColor(null); } if (secondaryFromUrl) { secondary = applySecondaryColor(secondaryFromUrl); } else { try { const stored = localStorage.getItem('secondary'); if (stored) secondary = applySecondaryColor(stored); } catch {} if (!secondary) secondary = applySecondaryColor(null); } // UI hooks const themeToggle = document.getElementById('themeToggle'); if (themeToggle) { themeToggle.addEventListener('click', () => { toggleTheme(); }); } const applyBtn = document.getElementById('applyColorsBtn'); const primaryInput = document.getElementById('primaryColor'); const secondaryInput = document.getElementById('secondaryColor'); if (applyBtn && primaryInput && secondaryInput) { // Initialize inputs with current values try { const sp = getComputedStyle(docEl).getPropertyValue('--color-primary').trim() || DEFAULT_PRIMARY.join(' '); const ss = getComputedStyle(docEl).getPropertyValue('--color-secondary').trim() || DEFAULT_SECONDARY.join(' '); primaryInput.value = sp; secondaryInput.value = ss; } catch {} applyBtn.addEventListener('click', () => { applyPrimaryColor(primaryInput.value); applySecondaryColor(secondaryInput.value); }); } // Expose a tiny API for integration window.ExcailTheme = { setPrimary: applyPrimaryColor, setSecondary: applySecondaryColor, setMode: applyThemeMode, toggleTheme, getMode: currentTheme, }; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot, { once: true }); } else { boot(); } })();