/** * Arod Theme Toggle Component * A theme switcher for light/dark/system modes * * Usage: * * * Note: This component manages the theme at the document level * by setting a data-theme attribute on or toggling a class. * It also persists the preference in localStorage. */ // ============================================ // AROD-THEME - Theme Toggle Component // ============================================ class ArodTheme extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.themes = ['system', 'light', 'dark', 'high-contrast', 'colorblind', 'sepia']; this.currentTheme = 'system'; } connectedCallback() { this.loadTheme(); this.render(); this.setupEventListeners(); this.applyTheme(); this.watchSystemTheme(); } disconnectedCallback() { this.unwatchSystemTheme(); } render() { this.shadowRoot.innerHTML = ` `; } setupEventListeners() { const toggle = this.shadowRoot.querySelector('.theme-toggle'); toggle.addEventListener('click', () => { this.cycleTheme(); }); } cycleTheme() { const currentIndex = this.themes.indexOf(this.currentTheme); const nextIndex = (currentIndex + 1) % this.themes.length; const nextTheme = this.themes[nextIndex]; this.setTheme(nextTheme); } loadTheme() { // Load from localStorage or default to system const saved = localStorage.getItem('arod-theme'); this.currentTheme = saved || 'system'; } setTheme(theme) { this.currentTheme = theme; localStorage.setItem('arod-theme', theme); this.applyTheme(); this.updateUI(); // Dispatch event for other components to react this.dispatchEvent(new CustomEvent('theme-change', { detail: { theme }, bubbles: true, composed: true })); } applyTheme() { const root = document.documentElement; let effectiveTheme = this.currentTheme; if (this.currentTheme === 'system') { effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } // Set data attribute on html element root.setAttribute('data-theme', effectiveTheme); // Also set on this component for styling this.setAttribute('data-theme', effectiveTheme); // Update all arod components document.querySelectorAll('arod-menu, article-sidenotes').forEach(el => { el.setAttribute('theme', effectiveTheme); }); this.updateUI(); } updateUI() { const icon = this.shadowRoot.querySelector('.theme-icon'); const toggle = this.shadowRoot.querySelector('.theme-toggle'); const tooltip = this.shadowRoot.querySelector('.tooltip'); // Clear existing icon with animation const existingIcons = icon.querySelectorAll('svg'); existingIcons.forEach(svg => { svg.classList.remove('entering'); svg.classList.add('exiting'); }); // Add new icon after a short delay for smooth transition setTimeout(() => { let iconSvg; let ariaLabel; let themeName; switch (this.currentTheme) { case 'system': // Hybrid icon for system mode iconSvg = ` `; ariaLabel = 'Toggle theme (System)'; themeName = 'System'; break; case 'light': // Sun icon for light mode iconSvg = ` `; ariaLabel = 'Toggle theme (Light)'; themeName = 'Light'; break; case 'dark': // Moon icon for dark mode iconSvg = ` `; ariaLabel = 'Toggle theme (Dark)'; themeName = 'Dark'; break; case 'high-contrast': // Eye icon for high contrast iconSvg = ` `; ariaLabel = 'Toggle theme (High Contrast)'; themeName = 'High Contrast'; break; case 'colorblind': // Palette icon for colorblind mode iconSvg = ` `; ariaLabel = 'Toggle theme (Colorblind)'; themeName = 'Colorblind'; break; case 'sepia': // Book icon for sepia/reading mode iconSvg = ` `; ariaLabel = 'Toggle theme (Sepia)'; themeName = 'Sepia'; break; default: // Default to system icon iconSvg = ` `; ariaLabel = 'Toggle theme'; themeName = 'System'; } icon.innerHTML = iconSvg; toggle.setAttribute('aria-label', ariaLabel); tooltip.textContent = themeName; }, 150); } getEffectiveTheme() { if (this.currentTheme === 'system') { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } return this.currentTheme; } watchSystemTheme() { this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); this.systemThemeHandler = () => { if (this.currentTheme === 'system') { this.applyTheme(); } }; this.mediaQuery.addEventListener('change', this.systemThemeHandler); } unwatchSystemTheme() { if (this.mediaQuery && this.systemThemeHandler) { this.mediaQuery.removeEventListener('change', this.systemThemeHandler); } } } // Register component customElements.define('arod-theme', ArodTheme);