Add theme management system with dynamic theme switching and localStorage support
This commit is contained in:
172
js/themes.js
Normal file
172
js/themes.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* themes.js - Theme management system
|
||||
*/
|
||||
|
||||
const STORAGE_KEY_THEME = 'selected-theme';
|
||||
|
||||
const themes = {
|
||||
dark: {
|
||||
name: 'Dark (Default)',
|
||||
primary: '#1a1a2e',
|
||||
secondary: '#16213e',
|
||||
accent: '#0f3460',
|
||||
text: '#e4e4e4',
|
||||
textMuted: '#a0a0a0',
|
||||
border: '#2a2a3e',
|
||||
cardBg: 'rgba(30, 30, 46, 0.7)',
|
||||
headerBg: 'rgba(26, 26, 46, 0.85)',
|
||||
overlayBg: 'rgba(60, 60, 60, 0.35)'
|
||||
},
|
||||
light: {
|
||||
name: 'Light',
|
||||
primary: '#f5f5f5',
|
||||
secondary: '#ffffff',
|
||||
accent: '#3498db',
|
||||
text: '#2c3e50',
|
||||
textMuted: '#7f8c8d',
|
||||
border: '#dfe6e9',
|
||||
cardBg: 'rgba(255, 255, 255, 0.85)',
|
||||
headerBg: 'rgba(245, 245, 245, 0.95)',
|
||||
overlayBg: 'rgba(255, 255, 255, 0.15)'
|
||||
},
|
||||
ocean: {
|
||||
name: 'Ocean',
|
||||
primary: '#0a1828',
|
||||
secondary: '#1a2332',
|
||||
accent: '#178582',
|
||||
text: '#e0f4f4',
|
||||
textMuted: '#8ab4b4',
|
||||
border: '#1a3a3a',
|
||||
cardBg: 'rgba(26, 35, 50, 0.75)',
|
||||
headerBg: 'rgba(10, 24, 40, 0.9)',
|
||||
overlayBg: 'rgba(23, 133, 130, 0.15)'
|
||||
},
|
||||
sunset: {
|
||||
name: 'Sunset',
|
||||
primary: '#1a0f1e',
|
||||
secondary: '#2a1b2e',
|
||||
accent: '#d4477a',
|
||||
text: '#fce9e9',
|
||||
textMuted: '#c4a4b4',
|
||||
border: '#3a2a3e',
|
||||
cardBg: 'rgba(42, 27, 46, 0.75)',
|
||||
headerBg: 'rgba(26, 15, 30, 0.9)',
|
||||
overlayBg: 'rgba(212, 71, 122, 0.15)'
|
||||
},
|
||||
forest: {
|
||||
name: 'Forest',
|
||||
primary: '#0f1a0f',
|
||||
secondary: '#1a2a1a',
|
||||
accent: '#4a9a4a',
|
||||
text: '#e4f4e4',
|
||||
textMuted: '#a4c4a4',
|
||||
border: '#2a3a2a',
|
||||
cardBg: 'rgba(26, 42, 26, 0.75)',
|
||||
headerBg: 'rgba(15, 26, 15, 0.9)',
|
||||
overlayBg: 'rgba(74, 154, 74, 0.15)'
|
||||
}
|
||||
};
|
||||
|
||||
// Apply theme
|
||||
function applyTheme(themeName) {
|
||||
const theme = themes[themeName] || themes.dark;
|
||||
const root = document.documentElement;
|
||||
|
||||
root.style.setProperty('--color-primary', theme.primary);
|
||||
root.style.setProperty('--color-secondary', theme.secondary);
|
||||
root.style.setProperty('--color-accent', theme.accent);
|
||||
root.style.setProperty('--color-text', theme.text);
|
||||
root.style.setProperty('--color-text-muted', theme.textMuted);
|
||||
root.style.setProperty('--color-border', theme.border);
|
||||
root.style.setProperty('--color-card-bg', theme.cardBg);
|
||||
root.style.setProperty('--color-header-bg', theme.headerBg);
|
||||
root.style.setProperty('--color-overlay-bg', theme.overlayBg);
|
||||
|
||||
// Save preference
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY_THEME, themeName);
|
||||
} catch (e) {
|
||||
console.error('Error saving theme:', e);
|
||||
}
|
||||
|
||||
// Update active state in selector
|
||||
document.querySelectorAll('.theme-option').forEach(option => {
|
||||
option.classList.toggle('active', option.dataset.theme === themeName);
|
||||
});
|
||||
}
|
||||
|
||||
// Load saved theme
|
||||
function loadSavedTheme() {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY_THEME);
|
||||
return saved || 'dark';
|
||||
} catch (e) {
|
||||
console.error('Error loading theme:', e);
|
||||
return 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
// Create theme selector UI
|
||||
function createThemeSelector() {
|
||||
const selector = document.createElement('div');
|
||||
selector.id = 'theme-selector';
|
||||
selector.className = 'theme-selector';
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'theme-toggle';
|
||||
toggle.innerHTML = '🎨';
|
||||
toggle.title = 'Change Theme';
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'theme-menu';
|
||||
|
||||
Object.entries(themes).forEach(([key, theme]) => {
|
||||
const option = document.createElement('div');
|
||||
option.className = 'theme-option';
|
||||
option.dataset.theme = key;
|
||||
option.textContent = theme.name;
|
||||
|
||||
option.addEventListener('click', () => {
|
||||
applyTheme(key);
|
||||
menu.classList.remove('open');
|
||||
});
|
||||
|
||||
menu.appendChild(option);
|
||||
});
|
||||
|
||||
toggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
menu.classList.toggle('open');
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
menu.classList.remove('open');
|
||||
});
|
||||
|
||||
selector.appendChild(toggle);
|
||||
selector.appendChild(menu);
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
// Initialize theme system
|
||||
function initThemes() {
|
||||
// Apply saved theme
|
||||
const savedTheme = loadSavedTheme();
|
||||
applyTheme(savedTheme);
|
||||
|
||||
// Add theme selector to header
|
||||
const header = document.querySelector('header');
|
||||
if (header) {
|
||||
const selector = createThemeSelector();
|
||||
header.appendChild(selector);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other modules
|
||||
window.themesModule = {
|
||||
init: initThemes,
|
||||
applyTheme: applyTheme,
|
||||
themes: themes
|
||||
};
|
||||
Reference in New Issue
Block a user