/** * Unified Theme Manager für MYP Platform * ===================================== * * Konsolidiert alle Dark/Light Mode JavaScript-Implementierungen * Ersetzt: dark-mode.js, verschiedene inline Scripts * * Author: Till Tomczak - MYP Team * Zweck: Einheitliches Theme-Management und Behebung von Inkonsistenzen */ class UnifiedThemeManager { constructor() { // Einheitlicher Storage Key (konsolidiert alle verschiedenen Keys) this.storageKey = 'myp-dark-mode'; this.oldStorageKeys = ['darkMode', 'theme', 'dark-theme']; // Legacy Keys für Migration // DOM Elemente this.htmlElement = document.documentElement; this.toggleButtons = []; this.themeIndicators = []; // Event Callbacks this.callbacks = []; // Debug Modus this.debug = false; // Initialisierung this.init(); // Globale Instanz für Legacy-Kompatibilität window.themeManager = this; } /** * Initialisiert den Theme Manager */ init() { this.log('🎨 Unified Theme Manager initialisiert'); // Legacy Storage Keys migrieren this.migrateLegacyStorage(); // Theme aus Storage laden oder System-Präferenz verwenden this.loadTheme(); // Event Listener registrieren this.setupEventListeners(); // DOM-Elemente suchen und registrieren this.findThemeElements(); // Initial UI aktualisieren this.updateUI(); // CSS Custom Properties setzen this.updateCSSProperties(); } /** * Migriert alte Storage Keys zum einheitlichen System */ migrateLegacyStorage() { for (const oldKey of this.oldStorageKeys) { const oldValue = localStorage.getItem(oldKey); if (oldValue && !localStorage.getItem(this.storageKey)) { // Normalisiere den Wert const isDark = oldValue === 'true' || oldValue === 'dark'; localStorage.setItem(this.storageKey, isDark.toString()); localStorage.removeItem(oldKey); // Alten Key entfernen this.log(`🔄 Migriert ${oldKey}: ${oldValue} → ${this.storageKey}: ${isDark}`); } } } /** * Lädt das Theme aus Storage oder verwendet System-Präferenz */ loadTheme() { let theme = localStorage.getItem(this.storageKey); if (theme === null) { // Keine gespeicherte Präferenz → System-Präferenz verwenden const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; theme = prefersDark ? 'true' : 'false'; this.log(`🌟 Keine gespeicherte Präferenz → System: ${prefersDark ? 'Dark' : 'Light'} Mode`); } const isDark = theme === 'true'; this.setTheme(isDark, false); // Ohne Storage-Update da bereits aus Storage geladen } /** * Event Listener für System-Präferenz-Änderungen und Keyboard-Shortcuts */ setupEventListeners() { // System-Präferenz-Änderungen const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); mediaQuery.addEventListener('change', (e) => { // Nur reagieren wenn keine manuelle Präferenz gesetzt wurde const storedTheme = localStorage.getItem(this.storageKey); if (storedTheme === null) { this.setTheme(e.matches); this.log(`🔄 System-Präferenz geändert: ${e.matches ? 'Dark' : 'Light'} Mode`); } }); // Keyboard Shortcut (Ctrl/Cmd + Shift + D) document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'D') { e.preventDefault(); this.toggle(); this.log('⌨️ Theme per Keyboard-Shortcut umgeschaltet'); } }); // Page Visibility API für Theme-Synchronisation zwischen Tabs document.addEventListener('visibilitychange', () => { if (!document.hidden) { this.syncThemeFromStorage(); } }); // Storage Event für Cross-Tab-Synchronisation window.addEventListener('storage', (e) => { if (e.key === this.storageKey && e.newValue !== null) { const isDark = e.newValue === 'true'; this.setTheme(isDark, false); // Ohne Storage-Update this.log(`🔄 Theme aus anderem Tab synchronisiert: ${isDark ? 'Dark' : 'Light'} Mode`); } }); // CSS-Transition Event für sanfte Übergänge this.htmlElement.addEventListener('transitionend', this.handleTransitionEnd.bind(this)); } /** * Sucht und registriert alle Theme-bezogenen DOM-Elemente */ findThemeElements() { // Toggle Buttons finden const toggleSelectors = [ '[data-theme-toggle]', '.theme-toggle', '.dark-mode-toggle', '#theme-toggle', '#darkModeToggle' ]; this.toggleButtons = []; toggleSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { this.registerToggleButton(element); }); }); // Theme Indikatoren finden const indicatorSelectors = [ '[data-theme-indicator]', '.theme-indicator', '.dark-mode-indicator' ]; this.themeIndicators = []; indicatorSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { this.themeIndicators.push(element); }); }); this.log(`🔍 Gefunden: ${this.toggleButtons.length} Toggle-Buttons, ${this.themeIndicators.length} Indikatoren`); } /** * Registriert einen Toggle-Button */ registerToggleButton(element) { if (this.toggleButtons.includes(element)) return; element.addEventListener('click', (e) => { e.preventDefault(); this.toggle(); }); // Accessibility element.setAttribute('role', 'button'); element.setAttribute('aria-label', 'Theme umschalten'); this.toggleButtons.push(element); } /** * Theme umschalten */ toggle() { const currentTheme = this.isDarkMode(); const newTheme = !currentTheme; this.setTheme(newTheme); this.log(`🔄 Theme umgeschaltet: ${currentTheme ? 'Dark' : 'Light'} → ${newTheme ? 'Dark' : 'Light'} Mode`); // Animation für visuelles Feedback this.triggerToggleAnimation(); } /** * Theme setzen */ setTheme(isDark, updateStorage = true) { const previousTheme = this.isDarkMode(); // HTML Klasse setzen/entfernen this.htmlElement.classList.toggle('dark', isDark); // Storage aktualisieren if (updateStorage) { localStorage.setItem(this.storageKey, isDark.toString()); } // CSS Custom Properties aktualisieren this.updateCSSProperties(); // UI-Elemente aktualisieren this.updateUI(); // Meta Theme-Color aktualisieren this.updateMetaThemeColor(isDark); // Callbacks ausführen this.executeCallbacks(isDark, previousTheme); // Custom Event dispatchen this.dispatchThemeEvent(isDark); this.log(`🎨 Theme gesetzt: ${isDark ? 'Dark' : 'Light'} Mode`); } /** * Aktueller Theme-Status */ isDarkMode() { return this.htmlElement.classList.contains('dark'); } /** * Theme als String */ getTheme() { return this.isDarkMode() ? 'dark' : 'light'; } /** * UI-Elemente aktualisieren */ updateUI() { const isDark = this.isDarkMode(); const themeText = isDark ? 'Dark' : 'Light'; // Toggle Buttons aktualisieren this.toggleButtons.forEach(button => { // Icon aktualisieren const iconElement = button.querySelector('.theme-icon') || button.querySelector('i') || button; if (iconElement) { // Entferne alle Theme-Icon-Klassen iconElement.classList.remove('fa-sun', 'fa-moon', 'fa-lightbulb', 'fa-moon-o'); // Füge entsprechendes Icon hinzu if (isDark) { iconElement.classList.add('fa-sun'); iconElement.title = 'Zu Light Mode wechseln'; } else { iconElement.classList.add('fa-moon'); iconElement.title = 'Zu Dark Mode wechseln'; } } // Text aktualisieren (falls vorhanden) const textElement = button.querySelector('.theme-text'); if (textElement) { textElement.textContent = isDark ? 'Light Mode' : 'Dark Mode'; } // Aria-Label aktualisieren button.setAttribute('aria-label', `Zu ${isDark ? 'Light' : 'Dark'} Mode wechseln`); // Data-Attribute für CSS-Styling button.setAttribute('data-theme', this.getTheme()); }); // Theme Indikatoren aktualisieren this.themeIndicators.forEach(indicator => { indicator.textContent = `${themeText} Mode`; indicator.setAttribute('data-theme', this.getTheme()); }); // Body-Klassen für Legacy-Kompatibilität document.body.classList.toggle('dark-mode', isDark); document.body.classList.toggle('light-mode', !isDark); } /** * CSS Custom Properties aktualisieren */ updateCSSProperties() { const isDark = this.isDarkMode(); // Zusätzliche CSS Properties für JavaScript-basierte Komponenten this.htmlElement.style.setProperty('--theme-mode', isDark ? 'dark' : 'light'); this.htmlElement.style.setProperty('--theme-multiplier', isDark ? '-1' : '1'); // Für Chart.js und andere Libraries this.htmlElement.style.setProperty('--chart-text-color', isDark ? '#ffffff' : '#111827'); this.htmlElement.style.setProperty('--chart-grid-color', isDark ? '#374151' : '#e5e7eb'); } /** * Meta Theme-Color für Mobile Browser aktualisieren */ updateMetaThemeColor(isDark) { let metaThemeColor = document.querySelector('meta[name="theme-color"]'); if (!metaThemeColor) { metaThemeColor = document.createElement('meta'); metaThemeColor.name = 'theme-color'; document.head.appendChild(metaThemeColor); } metaThemeColor.content = isDark ? '#000000' : '#ffffff'; } /** * Theme-Synchronisation aus Storage */ syncThemeFromStorage() { const storedTheme = localStorage.getItem(this.storageKey); if (storedTheme !== null) { const isDark = storedTheme === 'true'; if (this.isDarkMode() !== isDark) { this.setTheme(isDark, false); } } } /** * Toggle-Animation auslösen */ triggerToggleAnimation() { // CSS-Klasse für Animation hinzufügen this.htmlElement.classList.add('theme-transitioning'); // Animation nach kurzer Zeit entfernen setTimeout(() => { this.htmlElement.classList.remove('theme-transitioning'); }, 300); } /** * Transition End Handler */ handleTransitionEnd(event) { if (event.propertyName === 'background-color' && event.target === this.htmlElement) { this.log('🎨 Theme-Transition abgeschlossen'); } } /** * Custom Event dispatchen */ dispatchThemeEvent(isDark) { const event = new CustomEvent('themeChanged', { detail: { theme: this.getTheme(), isDark: isDark, timestamp: Date.now() } }); document.dispatchEvent(event); } /** * Callback registrieren */ onThemeChange(callback) { if (typeof callback === 'function') { this.callbacks.push(callback); } } /** * Callback entfernen */ offThemeChange(callback) { const index = this.callbacks.indexOf(callback); if (index > -1) { this.callbacks.splice(index, 1); } } /** * Alle Callbacks ausführen */ executeCallbacks(isDark, previousTheme) { this.callbacks.forEach(callback => { try { callback(isDark, previousTheme); } catch (error) { console.error('Theme Callback Fehler:', error); } }); } /** * Debug-Logging */ log(message) { if (this.debug || localStorage.getItem('myp-theme-debug') === 'true') { console.log(`[ThemeManager] ${message}`); } } /** * Debug-Modus aktivieren/deaktivieren */ setDebug(enabled) { this.debug = enabled; localStorage.setItem('myp-theme-debug', enabled.toString()); } /** * System-Präferenz abfragen */ getSystemPreference() { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } /** * Theme auf System-Präferenz zurücksetzen */ resetToSystemPreference() { localStorage.removeItem(this.storageKey); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; this.setTheme(prefersDark, false); this.log(`🔄 Theme auf System-Präferenz zurückgesetzt: ${prefersDark ? 'Dark' : 'Light'} Mode`); } /** * Theme-Status für APIs */ getStatus() { return { currentTheme: this.getTheme(), isDarkMode: this.isDarkMode(), systemPreference: this.getSystemPreference(), storageKey: this.storageKey, toggleButtons: this.toggleButtons.length, indicators: this.themeIndicators.length, callbacks: this.callbacks.length }; } } // ===== LEGACY COMPATIBILITY ===== // Globale Funktionen für Legacy-Kompatibilität window.toggleDarkMode = function() { if (window.themeManager) { window.themeManager.toggle(); } }; window.setDarkMode = function(enabled) { if (window.themeManager) { window.themeManager.setTheme(enabled); } }; window.isDarkMode = function() { return window.themeManager ? window.themeManager.isDarkMode() : false; }; // ===== AUTO-INITIALIZATION ===== // Theme Manager automatisch initialisieren wenn DOM bereit ist if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { new UnifiedThemeManager(); }); } else { new UnifiedThemeManager(); } // ===== CSS TRANSITION SUPPORT ===== // CSS für sanfte Theme-Übergänge hinzufügen const style = document.createElement('style'); style.textContent = ` .theme-transitioning, .theme-transitioning * { transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease !important; } .theme-toggle { transition: transform 0.2s ease; } .theme-toggle:hover { transform: scale(1.1); } .theme-toggle:active { transform: scale(0.95); } `; document.head.appendChild(style); console.log('🎨 Unified Theme Manager geladen'); // Export für Module if (typeof module !== 'undefined' && module.exports) { module.exports = UnifiedThemeManager; }