diff --git a/tauri-app/frontend/index.html b/tauri-app/frontend/index.html index 46a1e59..32fe4c7 100644 --- a/tauri-app/frontend/index.html +++ b/tauri-app/frontend/index.html @@ -20,7 +20,23 @@ + + + @@ -839,16 +855,29 @@

Appearance

-
-
- Dark Mode - -
+
+ + + + +
- Switch between light and dark theme + Choose a color theme. Your choice is saved and applied on startup.
diff --git a/tauri-app/frontend/js/app.js b/tauri-app/frontend/js/app.js index 518481f..2778109 100644 --- a/tauri-app/frontend/js/app.js +++ b/tauri-app/frontend/js/app.js @@ -1757,10 +1757,15 @@ NostrMailApp.prototype.setupEventListeners = function() { // Profile switcher this.setupProfileSwitcherListeners(); - // Dark mode toggle - const darkToggle = document.getElementById('dark-mode-toggle'); - if (darkToggle) { - darkToggle.addEventListener('click', () => this.toggleDarkMode()); + // Theme picker + const themePicker = document.getElementById('theme-picker'); + if (themePicker) { + themePicker.addEventListener('click', (e) => { + const swatch = e.target.closest('.theme-swatch'); + if (swatch) { + this.setTheme(swatch.getAttribute('data-theme-value')); + } + }); } // Profile form @@ -5694,28 +5699,58 @@ NostrMailApp.prototype.updatePublicKeyDisplay = async function() { } } -// Dark mode management -NostrMailApp.prototype.setDarkMode = function(enabled) { - document.body.classList.toggle('dark-mode', enabled); - const icon = document.getElementById('dark-mode-icon'); - if (icon) { - icon.className = enabled ? 'fas fa-sun' : 'fas fa-moon'; +// Theme management +NostrMailApp.AVAILABLE_THEMES = ['light', 'dark', 'high-contrast', 'ocean', 'forest']; + +// Resolve the active theme, honoring the legacy darkMode preference for +// users who set it before the multi-theme picker existed. +NostrMailApp.prototype.getSavedTheme = function() { + let theme = localStorage.getItem('theme'); + if (!theme) { + theme = localStorage.getItem('darkMode') === '1' ? 'dark' : 'light'; } - const text = document.getElementById('dark-mode-text'); - if (text) { - text.textContent = enabled ? 'Enable Light Mode' : 'Enable Dark Mode'; + if (NostrMailApp.AVAILABLE_THEMES.indexOf(theme) === -1) { + theme = 'light'; } - const label = document.getElementById('theme-label'); - if (label) { - label.textContent = enabled ? 'Dark Mode (Enabled)' : 'Light Mode (Enabled)'; + return theme; +}; + +NostrMailApp.prototype.setTheme = function(theme) { + if (NostrMailApp.AVAILABLE_THEMES.indexOf(theme) === -1) { + theme = 'light'; } - localStorage.setItem('darkMode', enabled ? '1' : '0'); -} + + // Token sets live on :root[data-theme]; keep html and body in sync. + document.documentElement.setAttribute('data-theme', theme); + document.body.setAttribute('data-theme', theme); + + // Dark keeps the legacy .dark-mode class (its visual overrides live in + // dark-mode.css); every other non-light theme uses the generic token + // layer driven by the .themed class. + document.body.classList.toggle('dark-mode', theme === 'dark'); + document.body.classList.toggle('themed', theme !== 'light' && theme !== 'dark'); + + // Persist, and keep darkMode in sync for any older code paths. + localStorage.setItem('theme', theme); + localStorage.setItem('darkMode', theme === 'dark' ? '1' : '0'); + + // Reflect the selection in the Settings picker if it is rendered. + const swatches = document.querySelectorAll('#theme-picker .theme-swatch'); + swatches.forEach((swatch) => { + const selected = swatch.getAttribute('data-theme-value') === theme; + swatch.setAttribute('aria-checked', selected ? 'true' : 'false'); + }); +}; + +// Backward-compatible shims for any remaining callers. +NostrMailApp.prototype.setDarkMode = function(enabled) { + this.setTheme(enabled ? 'dark' : 'light'); +}; NostrMailApp.prototype.toggleDarkMode = function() { - const enabled = !document.body.classList.contains('dark-mode'); - this.setDarkMode(enabled); -} + const isDark = this.getSavedTheme() === 'dark'; + this.setTheme(isDark ? 'light' : 'dark'); +}; // Add a method to render the email warning NostrMailApp.prototype.renderProfileEmailWarning = function() { @@ -6023,11 +6058,12 @@ document.addEventListener('DOMContentLoaded', () => { } - // Set initial dark mode from localStorage - const darkPref = localStorage.getItem('darkMode'); - window.app.setDarkMode(darkPref === '1'); - - console.log('🎨 Dark mode initialized:', darkPref === '1' ? 'enabled' : 'disabled'); + // Apply the saved theme (the inline script already set data-theme + // on pre-paint; this syncs the body classes and picker state). + const initialTheme = window.app.getSavedTheme(); + window.app.setTheme(initialTheme); + + console.log('🎨 Theme initialized:', initialTheme); // Initialize the application window.app.init(); diff --git a/tauri-app/frontend/js/contacts-service.js b/tauri-app/frontend/js/contacts-service.js index 7bbafa6..513bbbb 100644 --- a/tauri-app/frontend/js/contacts-service.js +++ b/tauri-app/frontend/js/contacts-service.js @@ -1980,9 +1980,9 @@ class ContactsService { // Update the UI to show it's an encrypted email if (toAddressInput) { - const isDarkMode = document.body.classList.contains('dark-mode'); - toAddressInput.style.borderColor = '#667eea'; - toAddressInput.style.backgroundColor = isDarkMode ? '#1a1f3a' : '#f8f9ff'; + const styles = getComputedStyle(document.body); + toAddressInput.style.borderColor = styles.getPropertyValue('--color-primary').trim() || '#667eea'; + toAddressInput.style.backgroundColor = styles.getPropertyValue('--color-input-bg').trim() || '#f8f9ff'; } window.notificationService.showSuccess(`Email address and recipient pubkey filled in for ${contact.name}`); diff --git a/tauri-app/frontend/js/email-service.js b/tauri-app/frontend/js/email-service.js index 478795a..082dc92 100644 --- a/tauri-app/frontend/js/email-service.js +++ b/tauri-app/frontend/js/email-service.js @@ -392,10 +392,10 @@ class EmailService { }); } - // Get appropriate background color for Nostr contact input based on dark mode + // Get appropriate background color for Nostr contact input from the active theme getNostrContactInputBackgroundColor() { - const isDarkMode = document.body.classList.contains('dark-mode'); - return isDarkMode ? '#1a1f3a' : '#f8f9ff'; + const inputBg = getComputedStyle(document.body).getPropertyValue('--color-input-bg').trim(); + return inputBg || '#f8f9ff'; } /** diff --git a/tauri-app/frontend/js/utils.js b/tauri-app/frontend/js/utils.js index 1842f7b..8eda9fe 100644 --- a/tauri-app/frontend/js/utils.js +++ b/tauri-app/frontend/js/utils.js @@ -26,8 +26,11 @@ class Utils { iframe.style.minHeight = '100px'; container.appendChild(iframe); const doc = iframe.contentDocument || iframe.contentWindow.document; - // Inject dark mode aware base styles + the email HTML - const isDark = document.body.classList.contains('dark-mode'); + // Inject dark mode aware base styles + the email HTML. + // Every theme except light uses a dark surface, so apply the dark + // email overrides whenever the active theme is not light. + const activeTheme = document.documentElement.getAttribute('data-theme') || 'light'; + const isDark = activeTheme !== 'light'; doc.open(); const darkOverrides = isDark ? ` h4 { color: #9ca3af !important; } diff --git a/tauri-app/frontend/styles/buttons.css b/tauri-app/frontend/styles/buttons.css index bd0c67a..b51ee81 100644 --- a/tauri-app/frontend/styles/buttons.css +++ b/tauri-app/frontend/styles/buttons.css @@ -14,7 +14,7 @@ } .btn-primary { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--color-primary-gradient); color: white; } @@ -205,7 +205,7 @@ select.btn-select:hover:not(:disabled) { select.btn-select:focus { outline: none; - border-color: #667eea; + border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } diff --git a/tauri-app/frontend/styles/contacts.css b/tauri-app/frontend/styles/contacts.css index ea745ea..2e7e49a 100644 --- a/tauri-app/frontend/styles/contacts.css +++ b/tauri-app/frontend/styles/contacts.css @@ -28,7 +28,7 @@ } .contact-item.active { - background-color: #667eea; + background-color: var(--color-primary); color: white; } @@ -196,7 +196,7 @@ body.dark-mode .contact-section-content .section-empty-message { border: none; cursor: pointer; padding: 4px; - color: #667eea; + color: var(--color-primary); font-size: 14px; opacity: 0.8; transition: opacity 0.2s ease, color 0.2s ease; @@ -208,7 +208,7 @@ body.dark-mode .contact-section-content .section-empty-message { .contact-email-icon:hover { opacity: 1; - color: #5568d3; + color: var(--color-primary-hover); background-color: rgba(102, 126, 234, 0.1); } @@ -388,7 +388,7 @@ body.dark-mode .contact-section-content .section-empty-message { } .contact-detail-email { - color: #667eea; + color: var(--color-primary); text-decoration: none; font-weight: 500; } @@ -405,7 +405,7 @@ body.dark-mode .contact-section-content .section-empty-message { margin: 0 0 15px 0; font-size: 1.1rem; color: #333; - border-bottom: 2px solid #667eea; + border-bottom: 2px solid var(--color-primary); padding-bottom: 5px; } @@ -472,7 +472,7 @@ body.dark-mode .contact-section-content .section-empty-message { .contacts-search-input:focus { outline: none; - border-color: #667eea; + border-color: var(--color-primary); background-color: white; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -513,7 +513,7 @@ body.dark-mode .contact-detail-info h3 { body.dark-mode .contact-detail-section h4 { color: #e0e0e0; - border-bottom-color: #667eea; + border-bottom-color: var(--color-primary); } body.dark-mode .contact-detail-field label { @@ -551,7 +551,7 @@ body.dark-mode .contact-item:hover { } body.dark-mode .contact-item.active { - background-color: #667eea; + background-color: var(--color-primary); color: white; } @@ -573,7 +573,7 @@ body.dark-mode .contact-detail-pubkey-code { } body.dark-mode .contact-email-icon { - color: #667eea; + color: var(--color-primary); opacity: 0.8; } @@ -591,7 +591,7 @@ body.dark-mode .contacts-search-input { } body.dark-mode .contacts-search-input:focus { - border-color: #667eea; + border-color: var(--color-primary); background-color: #232946; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -654,7 +654,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover { .privacy-toggle-icon { font-size: 1.2rem; - color: #667eea; + color: var(--color-primary); } .privacy-toggle-title { @@ -693,7 +693,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover { } .privacy-toggle input[type="checkbox"]:checked + .privacy-toggle-slider { - background-color: #667eea; + background-color: var(--color-primary); } .privacy-toggle-slider::before { @@ -740,7 +740,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover { z-index: 10; padding: 0; font-size: 0.95rem; - color: #667eea; + color: var(--color-primary); font-weight: 600; backdrop-filter: blur(2px); } @@ -755,7 +755,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover { body.dark-mode .privacy-toggle-loading { background-color: rgba(24, 26, 32, 0.98); - color: #667eea; + color: var(--color-primary); } /* Dark mode support for privacy toggle */ @@ -777,7 +777,7 @@ body.dark-mode .privacy-toggle-slider { } body.dark-mode .privacy-toggle input[type="checkbox"]:checked + .privacy-toggle-slider { - background-color: #667eea; + background-color: var(--color-primary); } body.dark-mode .privacy-toggle-description { diff --git a/tauri-app/frontend/styles/dark-mode.css b/tauri-app/frontend/styles/dark-mode.css index b4024f4..20d5072 100644 --- a/tauri-app/frontend/styles/dark-mode.css +++ b/tauri-app/frontend/styles/dark-mode.css @@ -69,7 +69,7 @@ body.dark-mode .email-item { body.dark-mode .email-item.unread { background-color: #232946; - border-left: 4px solid #667eea; + border-left: 4px solid var(--color-primary); } body.dark-mode .modal { @@ -90,7 +90,7 @@ body.dark-mode .form-group textarea { body.dark-mode .form-group input:focus, body.dark-mode .form-group textarea:focus { - border-color: #667eea; + border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } diff --git a/tauri-app/frontend/styles/dm.css b/tauri-app/frontend/styles/dm.css index f389fca..0996eda 100644 --- a/tauri-app/frontend/styles/dm.css +++ b/tauri-app/frontend/styles/dm.css @@ -61,7 +61,7 @@ .dm-search-input:focus { outline: none; - border-color: #667eea; + border-color: var(--color-primary); background-color: white; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -84,7 +84,7 @@ body.dark-mode .dm-search-input { } body.dark-mode .dm-search-input:focus { - border-color: #667eea; + border-color: var(--color-primary); background-color: #232946; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -189,7 +189,7 @@ body.dark-mode .dm-search-icon { } .dm-contact-item.active { - background-color: #667eea; + background-color: var(--color-primary); color: white; } @@ -445,7 +445,7 @@ body.dark-mode .dm-back-to-list-btn:hover { } .message-sent .message-content { - background: #667eea; + background: var(--color-primary); color: white; } @@ -810,7 +810,7 @@ body.dark-mode .dm-debug-copy:hover { } .dm-send-btn { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--color-primary-gradient); color: white; border: none; border-radius: 50%; @@ -825,7 +825,7 @@ body.dark-mode .dm-debug-copy:hover { } .dm-send-btn:hover:not(:disabled) { - background: linear-gradient(135deg, #5568d3 0%, #653a8f 100%); + background: linear-gradient(135deg, var(--color-primary-hover) 0%, #653a8f 100%); } .dm-send-btn:disabled { diff --git a/tauri-app/frontend/styles/email.css b/tauri-app/frontend/styles/email.css index cc970f5..9bcee42 100644 --- a/tauri-app/frontend/styles/email.css +++ b/tauri-app/frontend/styles/email.css @@ -785,7 +785,7 @@ body.dark-mode .email-item { body.dark-mode .email-item.unread { background-color: #232946; - border-left: 4px solid #667eea; + border-left: 4px solid var(--color-primary); } body.dark-mode .email-item:hover { @@ -1016,7 +1016,7 @@ body.dark-mode .email-attachment-icon { .email-search-input:focus { outline: none; - border-color: #667eea; + border-color: var(--color-primary); background-color: white; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -1039,7 +1039,7 @@ body.dark-mode .email-search-input { } body.dark-mode .email-search-input:focus { - border-color: #667eea; + border-color: var(--color-primary); background-color: #232946; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -1188,7 +1188,7 @@ body.dark-mode .email-body-content-text { min-width: 20px; height: 20px; border-radius: 10px; - background: #667eea; + background: var(--color-primary); color: white; font-size: 0.75rem; font-weight: 600; diff --git a/tauri-app/frontend/styles/forms.css b/tauri-app/frontend/styles/forms.css index e9bbda8..d3b3b5a 100644 --- a/tauri-app/frontend/styles/forms.css +++ b/tauri-app/frontend/styles/forms.css @@ -173,7 +173,7 @@ body.dark-mode .settings-section-toggle { .form-group textarea:focus, .form-group select:focus { outline: none; - border-color: #667eea; + border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } @@ -202,7 +202,7 @@ body.dark-mode .form-group select { } body.dark-mode .form-group select:focus { - border-color: #667eea; + border-color: var(--color-primary); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23667eea' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); } @@ -315,7 +315,7 @@ body.dark-mode .profile-field-error { min-height: 1.5em; } .profile-field-value a { - color: #667eea; + color: var(--color-primary); text-decoration: none; } .profile-field-value a:hover { @@ -330,7 +330,7 @@ body.dark-mode .profile-field-value a { body.dark-mode .form-group input:focus, body.dark-mode .form-group textarea:focus { - border-color: #667eea; + border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } @@ -631,7 +631,7 @@ body.dark-mode .toggle-setting > label:first-child { } .toggle-switch input[type="checkbox"]:checked { - background-color: #667eea; + background-color: var(--color-primary); } .toggle-switch input[type="checkbox"]:checked::before { @@ -643,7 +643,7 @@ body.dark-mode .toggle-switch input[type="checkbox"] { } body.dark-mode .toggle-switch input[type="checkbox"]:checked { - background-color: #667eea; + background-color: var(--color-primary); } /* Toggle Settings List */ @@ -714,7 +714,7 @@ body.dark-mode .toggle-settings-item:hover { } .selected-pubkey-display span { - color: #667eea; + color: var(--color-primary); font-family: 'Courier New', monospace; word-break: break-all; } diff --git a/tauri-app/frontend/styles/loading.css b/tauri-app/frontend/styles/loading.css index 51993bb..e0c9a72 100644 --- a/tauri-app/frontend/styles/loading.css +++ b/tauri-app/frontend/styles/loading.css @@ -6,7 +6,7 @@ width: 20px; height: 20px; border: 3px solid #f3f3f3; - border-top: 3px solid #667eea; + border-top: 3px solid var(--color-primary); border-radius: 50%; animation: spin 1s linear infinite; } diff --git a/tauri-app/frontend/styles/modals.css b/tauri-app/frontend/styles/modals.css index a16f86d..36f6fec 100644 --- a/tauri-app/frontend/styles/modals.css +++ b/tauri-app/frontend/styles/modals.css @@ -297,7 +297,7 @@ body.dark-mode .confirmation-modal .confirmation-dialog select { body.dark-mode .confirmation-modal .confirmation-dialog select:focus, body.dark-mode .confirmation-modal .confirmation-dialog input[type="text"]:focus { - border-color: #667eea !important; + border-color: var(--color-primary) !important; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important; } diff --git a/tauri-app/frontend/styles/navigation.css b/tauri-app/frontend/styles/navigation.css index 8ed8afa..19339d9 100644 --- a/tauri-app/frontend/styles/navigation.css +++ b/tauri-app/frontend/styles/navigation.css @@ -8,7 +8,7 @@ /* Sidebar */ .sidebar { width: 250px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--color-primary-gradient); color: white; display: flex; flex-direction: column; @@ -154,7 +154,7 @@ body.dark-mode .nostr-mail-logo-dark { bottom: 100%; left: 0; right: 0; - background: #5a6fd6; + background: var(--color-primary-hover); border-radius: 8px; margin-bottom: 4px; box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.25); diff --git a/tauri-app/frontend/styles/qr-scanner.css b/tauri-app/frontend/styles/qr-scanner.css index 7e1f1e5..9339e93 100644 --- a/tauri-app/frontend/styles/qr-scanner.css +++ b/tauri-app/frontend/styles/qr-scanner.css @@ -28,7 +28,7 @@ } #qr-reader__scan_region { - border: 2px solid #667eea; + border: 2px solid var(--color-primary); border-radius: 8px; } diff --git a/tauri-app/frontend/styles/relay.css b/tauri-app/frontend/styles/relay.css index 49c10c2..dfd5583 100644 --- a/tauri-app/frontend/styles/relay.css +++ b/tauri-app/frontend/styles/relay.css @@ -193,7 +193,7 @@ body.dark-mode .relay-item-actions .relay-toggle-caption { } .relay-item-actions input[type="checkbox"]:checked { - background-color: #667eea; + background-color: var(--color-primary); } .relay-item-actions input[type="checkbox"]:checked::before { diff --git a/tauri-app/frontend/styles/themes.css b/tauri-app/frontend/styles/themes.css new file mode 100644 index 0000000..c2908b4 --- /dev/null +++ b/tauri-app/frontend/styles/themes.css @@ -0,0 +1,251 @@ +/* ============================================================ + Theme system + ------------------------------------------------------------ + Light : default tokens in variables.css (no data-theme / no class) + Dark : legacy body.dark-mode overrides in dark-mode.css (unchanged), + with matching tokens below so brand colors retint too. + Others : body.themed drives the generic token-based surface layer + below; each theme just supplies a token set. + ============================================================ */ + +/* Root paints the theme background before body JS runs (no flash). */ +html { + background-color: var(--color-bg); +} + +/* ---------- Generic surface layer (applies to body.themed) ---------- */ +/* Mirrors the element coverage of dark-mode.css, but purely token-driven, + so any new theme is defined by its custom-property values alone. */ + +body.themed { + background-color: var(--color-bg); + color: var(--color-text); +} + +body.themed .sidebar { + background: var(--color-sidebar); + color: var(--color-sidebar-text); + box-shadow: 2px 0 10px rgba(0, 0, 0, 0.25); +} + +body.themed .sidebar-header, +body.themed .sidebar-nav { + color: var(--color-sidebar-text); +} + +body.themed .main-content { + background-color: var(--color-surface); + color: var(--color-text); +} + +body.themed .tab-header { + background-color: var(--color-surface-alt); + border-bottom: 1px solid var(--color-border); + color: var(--color-text); +} + +body.themed .tab-header h2 { + color: var(--color-text); +} + +body.themed .nav-item { + color: var(--color-sidebar-text); +} + +body.themed .nav-item.active { + background-color: rgba(255, 255, 255, 0.15); + border-left: 4px solid var(--color-sidebar-text); +} + +body.themed .nav-item:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +body.themed .btn-primary { + background: var(--color-primary-gradient); + color: var(--color-on-primary); +} + +body.themed .btn-secondary { + background-color: var(--color-surface-alt); + color: var(--color-text); + border: 2px solid var(--color-border); +} + +body.themed .btn-danger { + background-color: var(--color-danger); + color: #fff; +} + +body.themed .email-item { + background-color: var(--color-surface); + color: var(--color-text); + border-bottom: 1px solid var(--color-border); +} + +body.themed .email-item.unread { + background-color: var(--color-surface-alt); + border-left: 4px solid var(--color-primary); +} + +body.themed .modal { + background-color: var(--color-surface); + color: var(--color-text); +} + +body.themed .modal-header { + border-bottom: 1px solid var(--color-border); +} + +body.themed .form-group input, +body.themed .form-group textarea { + background-color: var(--color-input-bg); + color: var(--color-text); + border: 2px solid var(--color-border); +} + +body.themed .form-group input:focus, +body.themed .form-group textarea:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); +} + +body.themed .icon-button, +body.themed .sidebar .icon-button { + color: var(--color-sidebar-text); +} + +/* ============================================================ + Theme token sets + ============================================================ */ + +/* --- Dark (tokens only; visual overrides live in dark-mode.css) --- */ +:root[data-theme="dark"] { + --color-bg: #181a20; + --color-surface: #232946; + --color-surface-alt: #1e2138; + --color-text: #e0e0e0; + --color-text-muted: rgba(224, 224, 224, 0.6); + --color-border: #232946; + --color-input-bg: #181a20; + --color-sidebar: linear-gradient(135deg, #232946 0%, #121629 100%); + --color-sidebar-text: #e0e0e0; + --color-danger: #c82333; +} + +/* --- High contrast (accessibility) --- */ +:root[data-theme="high-contrast"] { + --color-primary: #ffd400; + --color-primary-dark: #ffaa00; + --color-primary-hover: #ffe14d; + --color-primary-gradient: linear-gradient(135deg, #ffd400 0%, #ffaa00 100%); + --color-on-primary: #000000; + --color-accent: #ffd400; + --color-bg: #000000; + --color-surface: #000000; + --color-surface-alt: #0a0a0a; + --color-text: #ffffff; + --color-text-muted: #cccccc; + --color-border: #ffffff; + --color-input-bg: #000000; + --color-sidebar: #000000; + --color-sidebar-text: #ffffff; + --color-danger: #ff5a5a; +} + +/* --- Ocean (cool blue) --- */ +:root[data-theme="ocean"] { + --color-primary: #2b9dc9; + --color-primary-dark: #1b6f9c; + --color-primary-hover: #37b0dd; + --color-primary-gradient: linear-gradient(135deg, #2b9dc9 0%, #1b6f9c 100%); + --color-on-primary: #ffffff; + --color-accent: #2b9dc9; + --color-bg: #0b1f2a; + --color-surface: #12303f; + --color-surface-alt: #0e2734; + --color-text: #e6f3f8; + --color-text-muted: rgba(230, 243, 248, 0.6); + --color-border: #1c4256; + --color-input-bg: #0b1f2a; + --color-sidebar: linear-gradient(135deg, #12303f 0%, #071620 100%); + --color-sidebar-text: #e6f3f8; + --color-danger: #e5484d; +} + +/* --- Forest (warm green) --- */ +:root[data-theme="forest"] { + --color-primary: #3fae6b; + --color-primary-dark: #2b7d4c; + --color-primary-hover: #4bc47c; + --color-primary-gradient: linear-gradient(135deg, #3fae6b 0%, #2b7d4c 100%); + --color-on-primary: #ffffff; + --color-accent: #3fae6b; + --color-bg: #14211a; + --color-surface: #1c3227; + --color-surface-alt: #172a20; + --color-text: #e7f4ec; + --color-text-muted: rgba(231, 244, 236, 0.6); + --color-border: #274838; + --color-input-bg: #14211a; + --color-sidebar: linear-gradient(135deg, #1c3227 0%, #0c160f 100%); + --color-sidebar-text: #e7f4ec; + --color-danger: #e5484d; +} + +/* ============================================================ + Settings → Appearance theme picker + ============================================================ */ +.theme-picker { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.theme-swatch { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 8px; + width: 92px; + background: transparent; + border: 2px solid var(--color-border); + border-radius: var(--radius-md); + cursor: pointer; + transition: border-color 0.15s ease, transform 0.15s ease; + color: inherit; +} + +.theme-swatch:hover { + transform: translateY(-2px); +} + +.theme-swatch[aria-checked="true"] { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); +} + +.theme-swatch-preview { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 44px; + border-radius: var(--radius-sm); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.theme-swatch-dot { + width: 14px; + height: 14px; + border-radius: 50%; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); +} + +.theme-swatch-name { + font-size: 12px; + line-height: 1.2; + text-align: center; +} diff --git a/tauri-app/frontend/styles/variables.css b/tauri-app/frontend/styles/variables.css index 506e248..f901ebb 100644 --- a/tauri-app/frontend/styles/variables.css +++ b/tauri-app/frontend/styles/variables.css @@ -1,5 +1,34 @@ /* CSS Custom Properties */ :root { + /* ============================================================ + Color tokens (Light theme = defaults) + Themes override these under :root[data-theme="..."] in themes.css. + ============================================================ */ + + /* Brand / primary */ + --color-primary: #667eea; + --color-primary-dark: #764ba2; + --color-primary-hover: #5568d3; + --color-primary-gradient: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%); + --color-on-primary: #ffffff; + --color-accent: var(--color-primary); + + /* Surfaces & text */ + --color-bg: #f5f5f5; + --color-surface: #ffffff; + --color-surface-alt: #fafafa; + --color-text: #333333; + --color-text-muted: #888888; + --color-border: #e0e0e0; + --color-input-bg: #f8f9ff; + + /* Sidebar */ + --color-sidebar: var(--color-primary-gradient); + --color-sidebar-text: #ffffff; + + /* Status */ + --color-danger: #dc3545; + /* Spacing - Safe to add */ --spacing-xs: 4px; --spacing-sm: 8px;