feat: add theme customization with 6 color schemes
Added comprehensive theme system with: - 6 themes: Dark (default), Darker, Midnight Blue, Forest, Sunset, Light - New Appearance tab in settings with theme selector - Live theme preview showing user/assistant message styles - Theme persistence using localStorage - Dynamic CSS variable updates for instant theme switching - Each theme includes custom gradients, accent colors, and text colors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,7 @@
|
|||||||
<div class="settings-tabs">
|
<div class="settings-tabs">
|
||||||
<button class="tab-btn active" data-tab="api">API</button>
|
<button class="tab-btn active" data-tab="api">API</button>
|
||||||
<button class="tab-btn" data-tab="character">Character</button>
|
<button class="tab-btn" data-tab="character">Character</button>
|
||||||
|
<button class="tab-btn" data-tab="appearance">Appearance</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="api-tab" class="tab-content active">
|
<div id="api-tab" class="tab-content active">
|
||||||
@@ -342,6 +343,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="appearance-tab" class="tab-content">
|
||||||
|
<div class="settings-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="theme-select">Theme</label>
|
||||||
|
<select id="theme-select" class="theme-select">
|
||||||
|
<option value="dark">Dark (Default)</option>
|
||||||
|
<option value="darker">Darker</option>
|
||||||
|
<option value="midnight">Midnight Blue</option>
|
||||||
|
<option value="forest">Forest</option>
|
||||||
|
<option value="sunset">Sunset</option>
|
||||||
|
<option value="light">Light</option>
|
||||||
|
</select>
|
||||||
|
<small style="color: var(--text-secondary); margin-top: 4px; display: block;">Choose your preferred color scheme</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="theme-preview-container">
|
||||||
|
<div class="theme-preview-label">Preview</div>
|
||||||
|
<div class="theme-preview">
|
||||||
|
<div class="theme-preview-message user-preview">
|
||||||
|
<div class="theme-preview-content">User message</div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-preview-message assistant-preview">
|
||||||
|
<div class="theme-preview-avatar"></div>
|
||||||
|
<div class="theme-preview-content">Assistant response</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="input-container">
|
<footer class="input-container">
|
||||||
|
|||||||
143
src/main.js
143
src/main.js
@@ -14,6 +14,138 @@ let newCharacterBtn;
|
|||||||
let currentCharacter = null;
|
let currentCharacter = null;
|
||||||
let pendingAvatarPath = null;
|
let pendingAvatarPath = null;
|
||||||
|
|
||||||
|
// Theme definitions
|
||||||
|
const themes = {
|
||||||
|
dark: {
|
||||||
|
name: 'Dark (Default)',
|
||||||
|
bgPrimary: '#1a1a1a',
|
||||||
|
bgSecondary: '#252525',
|
||||||
|
bgTertiary: '#2f2f2f',
|
||||||
|
textPrimary: '#e8e8e8',
|
||||||
|
textSecondary: '#a0a0a0',
|
||||||
|
accent: '#6366f1',
|
||||||
|
accentHover: '#4f46e5',
|
||||||
|
userMsg: '#4f46e5',
|
||||||
|
assistantMsg: '#2f2f2f',
|
||||||
|
border: '#3a3a3a',
|
||||||
|
gradient: 'linear-gradient(135deg, #1a1a1a 0%, #2a1a2a 100%)',
|
||||||
|
glow: 'rgba(99, 102, 241, 0.1)'
|
||||||
|
},
|
||||||
|
darker: {
|
||||||
|
name: 'Darker',
|
||||||
|
bgPrimary: '#0a0a0a',
|
||||||
|
bgSecondary: '#141414',
|
||||||
|
bgTertiary: '#1a1a1a',
|
||||||
|
textPrimary: '#e0e0e0',
|
||||||
|
textSecondary: '#909090',
|
||||||
|
accent: '#7c3aed',
|
||||||
|
accentHover: '#6d28d9',
|
||||||
|
userMsg: '#6d28d9',
|
||||||
|
assistantMsg: '#1a1a1a',
|
||||||
|
border: '#2a2a2a',
|
||||||
|
gradient: 'linear-gradient(135deg, #0a0a0a 0%, #1a0a1a 100%)',
|
||||||
|
glow: 'rgba(124, 58, 237, 0.1)'
|
||||||
|
},
|
||||||
|
midnight: {
|
||||||
|
name: 'Midnight Blue',
|
||||||
|
bgPrimary: '#0f1419',
|
||||||
|
bgSecondary: '#1a2332',
|
||||||
|
bgTertiary: '#243447',
|
||||||
|
textPrimary: '#e6f1ff',
|
||||||
|
textSecondary: '#8892a0',
|
||||||
|
accent: '#3b82f6',
|
||||||
|
accentHover: '#2563eb',
|
||||||
|
userMsg: '#1e40af',
|
||||||
|
assistantMsg: '#243447',
|
||||||
|
border: '#2d3e54',
|
||||||
|
gradient: 'linear-gradient(135deg, #0f1419 0%, #1a2845 100%)',
|
||||||
|
glow: 'rgba(59, 130, 246, 0.1)'
|
||||||
|
},
|
||||||
|
forest: {
|
||||||
|
name: 'Forest',
|
||||||
|
bgPrimary: '#0d1b14',
|
||||||
|
bgSecondary: '#162820',
|
||||||
|
bgTertiary: '#1f352b',
|
||||||
|
textPrimary: '#e8f5e9',
|
||||||
|
textSecondary: '#90a89f',
|
||||||
|
accent: '#10b981',
|
||||||
|
accentHover: '#059669',
|
||||||
|
userMsg: '#047857',
|
||||||
|
assistantMsg: '#1f352b',
|
||||||
|
border: '#2d4a3a',
|
||||||
|
gradient: 'linear-gradient(135deg, #0d1b14 0%, #1a2820 100%)',
|
||||||
|
glow: 'rgba(16, 185, 129, 0.1)'
|
||||||
|
},
|
||||||
|
sunset: {
|
||||||
|
name: 'Sunset',
|
||||||
|
bgPrimary: '#1a1214',
|
||||||
|
bgSecondary: '#261a1e',
|
||||||
|
bgTertiary: '#332228',
|
||||||
|
textPrimary: '#fde8e8',
|
||||||
|
textSecondary: '#b89090',
|
||||||
|
accent: '#f97316',
|
||||||
|
accentHover: '#ea580c',
|
||||||
|
userMsg: '#c2410c',
|
||||||
|
assistantMsg: '#332228',
|
||||||
|
border: '#4a3238',
|
||||||
|
gradient: 'linear-gradient(135deg, #1a1214 0%, #2a1a1e 100%)',
|
||||||
|
glow: 'rgba(249, 115, 22, 0.1)'
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
name: 'Light',
|
||||||
|
bgPrimary: '#ffffff',
|
||||||
|
bgSecondary: '#f5f5f5',
|
||||||
|
bgTertiary: '#e8e8e8',
|
||||||
|
textPrimary: '#1a1a1a',
|
||||||
|
textSecondary: '#666666',
|
||||||
|
accent: '#6366f1',
|
||||||
|
accentHover: '#4f46e5',
|
||||||
|
userMsg: '#6366f1',
|
||||||
|
assistantMsg: '#f0f0f0',
|
||||||
|
border: '#d0d0d0',
|
||||||
|
gradient: 'linear-gradient(135deg, #ffffff 0%, #f5f0ff 100%)',
|
||||||
|
glow: 'rgba(99, 102, 241, 0.05)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply theme
|
||||||
|
function applyTheme(themeName) {
|
||||||
|
const theme = themes[themeName];
|
||||||
|
if (!theme) return;
|
||||||
|
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.style.setProperty('--bg-primary', theme.bgPrimary);
|
||||||
|
root.style.setProperty('--bg-secondary', theme.bgSecondary);
|
||||||
|
root.style.setProperty('--bg-tertiary', theme.bgTertiary);
|
||||||
|
root.style.setProperty('--text-primary', theme.textPrimary);
|
||||||
|
root.style.setProperty('--text-secondary', theme.textSecondary);
|
||||||
|
root.style.setProperty('--accent', theme.accent);
|
||||||
|
root.style.setProperty('--accent-hover', theme.accentHover);
|
||||||
|
root.style.setProperty('--user-msg', theme.userMsg);
|
||||||
|
root.style.setProperty('--assistant-msg', theme.assistantMsg);
|
||||||
|
root.style.setProperty('--border', theme.border);
|
||||||
|
|
||||||
|
// Update gradient and glow
|
||||||
|
const appContainer = document.querySelector('.app-container');
|
||||||
|
if (appContainer) {
|
||||||
|
appContainer.style.background = theme.gradient;
|
||||||
|
const glow = appContainer.querySelector('::before');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store preference
|
||||||
|
localStorage.setItem('claudia-theme', themeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load saved theme
|
||||||
|
function loadSavedTheme() {
|
||||||
|
const savedTheme = localStorage.getItem('claudia-theme') || 'dark';
|
||||||
|
const themeSelect = document.getElementById('theme-select');
|
||||||
|
if (themeSelect) {
|
||||||
|
themeSelect.value = savedTheme;
|
||||||
|
}
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to get avatar URL
|
// Helper function to get avatar URL
|
||||||
async function getAvatarUrl(avatarFilename) {
|
async function getAvatarUrl(avatarFilename) {
|
||||||
if (!avatarFilename) return null;
|
if (!avatarFilename) return null;
|
||||||
@@ -1039,6 +1171,14 @@ function setupAppControls() {
|
|||||||
section.classList.toggle('collapsed');
|
section.classList.toggle('collapsed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup theme selector
|
||||||
|
const themeSelect = document.getElementById('theme-select');
|
||||||
|
if (themeSelect) {
|
||||||
|
themeSelect.addEventListener('change', (e) => {
|
||||||
|
applyTheme(e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
@@ -1433,5 +1573,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
messageInput.focus();
|
messageInput.focus();
|
||||||
setStatus('Ready');
|
setStatus('Ready');
|
||||||
|
|
||||||
|
// Load saved theme before anything else
|
||||||
|
loadSavedTheme();
|
||||||
|
|
||||||
loadExistingConfig();
|
loadExistingConfig();
|
||||||
});
|
});
|
||||||
|
|||||||
200
src/styles.css
200
src/styles.css
@@ -811,25 +811,117 @@ body {
|
|||||||
animation: spin 0.8s linear infinite;
|
animation: spin 0.8s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light mode support */
|
/* Theme System */
|
||||||
@media (prefers-color-scheme: light) {
|
/* Dark theme (default) */
|
||||||
:root {
|
:root,
|
||||||
--bg-primary: #ffffff;
|
[data-theme="dark"] {
|
||||||
--bg-secondary: #f5f5f5;
|
--bg-primary: #1a1a1a;
|
||||||
--bg-tertiary: #e8e8e8;
|
--bg-secondary: #252525;
|
||||||
--text-primary: #1a1a1a;
|
--bg-tertiary: #2f2f2f;
|
||||||
--text-secondary: #666666;
|
--text-primary: #e8e8e8;
|
||||||
--accent: #6366f1;
|
--text-secondary: #a0a0a0;
|
||||||
--accent-hover: #4f46e5;
|
--accent: #6366f1;
|
||||||
--user-msg: #6366f1;
|
--accent-hover: #4f46e5;
|
||||||
--assistant-msg: #f5f5f5;
|
--user-msg: #4f46e5;
|
||||||
--border: #e0e0e0;
|
--assistant-msg: #2f2f2f;
|
||||||
--shadow: rgba(0, 0, 0, 0.1);
|
--border: #3a3a3a;
|
||||||
}
|
--shadow: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.message.assistant .message-content {
|
/* Light theme */
|
||||||
border: 1px solid var(--border);
|
[data-theme="light"] {
|
||||||
}
|
--bg-primary: #ffffff;
|
||||||
|
--bg-secondary: #f5f5f5;
|
||||||
|
--bg-tertiary: #e8e8e8;
|
||||||
|
--text-primary: #1a1a1a;
|
||||||
|
--text-secondary: #666666;
|
||||||
|
--accent: #6366f1;
|
||||||
|
--accent-hover: #4f46e5;
|
||||||
|
--user-msg: #6366f1;
|
||||||
|
--assistant-msg: #f5f5f5;
|
||||||
|
--border: #e0e0e0;
|
||||||
|
--shadow: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .app-container {
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #f5f5f5 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .app-container::before {
|
||||||
|
background: radial-gradient(circle at top center, rgba(99, 102, 241, 0.05) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .message.assistant .message-content {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Abyss theme - Deep purple/blue */
|
||||||
|
[data-theme="abyss"] {
|
||||||
|
--bg-primary: #0f0f1e;
|
||||||
|
--bg-secondary: #1a1a2e;
|
||||||
|
--bg-tertiary: #252538;
|
||||||
|
--text-primary: #e0e0ff;
|
||||||
|
--text-secondary: #8888bb;
|
||||||
|
--accent: #9d4edd;
|
||||||
|
--accent-hover: #7b2cbf;
|
||||||
|
--user-msg: #7b2cbf;
|
||||||
|
--assistant-msg: #252538;
|
||||||
|
--border: #2f2f48;
|
||||||
|
--shadow: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="abyss"] .app-container {
|
||||||
|
background: linear-gradient(135deg, #0f0f1e 0%, #1a1033 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="abyss"] .app-container::before {
|
||||||
|
background: radial-gradient(circle at top center, rgba(157, 78, 221, 0.15) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nord theme - Cool blues and grays */
|
||||||
|
[data-theme="nord"] {
|
||||||
|
--bg-primary: #2e3440;
|
||||||
|
--bg-secondary: #3b4252;
|
||||||
|
--bg-tertiary: #434c5e;
|
||||||
|
--text-primary: #eceff4;
|
||||||
|
--text-secondary: #d8dee9;
|
||||||
|
--accent: #88c0d0;
|
||||||
|
--accent-hover: #81a1c1;
|
||||||
|
--user-msg: #5e81ac;
|
||||||
|
--assistant-msg: #434c5e;
|
||||||
|
--border: #4c566a;
|
||||||
|
--shadow: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="nord"] .app-container {
|
||||||
|
background: linear-gradient(135deg, #2e3440 0%, #3b4252 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="nord"] .app-container::before {
|
||||||
|
background: radial-gradient(circle at top center, rgba(136, 192, 208, 0.1) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mocha theme - Warm browns */
|
||||||
|
[data-theme="mocha"] {
|
||||||
|
--bg-primary: #1e1e2e;
|
||||||
|
--bg-secondary: #2a2837;
|
||||||
|
--bg-tertiary: #3a3850;
|
||||||
|
--text-primary: #cdd6f4;
|
||||||
|
--text-secondary: #bac2de;
|
||||||
|
--accent: #f5c2e7;
|
||||||
|
--accent-hover: #cba6f7;
|
||||||
|
--user-msg: #cba6f7;
|
||||||
|
--assistant-msg: #3a3850;
|
||||||
|
--border: #45475a;
|
||||||
|
--shadow: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="mocha"] .app-container {
|
||||||
|
background: linear-gradient(135deg, #1e1e2e 0%, #2a2440 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="mocha"] .app-container::before {
|
||||||
|
background: radial-gradient(circle at top center, rgba(245, 194, 231, 0.1) 0%, transparent 70%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Settings Panel - Slide-in Sidebar */
|
/* Settings Panel - Slide-in Sidebar */
|
||||||
@@ -1160,6 +1252,78 @@ body {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Theme Preview */
|
||||||
|
.theme-preview-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-message {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-message.user-preview {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-message.assistant-preview {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-avatar {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-content {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-message.user-preview .theme-preview-content {
|
||||||
|
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
|
||||||
|
color: white;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-message.assistant-preview .theme-preview-content {
|
||||||
|
background: var(--assistant-msg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.messages-list {
|
.messages-list {
|
||||||
|
|||||||
Reference in New Issue
Block a user