Spaces:
Sleeping
Sleeping
| /** | |
| * MorphGuard Theme Picker Component | |
| * | |
| * A user interface for selecting and customizing themes, | |
| * with visualization of theme colors and preview functionality. | |
| */ | |
| class ThemePicker { | |
| constructor(containerSelector, options = {}) { | |
| this.container = document.querySelector(containerSelector); | |
| if (!this.container) { | |
| console.error(`Theme picker container not found: ${containerSelector}`); | |
| return; | |
| } | |
| // Default options | |
| this.options = { | |
| showColorSwatches: true, | |
| showPreview: true, | |
| allowCustomThemes: true, | |
| enableRealTimePreview: true, | |
| ...options | |
| }; | |
| // Get theme manager instance | |
| this.themeManager = window.morphGuardTheme; | |
| if (!this.themeManager) { | |
| console.error('Theme manager not found. Ensure dynamic_theme.js is loaded first.'); | |
| return; | |
| } | |
| // State | |
| this.selectedTheme = this.themeManager.currentTheme; | |
| this.customTheme = { ...this.themeManager.themes.light.variables }; | |
| this.previousTheme = null; | |
| // Initialize component | |
| this.init(); | |
| } | |
| /** | |
| * Initialize the theme picker component | |
| */ | |
| init() { | |
| // Create the component markup | |
| this.render(); | |
| // Attach event listeners | |
| this.attachEventListeners(); | |
| // Update display based on current theme | |
| this.updateDisplay(); | |
| } | |
| /** | |
| * Render the theme picker component | |
| */ | |
| render() { | |
| // Create container structure | |
| this.container.innerHTML = ` | |
| <div class="theme-picker"> | |
| <div class="theme-picker-header"> | |
| <h3>Theme Settings</h3> | |
| <div class="theme-context-info"></div> | |
| </div> | |
| <div class="theme-selection"> | |
| <label for="theme-select">Select Theme:</label> | |
| <select id="theme-select" class="theme-selector"> | |
| ${this.getThemeOptions()} | |
| </select> | |
| <div class="theme-preview-container"> | |
| ${this.options.showPreview ? ` | |
| <div class="theme-preview"> | |
| <h4>Preview</h4> | |
| <div class="theme-preview-content"> | |
| <div class="preview-card"> | |
| <h5>Sample Card</h5> | |
| <p>This is how content will appear with the selected theme.</p> | |
| <button class="preview-button">Button</button> | |
| </div> | |
| <div class="preview-form"> | |
| <input type="text" placeholder="Text input" class="preview-input"> | |
| <div class="preview-alert success">Success message</div> | |
| <div class="preview-alert error">Error message</div> | |
| </div> | |
| </div> | |
| </div> | |
| ` : ''} | |
| </div> | |
| </div> | |
| ${this.options.showColorSwatches ? ` | |
| <div class="theme-colors"> | |
| <h4>Theme Colors</h4> | |
| <div class="color-swatches"> | |
| ${this.getColorSwatches()} | |
| </div> | |
| </div> | |
| ` : ''} | |
| ${this.options.allowCustomThemes ? ` | |
| <div class="custom-theme-section"> | |
| <h4>Customize Theme</h4> | |
| <p>Modify colors to create your own custom theme.</p> | |
| <div class="custom-theme-controls"> | |
| <div class="color-picker-group"> | |
| <label for="primary-color">Primary Color:</label> | |
| <input type="color" id="primary-color" name="primary-color" value="${this.getColorValue('--accent-primary')}"> | |
| </div> | |
| <div class="color-picker-group"> | |
| <label for="background-color">Background:</label> | |
| <input type="color" id="background-color" name="background-color" value="${this.getColorValue('--bg-primary')}"> | |
| </div> | |
| <div class="color-picker-group"> | |
| <label for="text-color">Text Color:</label> | |
| <input type="color" id="text-color" name="text-color" value="${this.getColorValue('--text-primary')}"> | |
| </div> | |
| <div class="color-picker-group"> | |
| <label for="success-color">Success Color:</label> | |
| <input type="color" id="success-color" name="success-color" value="${this.getColorValue('--success')}"> | |
| </div> | |
| <div class="color-picker-group"> | |
| <label for="error-color">Error Color:</label> | |
| <input type="color" id="error-color" name="error-color" value="${this.getColorValue('--error')}"> | |
| </div> | |
| </div> | |
| <div class="custom-theme-actions"> | |
| <button id="apply-custom-theme" class="btn">Apply Custom Theme</button> | |
| <button id="reset-custom-theme" class="btn btn-secondary">Reset</button> | |
| <button id="save-custom-theme" class="btn">Save as Custom Theme</button> | |
| </div> | |
| </div> | |
| ` : ''} | |
| <div class="theme-preferences"> | |
| <h4>Theme Preferences</h4> | |
| <div class="preference-group"> | |
| <label for="auto-detect-time"> | |
| <input type="checkbox" id="auto-detect-time" ${this.themeManager.options.autoDetectTime ? 'checked' : ''}> | |
| Automatically switch theme based on time of day | |
| </label> | |
| </div> | |
| <div class="preference-group"> | |
| <label for="transitions-enabled"> | |
| <input type="checkbox" id="transitions-enabled" ${this.themeManager.options.enableAnimations ? 'checked' : ''}> | |
| Enable smooth theme transitions | |
| </label> | |
| </div> | |
| <div class="preference-group"> | |
| <label for="high-contrast-enabled"> | |
| <input type="checkbox" id="high-contrast-enabled" ${this.themeManager.options.enableHighContrast ? 'checked' : ''}> | |
| Show high contrast theme option | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| // Add styles if not already present | |
| if (!document.getElementById('theme-picker-styles')) { | |
| const style = document.createElement('style'); | |
| style.id = 'theme-picker-styles'; | |
| style.textContent = this.getStyles(); | |
| document.head.appendChild(style); | |
| } | |
| } | |
| /** | |
| * Get theme options HTML for the select dropdown | |
| */ | |
| getThemeOptions() { | |
| const themes = this.themeManager.getAvailableThemes(); | |
| return themes.map(theme => { | |
| return `<option value="${theme.id}" ${theme.id === this.selectedTheme ? 'selected' : ''}> | |
| ${theme.name} | |
| </option>`; | |
| }).join(''); | |
| } | |
| /** | |
| * Get color swatches HTML | |
| */ | |
| getColorSwatches() { | |
| const colorVars = [ | |
| { name: 'Primary Background', var: '--bg-primary' }, | |
| { name: 'Secondary Background', var: '--bg-secondary' }, | |
| { name: 'Primary Text', var: '--text-primary' }, | |
| { name: 'Primary Accent', var: '--accent-primary' }, | |
| { name: 'Secondary Accent', var: '--accent-secondary' }, | |
| { name: 'Success', var: '--success' }, | |
| { name: 'Warning', var: '--warning' }, | |
| { name: 'Error', var: '--error' }, | |
| { name: 'Info', var: '--info' } | |
| ]; | |
| return colorVars.map(color => { | |
| return ` | |
| <div class="color-swatch-item"> | |
| <div class="color-swatch" style="background-color: var(${color.var});" data-color-var="${color.var}"></div> | |
| <div class="color-swatch-label">${color.name}</div> | |
| </div> | |
| `; | |
| }).join(''); | |
| } | |
| /** | |
| * Get CSS color value for a variable | |
| */ | |
| getColorValue(variable) { | |
| const computedStyle = getComputedStyle(document.documentElement); | |
| return computedStyle.getPropertyValue(variable).trim() || '#ffffff'; | |
| } | |
| /** | |
| * Get CSS styles for the theme picker | |
| */ | |
| getStyles() { | |
| return ` | |
| .theme-picker { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background-color: var(--card-bg); | |
| border-radius: var(--radius-lg); | |
| box-shadow: var(--shadow-md); | |
| padding: var(--spacing-lg); | |
| max-width: 800px; | |
| margin: 0 auto; | |
| } | |
| .theme-picker-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: var(--spacing-lg); | |
| } | |
| .theme-picker-header h3 { | |
| margin: 0; | |
| } | |
| .theme-context-info { | |
| font-size: var(--font-size-sm); | |
| color: var(--text-secondary); | |
| } | |
| .theme-selection { | |
| margin-bottom: var(--spacing-lg); | |
| } | |
| .theme-selector { | |
| width: 100%; | |
| padding: var(--spacing-sm); | |
| margin-bottom: var(--spacing-md); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| background-color: var(--bg-secondary); | |
| color: var(--text-primary); | |
| } | |
| .theme-preview-container { | |
| margin-top: var(--spacing-md); | |
| } | |
| .theme-preview { | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| padding: var(--spacing-md); | |
| margin-bottom: var(--spacing-lg); | |
| } | |
| .theme-preview h4 { | |
| margin-top: 0; | |
| margin-bottom: var(--spacing-md); | |
| } | |
| .theme-preview-content { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--spacing-md); | |
| } | |
| .preview-card { | |
| background-color: var(--card-bg); | |
| padding: var(--spacing-md); | |
| border-radius: var(--radius-md); | |
| box-shadow: var(--shadow-sm); | |
| flex: 1; | |
| min-width: 200px; | |
| } | |
| .preview-card h5 { | |
| margin-top: 0; | |
| color: var(--text-primary); | |
| } | |
| .preview-card p { | |
| color: var(--text-secondary); | |
| } | |
| .preview-button { | |
| background-color: var(--accent-primary); | |
| color: white; | |
| border: none; | |
| padding: var(--spacing-sm) var(--spacing-md); | |
| border-radius: var(--radius-md); | |
| cursor: pointer; | |
| } | |
| .preview-form { | |
| flex: 1; | |
| min-width: 200px; | |
| } | |
| .preview-input { | |
| width: 100%; | |
| padding: var(--spacing-sm); | |
| margin-bottom: var(--spacing-md); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| background-color: var(--bg-secondary); | |
| color: var(--text-primary); | |
| } | |
| .preview-alert { | |
| padding: var(--spacing-sm); | |
| border-radius: var(--radius-md); | |
| margin-bottom: var(--spacing-sm); | |
| } | |
| .preview-alert.success { | |
| background-color: var(--detection-safe); | |
| color: var(--success); | |
| } | |
| .preview-alert.error { | |
| background-color: var(--detection-morph); | |
| color: var(--error); | |
| } | |
| .theme-colors { | |
| margin-bottom: var(--spacing-lg); | |
| } | |
| .color-swatches { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--spacing-md); | |
| } | |
| .color-swatch-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| margin-bottom: var(--spacing-sm); | |
| } | |
| .color-swatch { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--border-color); | |
| margin-bottom: var(--spacing-xs); | |
| } | |
| .color-swatch-label { | |
| font-size: var(--font-size-xs); | |
| color: var(--text-secondary); | |
| text-align: center; | |
| } | |
| .custom-theme-section { | |
| margin-bottom: var(--spacing-lg); | |
| padding: var(--spacing-md); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| } | |
| .custom-theme-controls { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--spacing-md); | |
| margin-bottom: var(--spacing-md); | |
| } | |
| .color-picker-group { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .color-picker-group label { | |
| margin-bottom: var(--spacing-xs); | |
| font-size: var(--font-size-sm); | |
| } | |
| .color-picker-group input[type="color"] { | |
| width: 50px; | |
| height: 30px; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-sm); | |
| background-color: var(--bg-secondary); | |
| } | |
| .custom-theme-actions { | |
| display: flex; | |
| gap: var(--spacing-sm); | |
| margin-top: var(--spacing-md); | |
| } | |
| .theme-preferences { | |
| padding: var(--spacing-md); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| } | |
| .preference-group { | |
| margin-bottom: var(--spacing-sm); | |
| } | |
| .preference-group label { | |
| display: flex; | |
| align-items: center; | |
| cursor: pointer; | |
| } | |
| .preference-group input[type="checkbox"] { | |
| margin-right: var(--spacing-sm); | |
| } | |
| @media (max-width: 600px) { | |
| .theme-picker { | |
| padding: var(--spacing-md); | |
| } | |
| .custom-theme-controls, | |
| .custom-theme-actions { | |
| flex-direction: column; | |
| } | |
| .color-picker-group { | |
| width: 100%; | |
| } | |
| } | |
| `; | |
| } | |
| /** | |
| * Attach event listeners | |
| */ | |
| attachEventListeners() { | |
| // Theme selector | |
| const themeSelect = this.container.querySelector('#theme-select'); | |
| if (themeSelect) { | |
| themeSelect.addEventListener('change', (e) => { | |
| this.selectedTheme = e.target.value; | |
| this.applySelectedTheme(); | |
| }); | |
| } | |
| // Custom theme color pickers | |
| const colorInputs = this.container.querySelectorAll('.color-picker-group input[type="color"]'); | |
| colorInputs.forEach(input => { | |
| input.addEventListener('input', (e) => { | |
| if (this.options.enableRealTimePreview) { | |
| this.previewCustomColor(e.target); | |
| } | |
| }); | |
| input.addEventListener('change', (e) => { | |
| this.previewCustomColor(e.target); | |
| }); | |
| }); | |
| // Custom theme buttons | |
| const applyCustomButton = this.container.querySelector('#apply-custom-theme'); | |
| if (applyCustomButton) { | |
| applyCustomButton.addEventListener('click', () => { | |
| this.applyCustomTheme(); | |
| }); | |
| } | |
| const resetCustomButton = this.container.querySelector('#reset-custom-theme'); | |
| if (resetCustomButton) { | |
| resetCustomButton.addEventListener('click', () => { | |
| this.resetCustomTheme(); | |
| }); | |
| } | |
| const saveCustomButton = this.container.querySelector('#save-custom-theme'); | |
| if (saveCustomButton) { | |
| saveCustomButton.addEventListener('click', () => { | |
| this.saveCustomTheme(); | |
| }); | |
| } | |
| // Preferences checkboxes | |
| const autoDetectTimeCheckbox = this.container.querySelector('#auto-detect-time'); | |
| if (autoDetectTimeCheckbox) { | |
| autoDetectTimeCheckbox.addEventListener('change', (e) => { | |
| this.themeManager.options.autoDetectTime = e.target.checked; | |
| if (e.target.checked) { | |
| this.themeManager.setupTimeBasedTheme(); | |
| } | |
| }); | |
| } | |
| const transitionsCheckbox = this.container.querySelector('#transitions-enabled'); | |
| if (transitionsCheckbox) { | |
| transitionsCheckbox.addEventListener('change', (e) => { | |
| this.themeManager.options.enableAnimations = e.target.checked; | |
| }); | |
| } | |
| const highContrastCheckbox = this.container.querySelector('#high-contrast-enabled'); | |
| if (highContrastCheckbox) { | |
| highContrastCheckbox.addEventListener('change', (e) => { | |
| this.themeManager.options.enableHighContrast = e.target.checked; | |
| this.render(); // Re-render to update available themes | |
| }); | |
| } | |
| // Listen for theme changes from the theme manager | |
| this.themeManager.addObserver((event) => { | |
| if (event.type === 'themeChange') { | |
| this.updateDisplay(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Apply the selected theme | |
| */ | |
| applySelectedTheme() { | |
| // Save the previous theme before changing | |
| this.previousTheme = this.themeManager.activeTheme; | |
| // Apply the new theme | |
| this.themeManager.setTheme(this.selectedTheme); | |
| // Update display | |
| this.updateDisplay(); | |
| } | |
| /** | |
| * Preview a custom color change | |
| */ | |
| previewCustomColor(inputElement) { | |
| const id = inputElement.id; | |
| const value = inputElement.value; | |
| // Store in custom theme data | |
| if (id === 'primary-color') { | |
| document.documentElement.style.setProperty('--accent-primary', value); | |
| this.customTheme['--accent-primary'] = value; | |
| } else if (id === 'background-color') { | |
| document.documentElement.style.setProperty('--bg-primary', value); | |
| this.customTheme['--bg-primary'] = value; | |
| } else if (id === 'text-color') { | |
| document.documentElement.style.setProperty('--text-primary', value); | |
| this.customTheme['--text-primary'] = value; | |
| } else if (id === 'success-color') { | |
| document.documentElement.style.setProperty('--success', value); | |
| this.customTheme['--success'] = value; | |
| } else if (id === 'error-color') { | |
| document.documentElement.style.setProperty('--error', value); | |
| this.customTheme['--error'] = value; | |
| } | |
| } | |
| /** | |
| * Apply custom theme | |
| */ | |
| applyCustomTheme() { | |
| // Get values from inputs | |
| const primaryColor = this.container.querySelector('#primary-color').value; | |
| const backgroundColor = this.container.querySelector('#background-color').value; | |
| const textColor = this.container.querySelector('#text-color').value; | |
| const successColor = this.container.querySelector('#success-color').value; | |
| const errorColor = this.container.querySelector('#error-color').value; | |
| // Create derived colors | |
| const primaryLighter = this.adjustColor(primaryColor, 30); | |
| const primaryDarker = this.adjustColor(primaryColor, -30); | |
| const bgSecondary = this.adjustColor(backgroundColor, -5); | |
| const bgTertiary = this.adjustColor(backgroundColor, -10); | |
| const textSecondary = this.adjustColor(textColor, 40); | |
| const textTertiary = this.adjustColor(textColor, 80); | |
| const borderColor = this.adjustColor(backgroundColor, -15); | |
| // Generate more robust custom theme | |
| const customVariables = { | |
| '--bg-primary': backgroundColor, | |
| '--bg-secondary': bgSecondary, | |
| '--bg-tertiary': bgTertiary, | |
| '--text-primary': textColor, | |
| '--text-secondary': textSecondary, | |
| '--text-tertiary': textTertiary, | |
| '--accent-primary': primaryColor, | |
| '--accent-secondary': primaryLighter, | |
| '--accent-tertiary': this.adjustColor(primaryLighter, 30), | |
| '--success': successColor, | |
| '--error': errorColor, | |
| '--border-color': borderColor, | |
| '--card-bg': backgroundColor, | |
| '--detection-safe': this.adjustColor(successColor, 80), | |
| '--detection-morph': this.adjustColor(errorColor, 80) | |
| }; | |
| // Store the custom theme | |
| this.customTheme = customVariables; | |
| // Create and apply the custom theme | |
| const customThemeId = this.themeManager.createCustomTheme('Custom', customVariables); | |
| this.themeManager.setTheme(customThemeId); | |
| // Update the selector to show "Custom" | |
| const themeSelect = this.container.querySelector('#theme-select'); | |
| if (themeSelect) { | |
| themeSelect.value = customThemeId; | |
| } | |
| // Update selected theme | |
| this.selectedTheme = customThemeId; | |
| } | |
| /** | |
| * Reset custom theme to base theme | |
| */ | |
| resetCustomTheme() { | |
| // Reset to previous theme if available | |
| if (this.previousTheme) { | |
| this.themeManager.setTheme(this.previousTheme); | |
| this.selectedTheme = this.previousTheme; | |
| // Update select dropdown | |
| const themeSelect = this.container.querySelector('#theme-select'); | |
| if (themeSelect) { | |
| themeSelect.value = this.previousTheme; | |
| } | |
| } else { | |
| // Otherwise reset to light theme | |
| this.themeManager.setTheme('light'); | |
| this.selectedTheme = 'light'; | |
| // Update select dropdown | |
| const themeSelect = this.container.querySelector('#theme-select'); | |
| if (themeSelect) { | |
| themeSelect.value = 'light'; | |
| } | |
| } | |
| // Reset custom theme data | |
| this.customTheme = { ...this.themeManager.themes.light.variables }; | |
| // Update color picker inputs | |
| this.updateColorInputs(); | |
| } | |
| /** | |
| * Save custom theme permanently | |
| */ | |
| saveCustomTheme() { | |
| // Create the custom theme | |
| this.themeManager.createCustomTheme('Custom', this.customTheme); | |
| // Store the theme preference | |
| localStorage.setItem(this.themeManager.options.themeStorageKey, 'custom'); | |
| // Update UI | |
| this.updateDisplay(); | |
| // Show confirmation | |
| alert('Custom theme saved successfully!'); | |
| } | |
| /** | |
| * Update display based on current theme | |
| */ | |
| updateDisplay() { | |
| // Update theme selector | |
| const themeSelect = this.container.querySelector('#theme-select'); | |
| if (themeSelect) { | |
| themeSelect.value = this.themeManager.currentTheme; | |
| } | |
| // Update color inputs | |
| this.updateColorInputs(); | |
| // Update context info | |
| this.updateContextInfo(); | |
| } | |
| /** | |
| * Update color picker inputs to match current theme | |
| */ | |
| updateColorInputs() { | |
| const primaryColorInput = this.container.querySelector('#primary-color'); | |
| const backgroundColorInput = this.container.querySelector('#background-color'); | |
| const textColorInput = this.container.querySelector('#text-color'); | |
| const successColorInput = this.container.querySelector('#success-color'); | |
| const errorColorInput = this.container.querySelector('#error-color'); | |
| if (primaryColorInput) { | |
| primaryColorInput.value = this.getColorValue('--accent-primary'); | |
| } | |
| if (backgroundColorInput) { | |
| backgroundColorInput.value = this.getColorValue('--bg-primary'); | |
| } | |
| if (textColorInput) { | |
| textColorInput.value = this.getColorValue('--text-primary'); | |
| } | |
| if (successColorInput) { | |
| successColorInput.value = this.getColorValue('--success'); | |
| } | |
| if (errorColorInput) { | |
| errorColorInput.value = this.getColorValue('--error'); | |
| } | |
| } | |
| /** | |
| * Update theme context information | |
| */ | |
| updateContextInfo() { | |
| const contextInfo = this.container.querySelector('.theme-context-info'); | |
| if (!contextInfo) return; | |
| const themeContext = this.themeManager.getThemeContext(); | |
| let contextText = ''; | |
| if (themeContext.currentTheme === 'system') { | |
| contextText = `Using system preference: ${themeContext.activeTheme}`; | |
| } else if (themeContext.context.time && this.themeManager.options.autoDetectTime) { | |
| const timeContext = themeContext.context.time; | |
| if (timeContext.isNight) { | |
| contextText = 'Night mode active'; | |
| } else if (timeContext.isEvening) { | |
| contextText = 'Evening mode active'; | |
| } else if (timeContext.isMorning) { | |
| contextText = 'Morning mode active'; | |
| } else if (timeContext.isAfternoon) { | |
| contextText = 'Day mode active'; | |
| } | |
| } | |
| contextInfo.textContent = contextText; | |
| } | |
| /** | |
| * Adjust a color by a percentage | |
| * Positive percentage lightens, negative darkens | |
| */ | |
| adjustColor(color, percent) { | |
| // Convert hex to RGB | |
| let r = parseInt(color.substring(1, 3), 16); | |
| let g = parseInt(color.substring(3, 5), 16); | |
| let b = parseInt(color.substring(5, 7), 16); | |
| // Adjust the color | |
| if (percent > 0) { | |
| // Lighten | |
| r = Math.min(255, Math.round(r + (255 - r) * (percent / 100))); | |
| g = Math.min(255, Math.round(g + (255 - g) * (percent / 100))); | |
| b = Math.min(255, Math.round(b + (255 - b) * (percent / 100))); | |
| } else { | |
| // Darken | |
| const p = Math.abs(percent) / 100; | |
| r = Math.max(0, Math.round(r * (1 - p))); | |
| g = Math.max(0, Math.round(g * (1 - p))); | |
| b = Math.max(0, Math.round(b * (1 - p))); | |
| } | |
| // Convert back to hex | |
| return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; | |
| } | |
| } | |
| // Initialize ThemePicker when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Check if we should auto-initialize | |
| const autoInitPicker = document.querySelector('[data-theme-picker="auto"]'); | |
| if (autoInitPicker) { | |
| new ThemePicker('[data-theme-picker="auto"]'); | |
| } | |
| }); | |
| // Export for module use | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = ThemePicker; | |
| } |