/** * DataScience Masterclass - Theme Toggle * Supports light/dark mode with system preference detection * Version: 2.0.0 */ (function () { 'use strict'; const STORAGE_KEY = 'ds-masterclass-theme'; const THEMES = ['light', 'dark', 'system']; let currentTheme = 'system'; /** * Initialize theme system */ function init() { loadTheme(); applyTheme(); createToggleButton(); watchSystemPreference(); console.log('🎨 Theme system initialized:', currentTheme); } /** * Load theme preference from storage */ function loadTheme() { const stored = localStorage.getItem(STORAGE_KEY); if (stored && THEMES.includes(stored)) { currentTheme = stored; } } /** * Save theme preference */ function saveTheme() { localStorage.setItem(STORAGE_KEY, currentTheme); } /** * Get effective theme (resolving 'system') */ function getEffectiveTheme() { if (currentTheme === 'system') { return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; } return currentTheme; } /** * Apply theme to document */ function applyTheme() { const effectiveTheme = getEffectiveTheme(); document.documentElement.setAttribute('data-theme', effectiveTheme); // Update meta theme-color let metaTheme = document.querySelector('meta[name="theme-color"]'); if (!metaTheme) { metaTheme = document.createElement('meta'); metaTheme.name = 'theme-color'; document.head.appendChild(metaTheme); } metaTheme.content = effectiveTheme === 'dark' ? '#0d1117' : '#ffffff'; // Dispatch event for other components window.dispatchEvent(new CustomEvent('themechange', { detail: { theme: effectiveTheme } })); } /** * Set theme */ function setTheme(theme) { if (!THEMES.includes(theme)) return; currentTheme = theme; saveTheme(); applyTheme(); updateToggleButton(); } /** * Toggle between light and dark */ function toggle() { const effective = getEffectiveTheme(); setTheme(effective === 'dark' ? 'light' : 'dark'); } /** * Cycle through all themes */ function cycle() { const currentIndex = THEMES.indexOf(currentTheme); const nextIndex = (currentIndex + 1) % THEMES.length; setTheme(THEMES[nextIndex]); } /** * Watch for system preference changes */ function watchSystemPreference() { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); mediaQuery.addEventListener('change', () => { if (currentTheme === 'system') { applyTheme(); } }); } /** * Create theme toggle button */ function createToggleButton() { // Only create if container exists const container = document.getElementById('theme-toggle-container'); if (!container) return; const button = document.createElement('button'); button.id = 'theme-toggle'; button.className = 'theme-toggle'; button.setAttribute('aria-label', 'Toggle theme'); button.setAttribute('title', 'Toggle light/dark mode'); button.innerHTML = `
🌙 ☀️
`; button.addEventListener('click', toggle); container.appendChild(button); updateToggleButton(); } /** * Update toggle button state */ function updateToggleButton() { const button = document.getElementById('theme-toggle'); if (!button) return; const effective = getEffectiveTheme(); button.dataset.theme = effective; button.setAttribute('aria-pressed', effective === 'light'); } /** * Add inline styles for toggle (ensures it works before CSS loads) */ function addInlineStyles() { const style = document.createElement('style'); style.textContent = ` .theme-toggle { position: relative; width: 64px; height: 32px; background: var(--color-bg-tertiary, #1a1f2e); border: 1px solid var(--color-border-default, #30363d); border-radius: 16px; cursor: pointer; transition: all 0.3s ease; padding: 0; } .theme-toggle:hover { border-color: var(--color-border-hover, #8b949e); } .theme-toggle-track { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: space-between; padding: 0 6px; } .theme-icon { font-size: 14px; transition: opacity 0.3s ease; } .theme-toggle-thumb { position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; background: var(--color-accent-ml, #00d4ff); border-radius: 50%; transition: transform 0.3s ease, background 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } [data-theme="light"] .theme-toggle-thumb { transform: translateX(32px); background: var(--color-warning, #f39c12); } [data-theme="dark"] .theme-icon-light { opacity: 0.4; } [data-theme="light"] .theme-icon-dark { opacity: 0.4; } /* Progress toast styles */ .progress-toast { position: fixed; bottom: 20px; right: 20px; display: flex; align-items: center; gap: 10px; padding: 12px 20px; background: var(--color-bg-elevated, #21262d); border: 1px solid var(--color-success, #2ecc71); border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); z-index: 1000; opacity: 0; transform: translateY(20px); transition: all 0.3s ease; } .progress-toast.show { opacity: 1; transform: translateY(0); } .toast-icon { font-size: 18px; } .toast-message { font-size: 14px; font-weight: 500; color: var(--color-text-primary, #e4e6eb); } /* Search modal additional styles */ .search-modal { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: flex-start; justify-content: center; padding-top: 15vh; z-index: 500; opacity: 0; visibility: hidden; transition: all 0.2s ease; } .search-modal.open { opacity: 1; visibility: visible; } .search-modal-content { width: 100%; max-width: 600px; background: var(--color-bg-secondary, #161b22); border: 1px solid var(--color-border-default, #30363d); border-radius: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); overflow: hidden; } .search-modal-header { padding: 16px; border-bottom: 1px solid var(--color-border-default, #30363d); } .search-results { max-height: 400px; overflow-y: auto; } .search-empty, .search-no-results { padding: 40px 20px; text-align: center; color: var(--color-text-secondary, #8b949e); } .search-hints { margin-top: 16px; } .search-hint { font-size: 13px; color: var(--color-text-muted, #6e7681); } .search-result-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer; transition: background 0.15s ease; } .search-result-item:hover, .search-result-item.selected { background: var(--color-bg-tertiary, #1a1f2e); } .search-result-icon { font-size: 20px; width: 32px; text-align: center; } .search-result-info { flex: 1; min-width: 0; } .search-result-title { font-weight: 500; color: var(--color-text-primary, #e4e6eb); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .search-result-title mark { background: rgba(0, 212, 255, 0.3); color: inherit; border-radius: 2px; padding: 0 2px; } .search-result-module { font-size: 12px; color: var(--color-text-muted, #6e7681); } .search-result-type { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-muted, #6e7681); padding: 2px 8px; background: var(--color-bg-tertiary, #1a1f2e); border-radius: 4px; } `; document.head.appendChild(style); } // Expose API window.DSTheme = { init, setTheme, getTheme: () => currentTheme, getEffectiveTheme, toggle, cycle }; // Add inline styles immediately addInlineStyles(); // Initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();