/** * EasyStream Advanced Theme Switcher * Smooth theme transitions with system preference detection * Version: 2.0 */ class ThemeSwitcher { constructor(options = {}) { this.options = { storageKey: 'easystream-theme', colorStorageKey: 'easystream-color', transitionDuration: 300, detectSystemPreference: true, ...options }; this.themes = { light: ['blue', 'red', 'cyan', 'green', 'orange', 'pink', 'purple'], dark: ['darkblue', 'darkred', 'darkcyan', 'darkgreen', 'darkorange', 'darkpink', 'darkpurple'] }; this.currentTheme = null; this.currentColor = null; this.systemPreference = null; this.init(); } /** * Initialize theme switcher */ init() { // Detect system preference if (this.options.detectSystemPreference) { this.detectSystemPreference(); this.watchSystemPreference(); } // Load saved theme or use default const savedTheme = this.getSavedTheme(); const savedColor = this.getSavedColor(); if (savedTheme && savedColor) { this.applyTheme(savedTheme, savedColor, false); } else if (this.systemPreference) { // Use system preference const defaultColor = 'blue'; const theme = this.systemPreference === 'dark' ? `dark${defaultColor}` : defaultColor; this.applyTheme(this.systemPreference, defaultColor, false); } else { // Default to light blue this.applyTheme('light', 'blue', false); } // Setup UI controls if they exist this.setupControls(); } /** * Detect system color scheme preference */ detectSystemPreference() { if (window.matchMedia) { const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); this.systemPreference = darkModeQuery.matches ? 'dark' : 'light'; } } /** * Watch for system preference changes */ watchSystemPreference() { if (window.matchMedia) { const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); darkModeQuery.addEventListener('change', (e) => { this.systemPreference = e.matches ? 'dark' : 'light'; // Only auto-switch if user hasn't manually set a theme const savedTheme = this.getSavedTheme(); if (!savedTheme || savedTheme === 'auto') { this.applySystemTheme(); } this.dispatchEvent('system-preference-change', { preference: this.systemPreference }); }); } } /** * Apply system theme based on preference */ applySystemTheme() { const color = this.currentColor || 'blue'; const theme = this.systemPreference === 'dark' ? `dark${color}` : color; this.applyTheme(this.systemPreference, color, true); } /** * Get saved theme from storage */ getSavedTheme() { try { return localStorage.getItem(this.options.storageKey); } catch (e) { return null; } } /** * Get saved color from storage */ getSavedColor() { try { return localStorage.getItem(this.options.colorStorageKey); } catch (e) { return null; } } /** * Save theme to storage */ saveTheme(mode, color) { try { localStorage.setItem(this.options.storageKey, mode); localStorage.setItem(this.options.colorStorageKey, color); } catch (e) { console.warn('Failed to save theme preference', e); } } /** * Apply theme with smooth transition * @param {string} mode - 'light' or 'dark' * @param {string} color - color name (blue, red, etc.) * @param {boolean} animate - whether to animate the transition */ applyTheme(mode, color, animate = true) { const fullTheme = mode === 'dark' ? `dark${color}` : color; // Prevent unnecessary updates if (this.currentTheme === mode && this.currentColor === color) { return; } const previousMode = this.currentTheme; this.currentTheme = mode; this.currentColor = color; // Add transition class for smooth animation if (animate) { this.enableTransitions(); } // Update body/html data attribute document.documentElement.setAttribute('data-theme', fullTheme); document.body.setAttribute('data-theme', fullTheme); // Update meta theme-color for mobile browsers this.updateMetaThemeColor(); // Save preference this.saveTheme(mode, color); // Remove transition class after animation if (animate) { setTimeout(() => { this.disableTransitions(); }, this.options.transitionDuration); } // Update UI controls this.updateControls(); // Dispatch custom event this.dispatchEvent('theme-change', { mode, color, fullTheme, previousMode }); } /** * Enable smooth transitions during theme switch */ enableTransitions() { const style = document.createElement('style'); style.id = 'theme-transition-styles'; style.textContent = ` *, *::before, *::after { transition: background-color ${this.options.transitionDuration}ms ease, color ${this.options.transitionDuration}ms ease, border-color ${this.options.transitionDuration}ms ease, box-shadow ${this.options.transitionDuration}ms ease !important; } /* Disable transitions for animations that shouldn't be affected */ .no-theme-transition, [class*="animate"], .video-js, .spinner { transition: none !important; } `; document.head.appendChild(style); } /** * Disable transitions after theme switch */ disableTransitions() { const style = document.getElementById('theme-transition-styles'); if (style) { style.remove(); } } /** * Update meta theme-color for mobile browsers */ updateMetaThemeColor() { let themeColorMeta = document.querySelector('meta[name="theme-color"]'); if (!themeColorMeta) { themeColorMeta = document.createElement('meta'); themeColorMeta.name = 'theme-color'; document.head.appendChild(themeColorMeta); } // Color mapping const colors = { blue: '#06a2cb', red: '#dd1e2f', cyan: '#00997a', green: '#199900', orange: '#f28410', pink: '#ec7ab9', purple: '#b25c8b' }; const bgColors = { light: '#ffffff', dark: '#121212' }; // Use primary color for theme const primaryColor = colors[this.currentColor] || colors.blue; const bgColor = bgColors[this.currentTheme] || bgColors.light; themeColorMeta.content = this.currentTheme === 'dark' ? bgColor : primaryColor; } /** * Toggle between light and dark mode */ toggleMode() { const newMode = this.currentTheme === 'light' ? 'dark' : 'light'; this.applyTheme(newMode, this.currentColor, true); } /** * Set color theme * @param {string} color - color name */ setColor(color) { if (!this.themes.light.includes(color) && !this.themes.dark.includes(color.replace('dark', ''))) { console.warn(`Invalid color: ${color}`); return; } const baseColor = color.replace('dark', ''); this.applyTheme(this.currentTheme, baseColor, true); } /** * Setup UI controls */ setupControls() { // Theme toggle button const toggleBtn = document.getElementById('theme-toggle'); if (toggleBtn) { toggleBtn.addEventListener('click', () => this.toggleMode()); } // Color picker buttons const colorBtns = document.querySelectorAll('[data-color-theme]'); colorBtns.forEach(btn => { btn.addEventListener('click', () => { const color = btn.getAttribute('data-color-theme'); this.setColor(color); }); }); // Auto theme switch (follow system) const autoSwitch = document.getElementById('theme-auto'); if (autoSwitch) { autoSwitch.addEventListener('change', (e) => { if (e.target.checked) { this.applySystemTheme(); } }); } } /** * Update UI controls to reflect current theme */ updateControls() { // Update toggle button const toggleBtn = document.getElementById('theme-toggle'); if (toggleBtn) { const icon = toggleBtn.querySelector('i, .icon'); if (icon) { if (this.currentTheme === 'dark') { icon.className = 'icon-sun'; toggleBtn.setAttribute('aria-label', 'Switch to light mode'); } else { icon.className = 'icon-moon'; toggleBtn.setAttribute('aria-label', 'Switch to dark mode'); } } } // Update color buttons const colorBtns = document.querySelectorAll('[data-color-theme]'); colorBtns.forEach(btn => { const color = btn.getAttribute('data-color-theme'); if (color === this.currentColor) { btn.classList.add('active'); btn.setAttribute('aria-pressed', 'true'); } else { btn.classList.remove('active'); btn.setAttribute('aria-pressed', 'false'); } }); } /** * Get current theme info */ getCurrentTheme() { return { mode: this.currentTheme, color: this.currentColor, fullTheme: this.currentTheme === 'dark' ? `dark${this.currentColor}` : this.currentColor, systemPreference: this.systemPreference }; } /** * Dispatch custom event */ dispatchEvent(eventName, detail) { const event = new CustomEvent(`easystream:${eventName}`, { detail, bubbles: true }); document.dispatchEvent(event); } /** * Create theme picker UI * @returns {HTMLElement} */ static createThemePicker() { const container = document.createElement('div'); container.className = 'theme-picker'; container.setAttribute('role', 'toolbar'); container.setAttribute('aria-label', 'Theme controls'); container.innerHTML = `

Appearance

Color
`; return container; } } // Auto-initialize on page load if (typeof window !== 'undefined') { window.addEventListener('DOMContentLoaded', () => { window.themeSwitcher = new ThemeSwitcher(); // Make it globally accessible window.EasyStream = window.EasyStream || {}; window.EasyStream.ThemeSwitcher = ThemeSwitcher; }); } // Export for module systems if (typeof module !== 'undefined' && module.exports) { module.exports = ThemeSwitcher; }