/** * Systades Theme Toggle - Verbessertes Dark/Light Mode Switching * * Dieses Skript verwaltet die Theme-Umschaltung zwischen Light und Dark Mode * mit sanften Übergängen und persistenter Speicherung der Benutzereinstellung. */ document.addEventListener('DOMContentLoaded', () => { // Initialisiere den Theme-Modus basierend auf gespeicherter Einstellung initializeTheme(); // Führe eine Animation beim Umschalten der Themes aus setupThemeTransition(); }); /** * Initialisiere das Theme basierend auf der gespeicherten Benutzereinstellung. * Wenn keine Einstellung gefunden wird, verwende die Systemeinstellung. */ function initializeTheme() { // Prüfe zuerst die gespeicherte Benutzereinstellung const storedTheme = localStorage.getItem('darkMode'); if (storedTheme) { // Verwende die gespeicherte Einstellung const isDarkMode = storedTheme === 'dark'; applyTheme(isDarkMode); updateAlpineJsState(isDarkMode); } else { // Wenn keine Einstellung gefunden wurde, prüfe die Systemeinstellung const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; applyTheme(prefersDarkMode); updateAlpineJsState(prefersDarkMode); // Speichere die initiale Einstellung localStorage.setItem('darkMode', prefersDarkMode ? 'dark' : 'light'); } } /** * Wendet das ausgewählte Theme auf die Seite an * @param {boolean} isDarkMode - Ob der Dark Mode aktiviert werden soll */ function applyTheme(isDarkMode) { // Toggle der 'dark' Klasse auf dem HTML-Element document.documentElement.classList.toggle('dark', isDarkMode); // Meta-Theme-Color für mobile Browser aktualisieren const metaThemeColor = document.querySelector('meta[name="theme-color"]'); if (metaThemeColor) { metaThemeColor.setAttribute( 'content', isDarkMode ? '#111827' : '#f9fafb' ); } // Zusätzliche Stile für sanfte Übergänge document.body.style.transition = 'background-color 0.5s ease, color 0.3s ease'; } /** * Aktualisiert den Dark Mode Status in Alpine.js * @param {boolean} isDarkMode - Ob der Dark Mode aktiviert ist */ function updateAlpineJsState(isDarkMode) { // Wenn Alpine.js verfügbar ist, aktualisiere den State if (window.Alpine) { // Finde alle Alpine Components, die ein darkMode Property haben document.querySelectorAll('[x-data]').forEach(el => { const alpineComponent = Alpine.$data(el); if (alpineComponent && 'darkMode' in alpineComponent) { alpineComponent.darkMode = isDarkMode; } }); } } /** * Richte Event-Listener und Animationen für Themeübergänge ein */ function setupThemeTransition() { // Animationseffekt für Themenwechsel const createRippleEffect = (isDarkMode) => { // Entferne bestehende Ripple-Elemente const existingRipples = document.querySelectorAll('.theme-transition-ripple'); existingRipples.forEach(ripple => ripple.remove()); // Erstelle neues Ripple-Element const ripple = document.createElement('div'); ripple.classList.add('theme-transition-ripple'); // Positioniere das Ripple-Element in der oberen rechten Ecke (wo der Toggle-Button ist) ripple.style.position = 'fixed'; ripple.style.top = '60px'; ripple.style.right = '80px'; ripple.style.width = '10px'; ripple.style.height = '10px'; ripple.style.borderRadius = '50%'; ripple.style.backgroundColor = isDarkMode ? '#111827' : '#f9fafb'; ripple.style.transform = 'scale(0)'; ripple.style.transition = 'transform 1.2s ease-out'; ripple.style.zIndex = '9999'; ripple.style.pointerEvents = 'none'; // Füge das Ripple-Element zum Body hinzu document.body.appendChild(ripple); // Trigger Animation setTimeout(() => { // Berechne die Größe, um den gesamten Bildschirm abzudecken const maxDimension = Math.max(window.innerWidth, window.innerHeight) * 2.5; ripple.style.transform = `scale(${maxDimension})`; // Entferne das Element nach Abschluss der Animation setTimeout(() => { ripple.remove(); }, 1000); }, 50); }; // Event-Listener für den Theme-Toggle-Button const themeToggleButtons = document.querySelectorAll('[data-toggle-theme]'); themeToggleButtons.forEach(button => { button.addEventListener('click', () => { const isDarkMode = document.documentElement.classList.contains('dark'); const newMode = !isDarkMode; // Erstelle den Ripple-Effekt createRippleEffect(newMode); // Wende das neue Theme an applyTheme(newMode); // Aktualisiere Alpine.js State updateAlpineJsState(newMode); // Speichere die Einstellung localStorage.setItem('darkMode', newMode ? 'dark' : 'light'); }); }); // Füge CSS für den Ripple-Effekt hinzu const style = document.createElement('style'); style.textContent = ` .theme-transition-ripple { position: fixed; border-radius: 50%; z-index: 9999; pointer-events: none; transition: transform 1.2s cubic-bezier(0.22, 1, 0.36, 1); } `; document.head.appendChild(style); } /** * Öffentliche API für das Theme-Management */ window.ThemeManager = { /** * Schaltet zwischen Light und Dark Mode um */ toggleDarkMode() { const isDarkMode = document.documentElement.classList.contains('dark'); const newMode = !isDarkMode; // Wende das neue Theme an applyTheme(newMode); // Aktualisiere Alpine.js State updateAlpineJsState(newMode); // Speichere die Einstellung localStorage.setItem('darkMode', newMode ? 'dark' : 'light'); }, /** * Setzt das Theme auf einen bestimmten Modus * @param {boolean} isDarkMode - Ob der Dark Mode aktiviert werden soll */ setDarkMode(isDarkMode) { // Wende das gewünschte Theme an applyTheme(isDarkMode); // Aktualisiere Alpine.js State updateAlpineJsState(isDarkMode); // Speichere die Einstellung localStorage.setItem('darkMode', isDarkMode ? 'dark' : 'light'); } };