Files
blackroad-operating-system/backend/static/js/theme.js
Claude a180873b7d Fix frontend errors and pydantic config for local development
Frontend fixes:
- Copy missing JS files from blackroad-os/ to backend/static/js/
  - os.js (core OS functionality)
  - components.js (UI components)
  - registry.js (app registry)
  - app.js, config.js, theme.js, mock_data.js (supporting files)
- Fixes 3 ERROR findings from Cece audit
- System health: 0 ERRORS → 94 SUCCESSES (from 91)

Backend config fix:
- Add `extra = "ignore"` to Settings.Config in backend/app/config.py
- Allows .env.example to have more vars than Settings class defines
- Fixes Pydantic v2 validation errors on startup
- Enables local development without removing env template vars

Cece audit results after fixes:
🔴 CRITICAL: 0
🟠 ERROR:    0 (was 3)
🟡 WARNING:  6
🟢 SUCCESS:  94 (was 91)
2025-11-20 01:38:56 +00:00

248 lines
7.8 KiB
JavaScript

/**
* BlackRoad OS Theme Manager
* Handles theme switching and persistence
*
* Built-in Themes:
* - TealOS (default): Teal/cyan cyberpunk aesthetic
* - NightOS: Purple/magenta dark theme
*
* Features:
* - Theme persistence via localStorage
* - Smooth transitions between themes
* - Event emission for theme changes
* - Extensible architecture for custom themes
*
* Theme System Architecture:
* - Themes are defined via CSS variables in styles.css
* - Body attribute `data-theme` controls which CSS vars are active
* - All components reference CSS variables, not hardcoded colors
* - New themes can be added by adding `body[data-theme="name"]` blocks
*
* TODO v0.2.0: Add theme preview system
* TODO v0.2.0: Add custom theme builder in Settings app
* TODO v0.3.0: Support theme import/export (JSON format)
* TODO v0.3.0: Add system theme (auto dark/light based on OS preference)
*/
class ThemeManager {
constructor() {
this.currentTheme = 'tealOS';
this.availableThemes = ['tealOS', 'nightOS', 'contrastOS']; // Extensible list
// TODO v0.2.0: Load available themes dynamically from CSS
this.init();
}
init() {
// Load saved theme preference from localStorage
const saved = localStorage.getItem('blackroad-theme');
if (saved && this.availableThemes.includes(saved)) {
this.currentTheme = saved;
}
// Apply theme immediately (before page renders)
this.applyTheme(this.currentTheme);
// Setup toggle button
this.setupToggleButton();
console.log(`🎨 Theme Manager initialized: ${this.currentTheme}`);
}
/**
* Setup theme toggle button in system tray
*/
setupToggleButton() {
const toggleBtn = document.getElementById('theme-toggle');
if (!toggleBtn) return;
// Add accessibility attributes
toggleBtn.setAttribute('aria-label', 'Toggle theme');
toggleBtn.setAttribute('aria-pressed', 'false');
toggleBtn.addEventListener('click', () => {
this.toggleTheme();
});
// Keyboard support
toggleBtn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.toggleTheme();
}
});
this.updateToggleButton();
}
/**
* Toggle between available themes
* Currently cycles between TealOS and NightOS
* TODO v0.2.0: Support more than 2 themes with dropdown or cycle logic
*/
toggleTheme() {
// Cycle through available themes
const currentIndex = this.availableThemes.indexOf(this.currentTheme);
const nextIndex = (currentIndex + 1) % this.availableThemes.length;
this.currentTheme = this.availableThemes[nextIndex];
this.applyTheme(this.currentTheme);
this.saveTheme();
this.updateToggleButton();
// Emit event so apps can react if needed
if (window.OS) {
window.OS.eventBus.emit('theme:changed', {
theme: this.currentTheme,
previousTheme: this.currentTheme === 'tealOS' ? 'nightOS' : 'tealOS'
});
// Show notification
const themeName = this.currentTheme === 'tealOS' ? 'Teal OS' : 'Night OS';
window.OS.showNotification({
type: 'info',
title: 'Theme Changed',
message: `Switched to ${themeName}`,
duration: 2000
});
}
console.log(`🎨 Theme switched to: ${this.currentTheme}`);
}
/**
* Apply a theme by setting data-theme attribute
* @param {string} theme - Theme identifier (e.g., 'tealOS', 'nightOS')
*/
applyTheme(theme) {
if (!this.availableThemes.includes(theme)) {
console.warn(`Unknown theme: ${theme}. Falling back to tealOS`);
theme = 'tealOS';
}
// Apply theme with smooth transition
document.body.classList.add('theme-transitioning');
document.body.setAttribute('data-theme', theme);
// Remove transition class after animation completes
setTimeout(() => {
document.body.classList.remove('theme-transitioning');
}, 300);
}
/**
* Save current theme to localStorage
*/
saveTheme() {
localStorage.setItem('blackroad-theme', this.currentTheme);
}
/**
* Update toggle button icon to reflect current theme
*/
updateToggleButton() {
const toggleBtn = document.getElementById('theme-toggle');
if (!toggleBtn) return;
const icon = toggleBtn.querySelector('.icon');
if (icon) {
const iconMap = { tealOS: '🌙', nightOS: '☀️', contrastOS: '⚡️' };
icon.textContent = iconMap[this.currentTheme] || '🎨';
}
// Update aria-label for clarity
const currentIndex = this.availableThemes.indexOf(this.currentTheme);
const nextTheme = this.availableThemes[(currentIndex + 1) % this.availableThemes.length] || 'Teal OS';
toggleBtn.setAttribute('aria-label', `Switch to ${nextTheme}`);
}
/**
* Get current theme
* @returns {string} Current theme identifier
*/
getTheme() {
return this.currentTheme;
}
/**
* Set theme programmatically
* @param {string} theme - Theme identifier
*/
setTheme(theme) {
if (this.availableThemes.includes(theme)) {
this.currentTheme = theme;
this.applyTheme(theme);
this.saveTheme();
this.updateToggleButton();
// Emit event
if (window.OS) {
window.OS.eventBus.emit('theme:changed', { theme });
}
} else {
console.error(`Cannot set theme: ${theme} is not available`);
}
}
/**
* Get list of available themes
* @returns {Array} Array of theme identifiers
*/
getAvailableThemes() {
return [...this.availableThemes];
}
/**
* Get theme metadata (for Settings app or theme picker)
* @param {string} theme - Theme identifier
* @returns {Object} Theme metadata
*/
getThemeMetadata(theme) {
const metadata = {
tealOS: {
id: 'tealOS',
name: 'Teal OS',
description: 'Cyberpunk teal/cyan aesthetic with dark background',
primaryColor: '#0FA',
author: 'BlackRoad Team',
preview: null // TODO v0.2.0: Add preview image
},
nightOS: {
id: 'nightOS',
name: 'Night OS',
description: 'Purple/magenta dark theme',
primaryColor: '#A0F',
author: 'BlackRoad Team',
preview: null
}
};
return metadata[theme] || null;
}
/**
* Preview a theme without committing (for theme picker)
* TODO v0.2.0: Implement preview mode with cancel/apply buttons
* @param {string} theme - Theme to preview
*/
previewTheme(theme) {
console.log(`🔍 Preview mode for theme: ${theme} - Coming in v0.2.0`);
// Would temporarily apply theme without saving
// Settings app would show "Apply" and "Cancel" buttons
}
/**
* Register a custom theme (extension point for future custom theme system)
* TODO v0.3.0: Allow apps to register custom themes dynamically
* @param {Object} themeDefinition - Theme definition object
*/
registerCustomTheme(themeDefinition) {
console.log('📦 Custom theme registration - Coming in v0.3.0');
// Would validate theme definition
// Add CSS variables dynamically
// Add to availableThemes list
}
}
// Create global instance
window.ThemeManager = new ThemeManager();