From 318570295f566808aede1328bb9816b524503684 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 10:34:59 +0200 Subject: [PATCH] feat: add theme customization with accent colors and light mode --- src/App.vue | 25 ++++++- src/styles/main.css | 72 ++++++++++++++++++ src/views/Settings.vue | 163 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 244 insertions(+), 16 deletions(-) diff --git a/src/App.vue b/src/App.vue index 070ab5c..7ae72d6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,5 @@ diff --git a/src/styles/main.css b/src/styles/main.css index 58141c0..7b08f65 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -34,6 +34,63 @@ --font-mono: 'JetBrains Mono', 'IBM Plex Mono', monospace; } +/* Accent color overrides */ +[data-accent="amber"] { + --color-accent: #D97706; + --color-accent-hover: #B45309; + --color-accent-muted: rgba(217, 119, 6, 0.12); + --color-accent-text: #FBBF24; +} +[data-accent="blue"] { + --color-accent: #3B82F6; + --color-accent-hover: #2563EB; + --color-accent-muted: rgba(59, 130, 246, 0.12); + --color-accent-text: #60A5FA; +} +[data-accent="purple"] { + --color-accent: #8B5CF6; + --color-accent-hover: #7C3AED; + --color-accent-muted: rgba(139, 92, 246, 0.12); + --color-accent-text: #A78BFA; +} +[data-accent="green"] { + --color-accent: #10B981; + --color-accent-hover: #059669; + --color-accent-muted: rgba(16, 185, 129, 0.12); + --color-accent-text: #34D399; +} +[data-accent="red"] { + --color-accent: #EF4444; + --color-accent-hover: #DC2626; + --color-accent-muted: rgba(239, 68, 68, 0.12); + --color-accent-text: #F87171; +} +[data-accent="pink"] { + --color-accent: #EC4899; + --color-accent-hover: #DB2777; + --color-accent-muted: rgba(236, 72, 153, 0.12); + --color-accent-text: #F472B6; +} +[data-accent="cyan"] { + --color-accent: #06B6D4; + --color-accent-hover: #0891B2; + --color-accent-muted: rgba(6, 182, 212, 0.12); + --color-accent-text: #22D3EE; +} + +/* Light mode */ +[data-theme="light"] { + --color-bg-base: #FAFAF9; + --color-bg-surface: #FFFFFF; + --color-bg-elevated: #F5F5F4; + --color-bg-inset: #E7E5E4; + --color-text-primary: #1C1917; + --color-text-secondary: #57534E; + --color-text-tertiary: #A8A29E; + --color-border-subtle: #E7E5E4; + --color-border-visible: #D6D3D1; +} + @layer base { * { margin: 0; @@ -173,3 +230,18 @@ .animate-pulse-colon { animation: pulse-colon 1s ease-in-out infinite; } + +/* Markdown inline styles */ +.markdown-inline strong { font-weight: 600; } +.markdown-inline em { font-style: italic; } +.markdown-inline code { + padding: 0.1em 0.3em; + background: var(--color-bg-elevated); + border-radius: 3px; + font-family: var(--font-mono); + font-size: 0.85em; +} +.markdown-inline a { + color: var(--color-accent-text); + text-decoration: underline; +} diff --git a/src/views/Settings.vue b/src/views/Settings.vue index d4fa3ba..779d706 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -91,6 +91,44 @@ /> + +
+
+
+

Theme

+

Light or dark appearance

+
+
+ +
+
+ +
+
+

Accent Color

+

Primary interface color

+
+
+
+
+
@@ -120,22 +158,76 @@ - -
-
-

Reminder Interval

-

Minutes between reminders while running

+ +
+
+
+

Idle Timeout

+

Minutes before idle pause triggers

+
+
- +
+

Reminder Interval

+

Minutes between reminders while running

+
+ +
+
+ + +
+ + +
+
+

App Tracking Mode

+

What happens when a tracked app leaves focus

+
+
+ +
+
+ + +
+
+

Check Interval

+

How often to check idle & app visibility

+
+
@@ -259,11 +351,38 @@ const activeTab = ref('general') // Settings state const hourlyRate = ref(0) const idleDetection = ref(true) +const idleTimeout = ref(5) const reminderInterval = ref(15) +const appTrackingMode = ref('auto') +const appCheckInterval = ref(5) const zoomLevel = ref(100) const locale = ref('system') const currency = ref('USD') const currencyOptions = getCurrencies() +const themeMode = ref('dark') +const accentColor = ref('amber') + +const themeModes = [ + { value: 'dark', label: 'Dark' }, + { value: 'light', label: 'Light' }, + { value: 'system', label: 'System' }, +] + +const accentColors = [ + { value: 'amber', label: 'Amber', color: '#D97706' }, + { value: 'blue', label: 'Blue', color: '#3B82F6' }, + { value: 'purple', label: 'Purple', color: '#8B5CF6' }, + { value: 'green', label: 'Green', color: '#10B981' }, + { value: 'red', label: 'Red', color: '#EF4444' }, + { value: 'pink', label: 'Pink', color: '#EC4899' }, + { value: 'cyan', label: 'Cyan', color: '#06B6D4' }, +] + +const appTrackingModes = [ + { value: 'auto', label: 'Auto-pause/resume' }, + { value: 'notify', label: 'Notify + auto-resume' }, + { value: 'prompt', label: 'Prompt user' }, +] // Dialog state const showClearDataDialog = ref(false) @@ -312,7 +431,10 @@ async function saveSettings() { try { await settingsStore.updateSetting('hourly_rate', hourlyRate.value.toString()) await settingsStore.updateSetting('idle_detection', idleDetection.value.toString()) + await settingsStore.updateSetting('idle_timeout', idleTimeout.value.toString()) await settingsStore.updateSetting('reminder_interval', reminderInterval.value.toString()) + await settingsStore.updateSetting('app_tracking_mode', appTrackingMode.value) + await settingsStore.updateSetting('app_check_interval', appCheckInterval.value.toString()) } catch (error) { console.error('Failed to save settings:', error) toastStore.error('Failed to save settings') @@ -325,6 +447,12 @@ async function saveLocaleSettings() { await settingsStore.updateSetting('currency', currency.value) } +// Save theme settings +async function saveThemeSettings() { + await settingsStore.updateSetting('theme_mode', themeMode.value) + await settingsStore.updateSetting('accent_color', accentColor.value) +} + // Export all data async function exportData() { try { @@ -362,9 +490,14 @@ onMounted(async () => { hourlyRate.value = parseFloat(settingsStore.settings.hourly_rate) || 0 idleDetection.value = settingsStore.settings.idle_detection !== 'false' + idleTimeout.value = parseInt(settingsStore.settings.idle_timeout) || 5 reminderInterval.value = parseInt(settingsStore.settings.reminder_interval) || 15 + appTrackingMode.value = settingsStore.settings.app_tracking_mode || 'auto' + appCheckInterval.value = parseInt(settingsStore.settings.app_check_interval) || 5 zoomLevel.value = parseInt(settingsStore.settings.ui_zoom) || 100 locale.value = settingsStore.settings.locale || 'system' currency.value = settingsStore.settings.currency || 'USD' + themeMode.value = settingsStore.settings.theme_mode || 'dark' + accentColor.value = settingsStore.settings.accent_color || 'amber' })