diff --git a/packages/client/src/App.vue b/packages/client/src/App.vue
index c1e52cf..6fcfcd9 100644
--- a/packages/client/src/App.vue
+++ b/packages/client/src/App.vue
@@ -1,17 +1,22 @@
-
+
diff --git a/packages/client/src/assets/dance-dark.mp4 b/packages/client/src/assets/dance-dark.mp4
new file mode 100644
index 0000000..33197d1
Binary files /dev/null and b/packages/client/src/assets/dance-dark.mp4 differ
diff --git a/packages/client/src/assets/dance-light.mp4 b/packages/client/src/assets/dance-light.mp4
new file mode 100644
index 0000000..654e08f
Binary files /dev/null and b/packages/client/src/assets/dance-light.mp4 differ
diff --git a/packages/client/src/assets/dance.mp4 b/packages/client/src/assets/dance.mp4
index 34144c8..d1f2755 100644
Binary files a/packages/client/src/assets/dance.mp4 and b/packages/client/src/assets/dance.mp4 differ
diff --git a/packages/client/src/assets/thinking-dark.mp4 b/packages/client/src/assets/thinking-dark.mp4
new file mode 100644
index 0000000..33197d1
Binary files /dev/null and b/packages/client/src/assets/thinking-dark.mp4 differ
diff --git a/packages/client/src/assets/thinking-light.mp4 b/packages/client/src/assets/thinking-light.mp4
new file mode 100644
index 0000000..654e08f
Binary files /dev/null and b/packages/client/src/assets/thinking-light.mp4 differ
diff --git a/packages/client/src/components/hermes/chat/ChatInput.vue b/packages/client/src/components/hermes/chat/ChatInput.vue
index 4d8db0a..7f0390c 100644
--- a/packages/client/src/components/hermes/chat/ChatInput.vue
+++ b/packages/client/src/components/hermes/chat/ChatInput.vue
@@ -331,7 +331,7 @@ function isImage(type: string): boolean {
border-radius: 50%;
border: none;
background: rgba(0, 0, 0, 0.5);
- color: #fff;
+ color: var(--text-on-overlay);
display: flex;
align-items: center;
justify-content: center;
@@ -394,8 +394,8 @@ function isImage(type: string): boolean {
// Drag-over state
.input-wrapper.drag-over {
- border-color: #4a90d9;
+ border-color: var(--accent-info);
border-style: dashed;
- background-color: rgba(74, 144, 217, 0.04);
+ background-color: rgba(var(--accent-info-rgb), 0.04);
}
diff --git a/packages/client/src/components/hermes/chat/MarkdownRenderer.vue b/packages/client/src/components/hermes/chat/MarkdownRenderer.vue
index 734158a..c28fce7 100644
--- a/packages/client/src/components/hermes/chat/MarkdownRenderer.vue
+++ b/packages/client/src/components/hermes/chat/MarkdownRenderer.vue
@@ -105,7 +105,7 @@ const renderedHtml = computed(() => md.render(props.content))
}
th {
- background: rgba($accent-primary, 0.08);
+ background: rgba(var(--accent-primary-rgb), 0.08);
color: $text-primary;
font-weight: 600;
}
@@ -189,4 +189,20 @@ const renderedHtml = computed(() => md.render(props.content))
.hljs-title\.function_ { color: #1a1a1a; }
.hljs-params { color: #2a2a2a; }
.hljs-meta { color: #999999; }
+
+// Dark mode highlight.js — inverted pure ink
+.dark .hljs { color: #d0d0d0; }
+.dark .hljs-keyword,
+.dark .hljs-selector-tag { color: #f0f0f0; font-weight: 600; }
+.dark .hljs-string,
+.dark .hljs-attr { color: #aaaaaa; }
+.dark .hljs-number { color: #cccccc; }
+.dark .hljs-comment { color: #666666; font-style: italic; }
+.dark .hljs-built_in { color: #bbbbbb; }
+.dark .hljs-type { color: #c6c6c6; }
+.dark .hljs-variable { color: #f0f0f0; }
+.dark .hljs-title,
+.dark .hljs-title\.function_ { color: #f0f0f0; }
+.dark .hljs-params { color: #d0d0d0; }
+.dark .hljs-meta { color: #666666; }
diff --git a/packages/client/src/components/hermes/chat/MessageItem.vue b/packages/client/src/components/hermes/chat/MessageItem.vue
index 8636b93..6f50254 100644
--- a/packages/client/src/components/hermes/chat/MessageItem.vue
+++ b/packages/client/src/components/hermes/chat/MessageItem.vue
@@ -201,7 +201,7 @@ const formattedToolResult = computed(() => {
.message-bubble {
background-color: $msg-user-bg;
- border-radius: $radius-md $radius-md 4px $radius-md;
+ border-radius: 10px;
}
}
@@ -223,7 +223,7 @@ const formattedToolResult = computed(() => {
.message-bubble {
background-color: $msg-assistant-bg;
- border-radius: $radius-md $radius-md $radius-md 4px;
+ border-radius: 10px;
}
}
@@ -238,7 +238,7 @@ const formattedToolResult = computed(() => {
border-left: 3px solid $warning;
border-radius: $radius-sm;
max-width: 80%;
- background-color: rgba($warning, 0.06);
+ background-color: rgba(var(--warning-rgb), 0.06);
}
}
}
@@ -261,6 +261,7 @@ const formattedToolResult = computed(() => {
font-size: 14px;
line-height: 1.65;
word-break: break-word;
+ border-radius: 10px;
}
.msg-attachments {
@@ -315,6 +316,10 @@ const formattedToolResult = computed(() => {
color: $text-muted;
margin-top: 4px;
padding: 0 4px;
+
+ .dark & {
+ color: #999999;
+ }
}
.tool-line {
@@ -369,7 +374,7 @@ const formattedToolResult = computed(() => {
.tool-error-badge {
font-size: 9px;
color: $error;
- background: rgba($error, 0.08);
+ background: rgba(var(--error-rgb), 0.08);
padding: 0 4px;
border-radius: 3px;
line-height: 14px;
diff --git a/packages/client/src/components/hermes/chat/MessageList.vue b/packages/client/src/components/hermes/chat/MessageList.vue
index 9fe4509..27570b8 100644
--- a/packages/client/src/components/hermes/chat/MessageList.vue
+++ b/packages/client/src/components/hermes/chat/MessageList.vue
@@ -91,7 +91,11 @@ watch(currentToolCalls, scrollToBottom)
display: flex;
flex-direction: column;
gap: 16px;
- background-color: #ffffff;
+ background-color: $bg-card;
+
+ .dark & {
+ background-color: #333333;
+ }
}
.empty-state {
diff --git a/packages/client/src/components/hermes/jobs/JobCard.vue b/packages/client/src/components/hermes/jobs/JobCard.vue
index be6be2a..f2afbcb 100644
--- a/packages/client/src/components/hermes/jobs/JobCard.vue
+++ b/packages/client/src/components/hermes/jobs/JobCard.vue
@@ -152,7 +152,7 @@ async function handleDelete() {
transition: border-color $transition-fast;
&:hover {
- border-color: rgba($accent-primary, 0.3);
+ border-color: rgba(var(--accent-primary-rgb), 0.3);
}
}
@@ -180,22 +180,22 @@ async function handleDelete() {
font-weight: 500;
&.success {
- background: rgba($success, 0.12);
+ background: rgba(var(--success-rgb), 0.12);
color: $success;
}
&.info {
- background: rgba($accent-primary, 0.12);
+ background: rgba(var(--accent-primary-rgb), 0.12);
color: $accent-primary;
}
&.warning {
- background: rgba($warning, 0.12);
+ background: rgba(var(--warning-rgb), 0.12);
color: $warning;
}
&.error {
- background: rgba($error, 0.12);
+ background: rgba(var(--error-rgb), 0.12);
color: $error;
}
}
diff --git a/packages/client/src/components/hermes/models/ProviderCard.vue b/packages/client/src/components/hermes/models/ProviderCard.vue
index 96af61f..a31a80a 100644
--- a/packages/client/src/components/hermes/models/ProviderCard.vue
+++ b/packages/client/src/components/hermes/models/ProviderCard.vue
@@ -70,7 +70,7 @@ async function handleDelete() {
transition: border-color $transition-fast;
&:hover {
- border-color: rgba($accent-primary, 0.3);
+ border-color: rgba(var(--accent-primary-rgb), 0.3);
}
}
@@ -98,12 +98,12 @@ async function handleDelete() {
font-weight: 500;
&.builtin {
- background: rgba($accent-primary, 0.12);
+ background: rgba(var(--accent-primary-rgb), 0.12);
color: $accent-primary;
}
&.custom {
- background: rgba($success, 0.12);
+ background: rgba(var(--success-rgb), 0.12);
color: $success;
}
}
diff --git a/packages/client/src/components/hermes/profiles/ProfileCard.vue b/packages/client/src/components/hermes/profiles/ProfileCard.vue
index 69db9f4..94466f8 100644
--- a/packages/client/src/components/hermes/profiles/ProfileCard.vue
+++ b/packages/client/src/components/hermes/profiles/ProfileCard.vue
@@ -181,11 +181,11 @@ async function handleExport() {
transition: border-color $transition-fast;
&:hover {
- border-color: rgba($accent-primary, 0.3);
+ border-color: rgba(var(--accent-primary-rgb), 0.3);
}
&.active {
- border-color: rgba($success, 0.4);
+ border-color: rgba(var(--success-rgb), 0.4);
}
}
diff --git a/packages/client/src/components/hermes/settings/DisplaySettings.vue b/packages/client/src/components/hermes/settings/DisplaySettings.vue
index 38b96a8..35491ad 100644
--- a/packages/client/src/components/hermes/settings/DisplaySettings.vue
+++ b/packages/client/src/components/hermes/settings/DisplaySettings.vue
@@ -1,12 +1,20 @@
+
+
+
save({ streaming: v })" />
diff --git a/packages/client/src/components/hermes/settings/PlatformCard.vue b/packages/client/src/components/hermes/settings/PlatformCard.vue
index d541b7f..62b8d49 100644
--- a/packages/client/src/components/hermes/settings/PlatformCard.vue
+++ b/packages/client/src/components/hermes/settings/PlatformCard.vue
@@ -57,7 +57,7 @@ const configured = computed(() => {
overflow: hidden;
&.configured {
- border-color: rgba($success, 0.2);
+ border-color: rgba(var(--success-rgb), 0.2);
}
}
@@ -70,7 +70,7 @@ const configured = computed(() => {
user-select: none;
&:hover {
- background-color: rgba($text-primary, 0.03);
+ background-color: rgba(var(--text-primary-rgb), 0.03);
}
}
diff --git a/packages/client/src/components/hermes/skills/SkillDetail.vue b/packages/client/src/components/hermes/skills/SkillDetail.vue
index 7225aad..54b1262 100644
--- a/packages/client/src/components/hermes/skills/SkillDetail.vue
+++ b/packages/client/src/components/hermes/skills/SkillDetail.vue
@@ -185,7 +185,7 @@ watch(() => `${props.category}/${props.skill}`, loadSkill, { immediate: true })
border-radius: 4px;
&:hover {
- background: rgba($accent-primary, 0.06);
+ background: rgba(var(--accent-primary-rgb), 0.06);
}
}
diff --git a/packages/client/src/components/hermes/skills/SkillList.vue b/packages/client/src/components/hermes/skills/SkillList.vue
index 4a7eb00..1812776 100644
--- a/packages/client/src/components/hermes/skills/SkillList.vue
+++ b/packages/client/src/components/hermes/skills/SkillList.vue
@@ -150,7 +150,7 @@ async function handleToggle(category: string, skillName: string, newEnabled: boo
border-radius: $radius-sm;
&:hover {
- background: rgba($accent-primary, 0.04);
+ background: rgba(var(--accent-primary-rgb), 0.04);
}
}
@@ -174,7 +174,7 @@ async function handleToggle(category: string, skillName: string, newEnabled: boo
.category-count {
font-size: 11px;
color: $text-muted;
- background: rgba($accent-primary, 0.06);
+ background: rgba(var(--accent-primary-rgb), 0.06);
padding: 1px 6px;
border-radius: 8px;
}
@@ -200,12 +200,12 @@ async function handleToggle(category: string, skillName: string, newEnabled: boo
gap: 8px;
&:hover {
- background: rgba($accent-primary, 0.06);
+ background: rgba(var(--accent-primary-rgb), 0.06);
color: $text-primary;
}
&.active {
- background: rgba($accent-primary, 0.1);
+ background: rgba(var(--accent-primary-rgb), 0.1);
color: $text-primary;
font-weight: 500;
}
diff --git a/packages/client/src/components/hermes/usage/DailyTrend.vue b/packages/client/src/components/hermes/usage/DailyTrend.vue
index 7e11310..630dd8f 100644
--- a/packages/client/src/components/hermes/usage/DailyTrend.vue
+++ b/packages/client/src/components/hermes/usage/DailyTrend.vue
@@ -127,6 +127,10 @@ import { computed } from 'vue'
border-radius: 2px 2px 0 0;
min-height: 0;
transition: height 0.3s ease;
+
+ .dark & {
+ background: #66bb6a;
+ }
}
.bar-col {
@@ -140,7 +144,7 @@ import { computed } from 'vue'
left: 50%;
transform: translateX(-50%);
background: $text-primary;
- color: #fff;
+ color: var(--text-on-accent);
padding: 6px 10px;
border-radius: $radius-sm;
font-size: 11px;
diff --git a/packages/client/src/components/hermes/usage/ModelBreakdown.vue b/packages/client/src/components/hermes/usage/ModelBreakdown.vue
index 0290391..e15437f 100644
--- a/packages/client/src/components/hermes/usage/ModelBreakdown.vue
+++ b/packages/client/src/components/hermes/usage/ModelBreakdown.vue
@@ -85,6 +85,10 @@ function formatTokens(n: number): string {
border-radius: 3px;
min-width: 2px;
transition: width 0.3s ease;
+
+ .dark & {
+ background: #66bb6a;
+ }
}
.model-tokens {
diff --git a/packages/client/src/components/layout/AppSidebar.vue b/packages/client/src/components/layout/AppSidebar.vue
index dae2e73..0e16a75 100644
--- a/packages/client/src/components/layout/AppSidebar.vue
+++ b/packages/client/src/components/layout/AppSidebar.vue
@@ -1,5 +1,5 @@
+
+
+
+
+
+
diff --git a/packages/client/src/composables/useTheme.ts b/packages/client/src/composables/useTheme.ts
new file mode 100644
index 0000000..3403de7
--- /dev/null
+++ b/packages/client/src/composables/useTheme.ts
@@ -0,0 +1,57 @@
+import { ref, watch } from 'vue'
+
+export type ThemeMode = 'light' | 'dark' | 'system'
+
+const STORAGE_KEY = 'hermes_theme'
+
+const mode = ref(
+ (localStorage.getItem(STORAGE_KEY) as ThemeMode) || 'system',
+)
+
+const isDark = ref(false)
+
+function applyTheme(dark: boolean) {
+ isDark.value = dark
+ document.documentElement.classList.toggle('dark', dark)
+}
+
+function resolveDark(m: ThemeMode): boolean {
+ if (m === 'system') {
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
+ }
+ return m === 'dark'
+}
+
+// Initial resolve
+applyTheme(resolveDark(mode.value))
+
+// Listen for system preference changes
+const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
+mediaQuery.addEventListener('change', () => {
+ if (mode.value === 'system') {
+ applyTheme(true)
+ }
+})
+
+// Watch mode changes
+watch(mode, (newMode) => {
+ localStorage.setItem(STORAGE_KEY, newMode)
+ applyTheme(resolveDark(newMode))
+})
+
+export function useTheme() {
+ function setMode(m: ThemeMode) {
+ mode.value = m
+ }
+
+ function toggleTheme() {
+ mode.value = isDark.value ? 'light' : 'dark'
+ }
+
+ return {
+ mode,
+ isDark,
+ setMode,
+ toggleTheme,
+ }
+}
diff --git a/packages/client/src/i18n/locales/en.ts b/packages/client/src/i18n/locales/en.ts
index 1705a2d..292cb49 100644
--- a/packages/client/src/i18n/locales/en.ts
+++ b/packages/client/src/i18n/locales/en.ts
@@ -292,6 +292,11 @@ export default {
bellOnCompleteHint: 'Play sound when AI finishes',
busyInputMode: 'Busy Input Mode',
busyInputModeHint: 'Allow input while AI is processing',
+ theme: 'Theme',
+ themeHint: 'Choose light, dark, or follow system preference',
+ themeLight: 'Light',
+ themeDark: 'Dark',
+ themeSystem: 'System',
},
agent: {
maxTurns: 'Max Turns',
diff --git a/packages/client/src/i18n/locales/zh.ts b/packages/client/src/i18n/locales/zh.ts
index 2c832e5..280f93d 100644
--- a/packages/client/src/i18n/locales/zh.ts
+++ b/packages/client/src/i18n/locales/zh.ts
@@ -292,6 +292,11 @@ export default {
bellOnCompleteHint: 'AI 回复完成时播放提示音',
busyInputMode: '忙碌输入模式',
busyInputModeHint: 'AI 处理中仍可输入',
+ theme: '主题',
+ themeHint: '选择浅色、暗色或跟随系统',
+ themeLight: '浅色',
+ themeDark: '暗色',
+ themeSystem: '跟随系统',
},
agent: {
maxTurns: '最大轮次',
diff --git a/packages/client/src/main.ts b/packages/client/src/main.ts
index 24069c1..c683d7a 100644
--- a/packages/client/src/main.ts
+++ b/packages/client/src/main.ts
@@ -5,6 +5,13 @@ import { i18n } from './i18n'
import App from './App.vue'
import './styles/global.scss'
+// Apply dark class before mount to prevent FOUC
+const savedTheme = localStorage.getItem('hermes_theme') || 'system'
+const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
+if (savedTheme === 'dark' || (savedTheme === 'system' && prefersDark)) {
+ document.documentElement.classList.add('dark')
+}
+
// Read token from URL BEFORE router initializes (hash router strips params)
const urlParams = new URLSearchParams(window.location.search)
const hashQuery = window.location.hash.split('?')[1]
diff --git a/packages/client/src/styles/global.scss b/packages/client/src/styles/global.scss
index 1405838..cda21ab 100644
--- a/packages/client/src/styles/global.scss
+++ b/packages/client/src/styles/global.scss
@@ -19,6 +19,15 @@
}
}
+// Theme transition (applied programmatically on toggle, not on load)
+html.theme-transitioning,
+html.theme-transitioning *,
+html.theme-transitioning *::before,
+html.theme-transitioning *::after {
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease,
+ box-shadow 0.3s ease, fill 0.3s ease, stroke 0.3s ease !important;
+}
+
html, body, #app {
height: 100%;
width: 100%;
@@ -67,7 +76,7 @@ a {
}
::selection {
- background: rgba($accent-primary, 0.3);
+ background: rgba(var(--accent-primary-rgb), 0.3);
}
// Shared page header
diff --git a/packages/client/src/styles/theme.ts b/packages/client/src/styles/theme.ts
index 75b63ac..26ceaac 100644
--- a/packages/client/src/styles/theme.ts
+++ b/packages/client/src/styles/theme.ts
@@ -1,6 +1,6 @@
import type { GlobalThemeOverrides } from 'naive-ui'
-export const themeOverrides: GlobalThemeOverrides = {
+export const lightThemeOverrides: GlobalThemeOverrides = {
common: {
primaryColor: '#333333',
primaryColorHover: '#1a1a1a',
@@ -69,3 +69,83 @@ export const themeOverrides: GlobalThemeOverrides = {
borderRadius: '6px',
},
}
+
+export const darkThemeOverrides: GlobalThemeOverrides = {
+ common: {
+ primaryColor: '#e0e0e0',
+ primaryColorHover: '#f5f5f5',
+ primaryColorPressed: '#ffffff',
+ primaryColorSuppl: '#e0e0e0',
+ bodyColor: '#1a1a1a',
+ cardColor: '#2a2a2a',
+ modalColor: '#2a2a2a',
+ popoverColor: '#2a2a2a',
+ tableColor: '#2a2a2a',
+ inputColor: '#2a2a2a',
+ actionColor: '#252525',
+ textColorBase: '#e0e0e0',
+ textColor1: '#e0e0e0',
+ textColor2: '#a0a0a0',
+ textColor3: '#666666',
+ dividerColor: '#3a3a3a',
+ borderColor: '#3a3a3a',
+ hoverColor: 'rgba(255, 255, 255, 0.06)',
+ borderRadius: '8px',
+ borderRadiusSmall: '6px',
+ fontSize: '14px',
+ fontSizeMedium: '14px',
+ heightMedium: '36px',
+ fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
+ fontFamilyMono: 'JetBrains Mono, Fira Code, Consolas, monospace',
+ },
+ Layout: {
+ color: '#1a1a1a',
+ siderColor: '#202020',
+ headerColor: '#1a1a1a',
+ },
+ Menu: {
+ itemTextColorActive: '#e0e0e0',
+ itemTextColorActiveHover: '#e0e0e0',
+ itemTextColorChildActive: '#e0e0e0',
+ itemIconColorActive: '#e0e0e0',
+ itemIconColorActiveHover: '#ffffff',
+ itemColorActive: 'rgba(255, 255, 255, 0.08)',
+ itemColorActiveHover: 'rgba(255, 255, 255, 0.12)',
+ arrowColorActive: '#e0e0e0',
+ },
+ Button: {
+ textColorPrimary: '#1a1a1a',
+ colorPrimary: '#e0e0e0',
+ colorHoverPrimary: '#f5f5f5',
+ colorPressedPrimary: '#ffffff',
+ },
+ Input: {
+ color: '#2a2a2a',
+ colorFocus: '#2a2a2a',
+ border: '1px solid #3a3a3a',
+ borderHover: '1px solid #666666',
+ borderFocus: '1px solid #e0e0e0',
+ placeholderColor: '#666666',
+ caretColor: '#e0e0e0',
+ },
+ Card: {
+ color: '#2a2a2a',
+ borderColor: '#3a3a3a',
+ },
+ Modal: {
+ color: '#2a2a2a',
+ },
+ Tag: {
+ borderRadius: '6px',
+ },
+ Switch: {
+ railColor: '#3a3a3a',
+ railColorActive: '#66bb6a',
+ loadingColor: '#e0e0e0',
+ opacityDisabled: 0.4,
+ },
+}
+
+export function getThemeOverrides(isDark: boolean): GlobalThemeOverrides {
+ return isDark ? darkThemeOverrides : lightThemeOverrides
+}
diff --git a/packages/client/src/styles/variables.scss b/packages/client/src/styles/variables.scss
index eada616..79b433f 100644
--- a/packages/client/src/styles/variables.scss
+++ b/packages/client/src/styles/variables.scss
@@ -1,41 +1,150 @@
// 黑白水墨 — Pure Ink
// 纯黑白灰,无彩色
+// 支持 light / dark 双主题
+
+// ─── CSS Custom Properties ─────────────────────────────────────
+
+:root {
+ // Backgrounds
+ --bg-primary: #fafafa;
+ --bg-secondary: #f0f0f0;
+ --bg-sidebar: #f5f5f5;
+ --bg-card: #ffffff;
+ --bg-card-hover: #fafafa;
+ --bg-input: #ffffff;
+
+ // Borders
+ --border-color: #e0e0e0;
+ --border-light: #ebebeb;
+
+ // Accent
+ --accent-primary: #333333;
+ --accent-hover: #1a1a1a;
+ --accent-muted: #888888;
+
+ // Text
+ --text-primary: #1a1a1a;
+ --text-secondary: #666666;
+ --text-muted: #999999;
+
+ // Status
+ --success: #2e7d32;
+ --error: #c62828;
+ --warning: #f57f17;
+
+ // Message bubbles
+ --msg-user-bg: #f5f5f5;
+ --msg-assistant-bg: #f5f5f5;
+ --msg-system-border: #bdbdbd;
+
+ // Code
+ --code-bg: #f4f4f4;
+
+ // Utility
+ --text-on-accent: #ffffff;
+ --text-on-overlay: #ffffff;
+ --accent-info: #4a90d9;
+
+ // RGB components (for rgba() usage)
+ --accent-primary-rgb: 51, 51, 51;
+ --accent-hover-rgb: 26, 26, 26;
+ --text-primary-rgb: 26, 26, 26;
+ --text-muted-rgb: 153, 153, 153;
+ --success-rgb: 46, 125, 50;
+ --error-rgb: 198, 40, 40;
+ --warning-rgb: 245, 127, 23;
+ --accent-info-rgb: 74, 144, 217;
+}
+
+.dark {
+ // Backgrounds
+ --bg-primary: #1a1a1a;
+ --bg-secondary: #252525;
+ --bg-sidebar: #202020;
+ --bg-card: #333333;
+ --bg-card-hover: #333333;
+ --bg-input: #2a2a2a;
+
+ // Borders
+ --border-color: #3a3a3a;
+ --border-light: #333333;
+
+ // Accent
+ --accent-primary: #e0e0e0;
+ --accent-hover: #f5f5f5;
+ --accent-muted: #888888;
+
+ // Text
+ --text-primary: #e0e0e0;
+ --text-secondary: #a0a0a0;
+ --text-muted: #666666;
+
+ // Status
+ --success: #66bb6a;
+ --error: #ef5350;
+ --warning: #ffb74d;
+
+ // Message bubbles
+ --msg-user-bg: #252525;
+ --msg-assistant-bg: #252525;
+ --msg-system-border: #555555;
+
+ // Code
+ --code-bg: #1e1e1e;
+
+ // Utility
+ --text-on-accent: #1a1a1a;
+ --text-on-overlay: #ffffff;
+ --accent-info: #6ba3d6;
+
+ // RGB components
+ --accent-primary-rgb: 224, 224, 224;
+ --accent-hover-rgb: 245, 245, 245;
+ --text-primary-rgb: 224, 224, 224;
+ --text-muted-rgb: 102, 102, 102;
+ --success-rgb: 102, 187, 106;
+ --error-rgb: 239, 83, 80;
+ --warning-rgb: 255, 183, 77;
+ --accent-info-rgb: 107, 163, 214;
+}
+
+// ─── SCSS Variables (delegate to CSS custom properties) ────────
// Backgrounds
-$bg-primary: #fafafa;
-$bg-secondary: #f0f0f0;
-$bg-sidebar: #f5f5f5;
-$bg-card: #ffffff;
-$bg-card-hover: #fafafa;
-$bg-input: #ffffff;
+$bg-primary: var(--bg-primary);
+$bg-secondary: var(--bg-secondary);
+$bg-sidebar: var(--bg-sidebar);
+$bg-card: var(--bg-card);
+$bg-card-hover: var(--bg-card-hover);
+$bg-input: var(--bg-input);
// Borders
-$border-color: #e0e0e0;
-$border-light: #ebebeb;
+$border-color: var(--border-color);
+$border-light: var(--border-light);
// Accent
-$accent-primary: #333333;
-$accent-hover: #1a1a1a;
-$accent-muted: #888888;
+$accent-primary: var(--accent-primary);
+$accent-hover: var(--accent-hover);
+$accent-muted: var(--accent-muted);
// Text
-$text-primary: #1a1a1a;
-$text-secondary: #666666;
-$text-muted: #999999;
+$text-primary: var(--text-primary);
+$text-secondary: var(--text-secondary);
+$text-muted: var(--text-muted);
// Status
-$success: #2e7d32;
-$error: #c62828;
-$warning: #f57f17;
+$success: var(--success);
+$error: var(--error);
+$warning: var(--warning);
$info: $accent-primary;
// Message bubbles
-$msg-user-bg: #e8e8e8;
-$msg-assistant-bg: #f5f5f5;
-$msg-system-border: #bdbdbd;
+$msg-user-bg: var(--msg-user-bg);
+$msg-assistant-bg: var(--msg-assistant-bg);
+$msg-system-border: var(--msg-system-border);
// Code
-$code-bg: #f4f4f4;
+$code-bg: var(--code-bg);
// Typography
$font-ui: 'Inter', system-ui, -apple-system, sans-serif;
diff --git a/packages/client/src/views/LoginView.vue b/packages/client/src/views/LoginView.vue
index 1d632fe..ebfe6d8 100644
--- a/packages/client/src/views/LoginView.vue
+++ b/packages/client/src/views/LoginView.vue
@@ -159,7 +159,7 @@ async function handleLogin() {
border: none;
border-radius: $radius-sm;
background: $text-primary;
- color: #fff;
+ color: var(--text-on-accent);
font-size: 15px;
font-weight: 500;
cursor: pointer;
diff --git a/packages/client/src/views/hermes/LogsView.vue b/packages/client/src/views/hermes/LogsView.vue
index 749570e..f2752b1 100644
--- a/packages/client/src/views/hermes/LogsView.vue
+++ b/packages/client/src/views/hermes/LogsView.vue
@@ -215,7 +215,7 @@ onMounted(async () => {
border-left: 2px solid transparent;
&:hover {
- background-color: rgba($accent-primary, 0.03);
+ background-color: rgba(var(--accent-primary-rgb), 0.03);
}
&.level-error {
@@ -225,7 +225,7 @@ onMounted(async () => {
&.level-warning {
border-left-color: $warning;
- .log-message { color: #d9720f; }
+ .log-message { color: $warning; }
}
}
@@ -244,10 +244,10 @@ onMounted(async () => {
min-width: 42px;
text-align: center;
- &.level-error { background: rgba($error, 0.12); color: $error; }
- &.level-warning { background: rgba($warning, 0.12); color: #d9720f; }
- &.level-debug { background: rgba($accent-primary, 0.06); color: $text-muted; }
- &.level-info { background: rgba($accent-primary, 0.06); color: $text-muted; }
+ &.level-error { background: rgba(var(--error-rgb), 0.12); color: $error; }
+ &.level-warning { background: rgba(var(--warning-rgb), 0.12); color: $warning; }
+ &.level-debug { background: rgba(var(--accent-primary-rgb), 0.06); color: $text-muted; }
+ &.level-info { background: rgba(var(--accent-primary-rgb), 0.06); color: $text-muted; }
}
.log-logger {
diff --git a/packages/client/src/views/hermes/SkillsView.vue b/packages/client/src/views/hermes/SkillsView.vue
index 84ef9bc..544122d 100644
--- a/packages/client/src/views/hermes/SkillsView.vue
+++ b/packages/client/src/views/hermes/SkillsView.vue
@@ -163,7 +163,7 @@ function handleSelect(category: string, skill: string) {
border-radius: $radius-sm;
&:hover {
- background: rgba($accent-primary, 0.06);
+ background: rgba(var(--accent-primary-rgb), 0.06);
}
}
diff --git a/packages/client/src/views/hermes/TerminalView.vue b/packages/client/src/views/hermes/TerminalView.vue
index 51b828b..eb64b12 100644
--- a/packages/client/src/views/hermes/TerminalView.vue
+++ b/packages/client/src/views/hermes/TerminalView.vue
@@ -562,7 +562,7 @@ onUnmounted(() => {
margin-bottom: 2px;
&:hover {
- background: rgba($accent-primary, 0.06);
+ background: rgba(var(--accent-primary-rgb), 0.06);
color: $text-primary;
.session-item-delete {
@@ -571,7 +571,7 @@ onUnmounted(() => {
}
&.active {
- background: rgba($accent-primary, 0.1);
+ background: rgba(var(--accent-primary-rgb), 0.1);
color: $text-primary;
font-weight: 500;
}
@@ -604,7 +604,7 @@ onUnmounted(() => {
.session-item-shell {
font-size: 10px;
color: $accent-primary;
- background: rgba($accent-primary, 0.08);
+ background: rgba(var(--accent-primary-rgb), 0.08);
padding: 0 5px;
border-radius: 3px;
line-height: 16px;
@@ -634,7 +634,7 @@ onUnmounted(() => {
&:hover {
color: $error;
- background: rgba($error, 0.1);
+ background: rgba(var(--error-rgb), 0.1);
}
}
@@ -648,7 +648,7 @@ onUnmounted(() => {
border-radius: $radius-sm;
&:hover {
- background: rgba($accent-primary, 0.06);
+ background: rgba(var(--accent-primary-rgb), 0.06);
}
}