/** * @fileoverview Theme Service for Rantii * @author retoor * @description Manages application themes and visual appearance * @keywords theme, dark mode, light mode, appearance, styling */ const THEMES = { dark: { name: 'Dark', class: 'theme-dark' }, light: { name: 'Light', class: 'theme-light' }, black: { name: 'Black', class: 'theme-black' }, white: { name: 'White', class: 'theme-white' }, ocean: { name: 'Ocean', class: 'theme-ocean' }, forest: { name: 'Forest', class: 'theme-forest' }, sunset: { name: 'Sunset', class: 'theme-sunset' } }; class ThemeService { constructor(storageService) { this.storage = storageService; this.currentTheme = 'dark'; this.onThemeChange = null; } init() { const savedTheme = this.storage.getTheme(); if (savedTheme && THEMES[savedTheme]) { this.applyTheme(savedTheme); } else { this.applyTheme(this.detectPreferredTheme()); } this.listenToSystemPreference(); } detectPreferredTheme() { if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return 'light'; } listenToSystemPreference() { if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (!this.storage.has('theme')) { this.applyTheme(e.matches ? 'dark' : 'light'); } }); } } applyTheme(themeName) { const theme = THEMES[themeName]; if (!theme) { return false; } Object.values(THEMES).forEach(t => { document.documentElement.classList.remove(t.class); document.body.classList.remove(t.class); }); document.documentElement.classList.add(theme.class); document.body.classList.add(theme.class); this.currentTheme = themeName; this.storage.setTheme(themeName); const metaThemeColor = document.querySelector('meta[name="theme-color"]'); if (metaThemeColor) { const colors = { dark: '#1a1a2e', light: '#f5f5f7', black: '#000000', white: '#ffffff', ocean: '#0a1929', forest: '#0d1f0d', sunset: '#1f1410' }; metaThemeColor.setAttribute('content', colors[themeName] || '#1a1a2e'); } if (this.onThemeChange) { this.onThemeChange(themeName, theme); } window.dispatchEvent(new CustomEvent('rantii:theme-change', { detail: { theme: themeName, themeData: theme } })); return true; } setTheme(themeName) { return this.applyTheme(themeName); } getTheme() { return this.currentTheme; } getThemeData() { return THEMES[this.currentTheme]; } getAvailableThemes() { return Object.entries(THEMES).map(([key, value]) => ({ id: key, name: value.name })); } toggle() { const currentIndex = Object.keys(THEMES).indexOf(this.currentTheme); const nextIndex = (currentIndex + 1) % Object.keys(THEMES).length; const nextTheme = Object.keys(THEMES)[nextIndex]; return this.applyTheme(nextTheme); } toggleDarkLight() { if (this.currentTheme === 'dark' || this.currentTheme === 'black') { return this.applyTheme('light'); } return this.applyTheme('dark'); } setThemeChangeCallback(callback) { this.onThemeChange = callback; } isDark() { return ['dark', 'black', 'ocean', 'forest', 'sunset'].includes(this.currentTheme); } } export { ThemeService, THEMES };