class ThemeModule { constructor() { this.commands = {}; this.terminal = null; this.key = 'webx11_themes'; this._previewTimer = null; this.defaults = { 'dark': { '--bg-color': '#000000', '--bg-secondary': '#0a0a0a', '--bg-tertiary': '#111111', '--text-color': '#00ff00', '--text-muted': '#888888', '--accent-color': '#00aa00', '--border-color': '#005500' }, 'light': { '--bg-color': '#ffffff', '--bg-secondary': '#f5f5f5', '--bg-tertiary': '#eeeeee', '--text-color': '#222222', '--text-muted': '#888888', '--accent-color': '#0066cc', '--border-color': '#cccccc' }, 'amber': { '--bg-color': '#1a0f00', '--bg-secondary': '#2a1a00', '--bg-tertiary': '#332200', '--text-color': '#ffaa00', '--text-muted': '#886600', '--accent-color': '#ff8800', '--border-color': '#663300' }, 'cyber': { '--bg-color': '#000a14', '--bg-secondary': '#001a2e', '--bg-tertiary': '#002240', '--text-color': '#00ffff', '--text-muted': '#0088aa', '--accent-color': '#ff00ff', '--border-color': '#004466' }, 'ocean': { '--bg-color': '#0a1628', '--bg-secondary': '#0f2240', '--bg-tertiary': '#142e54', '--text-color': '#88ddff', '--text-muted': '#5599bb', '--accent-color': '#00aaff', '--border-color': '#1a4470' }, 'forest': { '--bg-color': '#0a1a0e', '--bg-secondary': '#0f2814', '--bg-tertiary': '#14361c', '--text-color': '#88dd88', '--text-muted': '#559955', '--accent-color': '#33cc33', '--border-color': '#1a4a22' }, 'monochrome': { '--bg-color': '#0a0a0a', '--bg-secondary': '#181818', '--bg-tertiary': '#222222', '--text-color': '#cccccc', '--text-muted': '#666666', '--accent-color': '#999999', '--border-color': '#333333' }, 'sunset': { '--bg-color': '#1a0a14', '--bg-secondary': '#2a1220', '--bg-tertiary': '#3a1a2c', '--text-color': '#ffbb99', '--text-muted': '#cc7755', '--accent-color': '#ff6644', '--border-color': '#552233' }, 'midnight': { '--bg-color': '#050510', '--bg-secondary': '#0a0a20', '--bg-tertiary': '#101030', '--text-color': '#aaaaee', '--text-muted': '#555588', '--accent-color': '#6666ff', '--border-color': '#1a1a3a' } }; this.themes = this.load(); } requirements() { return { met: true, missing: [] }; } getHelpInfo() { return { 'theme': 'theme [...] — Manage and preview visual themes' }; } load() { try { const data = localStorage.getItem(this.key); return data ? { ...this.defaults, ...JSON.parse(data) } : { ...this.defaults }; } catch (e) { return { ...this.defaults }; } } save() { const custom = {}; Object.keys(this.themes).forEach(k => { if (!this.defaults[k]) custom[k] = this.themes[k]; }); localStorage.setItem(this.key, JSON.stringify(custom)); } registerCommands() { this.commands['theme'] = (args) => this.handle(args); } handle(args) { if (!args[0]) return this.terminal.print('Usage: theme set , theme list, theme new, theme delete , theme export , theme preview , theme keep', 'warning'); const sub = args[0].toLowerCase(); if (sub === 'set') this.setTheme(args[1]); else if (sub === 'list') this.listThemes(); else if (sub === 'new') this.newTheme(args.slice(1)); else if (sub === 'delete') this.deleteTheme(args[1]); else if (sub === 'export') this.exportTheme(args[1]); else if (sub === 'preview') this.previewTheme(args[1]); else if (sub === 'keep') this.keepPreview(); else this.terminal.print(`Unknown: ${sub}`, 'error'); } _applyVars(name) { if (!this.themes[name]) return false; const vars = this.themes[name]; const root = document.documentElement; Object.entries(vars).forEach(([key, val]) => root.style.setProperty(key, val)); return true; } setTheme(name) { if (!name) return this.terminal.print('Usage: theme set ', 'warning'); if (!this.themes[name]) return this.terminal.print(`Theme '${name}' not found`, 'error'); // Cancel any active preview if (this._previewTimer) { clearTimeout(this._previewTimer); this._previewTimer = null; } this._applyVars(name); localStorage.setItem('webx11_active_theme', name); this.terminal.print(`Theme set to: ${name}`, 'success'); } previewTheme(name) { if (!name) return this.terminal.print('Usage: theme preview ', 'warning'); if (!this.themes[name]) return this.terminal.print(`Theme '${name}' not found`, 'error'); // Cancel any existing preview if (this._previewTimer) { clearTimeout(this._previewTimer); this._previewTimer = null; } const previous = localStorage.getItem('webx11_active_theme') || 'dark'; this._applyVars(name); this.terminal.print(`🎨 Previewing: ${name} — will revert in 5s`, 'info'); this.terminal.print(' Type "theme keep" to keep this theme permanently', 'info'); this._previewTimer = setTimeout(() => { this._previewTimer = null; this._applyVars(previous); this.terminal.print(`⏱ Preview ended — reverted to: ${previous}`, 'info'); }, 5000); } keepPreview() { if (!this._previewTimer) return this.terminal.print('No active preview to keep', 'warning'); clearTimeout(this._previewTimer); this._previewTimer = null; const root = document.documentElement; const cssVars = ['--bg-color', '--bg-secondary', '--bg-tertiary', '--text-color', '--text-muted', '--accent-color', '--border-color']; const currentVars = {}; cssVars.forEach(v => { currentVars[v] = getComputedStyle(root).getPropertyValue(v).trim() || '#000'; }); let matchName = null; for (const [name, vars] of Object.entries(this.themes)) { const match = cssVars.every(v => vars[v] === currentVars[v]); if (match) { matchName = name; break; } } if (matchName) { localStorage.setItem('webx11_active_theme', matchName); this.terminal.print(`✅ Preview kept — theme saved as: ${matchName}`, 'success'); } else { // Custom vars — save current state as active const active = localStorage.getItem('webx11_active_theme') || 'dark'; this.terminal.print(`✅ Preview kept (${active} theme)`, 'success'); } } listThemes() { const active = localStorage.getItem('webx11_active_theme') || 'dark'; this.terminal.print('=== Themes ===', 'info'); Object.keys(this.themes).forEach(name => { const mark = name === active ? ' ✅' : ''; this.terminal.print(` ${name}${mark}`); }); this.terminal.print('Use: theme set to apply', 'info'); } newTheme(args) { if (args.length < 1) return this.terminal.print('Usage: theme new --bg #000 --text #0f0', 'warning'); const name = args[0].toLowerCase(); if (this.themes[name]) return this.terminal.print(`Theme '${name}' already exists`, 'warning'); const vars = {}; const root = document.documentElement; const cssVars = ['--bg-color', '--bg-secondary', '--bg-tertiary', '--text-color', '--text-muted', '--accent-color', '--border-color', '--error-color', '--warning-color', '--info-color', '--success-color']; for (let i = 1; i < args.length; i++) { if (args[i].startsWith('--')) { const val = args[i + 1]; if (val && !val.startsWith('--')) { vars[args[i]] = val; i++; } } } if (Object.keys(vars).length === 0) { const preview = {}; cssVars.forEach(v => { preview[v] = getComputedStyle(root).getPropertyValue(v).trim() || '#000'; }); this.themes[name] = preview; } else { this.themes[name] = vars; } this.save(); this.terminal.print(`Theme '${name}' created with ${Object.keys(this.themes[name]).length} variables`, 'success'); } deleteTheme(name) { if (!name) return this.terminal.print('Usage: theme delete ', 'warning'); if (this.defaults[name]) return this.terminal.print('Cannot delete built-in theme', 'warning'); if (!this.themes[name]) return this.terminal.print(`Theme '${name}' not found`, 'error'); delete this.themes[name]; this.save(); const active = localStorage.getItem('webx11_active_theme'); if (active === name) { localStorage.removeItem('webx11_active_theme'); } this.terminal.print(`Theme '${name}' deleted`, 'success'); } exportTheme(name) { if (!name) return this.terminal.print('Usage: theme export ', 'warning'); if (!this.themes[name]) return this.terminal.print(`Theme '${name}' not found`, 'error'); const data = JSON.stringify(this.themes[name], null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `theme-${name}.json`; a.click(); URL.revokeObjectURL(url); this.terminal.print(`Theme '${name}' exported`, 'success'); } } if (typeof window.webx11 !== 'undefined' && window.webx11) { webx11.moduleManager.registerModule(new ThemeModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/theme.js'); }