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
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;