adopt CYBER neon-dark theme tokens

Replace the Pure Ink light/dark/comic theme system with the CYBER
single-theme dark palette from the reviewed prototype: OKLCH surfaces,
one neon-cyan accent, and monospace-first typography.

Arco components were reading colors from arco.css because no
ConfigProvider was mounted, so the theme values in arco-theme.ts never
took effect. Bridge CYBER into Arco through its --color-* variables in
global.scss instead, and align arco-theme.ts to match.

Brightness and style switches are gone: useTheme now pins dark, the
comic font faces and locale keys are removed, and ThemeSwitch.vue is
deleted along with its sidebar slot and display setting.
This commit is contained in:
2026-10-19 13:04:49 +08:00
parent 145d240dc2
commit 282624f801
12 changed files with 197 additions and 384 deletions
-9
View File
@@ -68,10 +68,6 @@ vi.mock('@/components/layout/LanguageSwitch.vue', () => ({
default: { name: 'LanguageSwitch', template: '<div />' },
}))
vi.mock('@/components/layout/ThemeSwitch.vue', () => ({
default: { name: 'ThemeSwitch', template: '<div />' },
}))
vi.mock('@/components/common/RouteLinkItem.vue', () => ({
default: {
name: 'RouteLinkItem',
@@ -135,7 +131,6 @@ describe('AppSidebar navigation', () => {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
},
},
})
@@ -153,7 +148,6 @@ describe('AppSidebar navigation', () => {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
},
},
})
@@ -181,7 +175,6 @@ describe('AppSidebar navigation', () => {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
},
},
})
@@ -200,7 +193,6 @@ describe('AppSidebar navigation', () => {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
},
},
})
@@ -225,7 +217,6 @@ describe('AppSidebar navigation', () => {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
},
},
})
+20 -6
View File
@@ -15,12 +15,24 @@ describe('client style system', () => {
it('aligns SCSS surfaces and radii with the shared theme tokens', () => {
const variables = readClientFile('styles/variables.scss')
expect(variables).toContain('--bg-card: #ffffff;')
// 深色主题必须重新定义 card surface,否则浅色值会漏到深色模式
expect(variables).toContain('--bg-card: #2a2a2a;')
// CYBER 是单色暗色主题:卡片面必须是暗色,且亮度分层(page < sidebar < card < input)。
expect(variables).toContain('--bg-card: oklch(0.185 0.014 248);')
expect(variables).toContain('--bg-input: oklch(0.105 0.012 252);')
expect(variables).toContain('$radius-sm: 6px;')
expect(variables).toContain('$radius-md: 8px;')
expect(variables).toContain('$radius-lg: 8px;')
// 亮色主题与 comic 风格已移除:不能再有 .dark / .comic 变体块。
expect(variables).not.toMatch(/^\.dark\s*{/m)
expect(variables).not.toMatch(/^\.comic\s*{/m)
})
it('bridges CYBER tokens into Arco CSS variables', () => {
// 没有 ConfigProvider,Arco 的实际外观由 global.scss 里的 --color-* 覆盖决定。
const globalStyles = readClientFile('styles/global.scss')
expect(globalStyles).toContain('--color-primary: oklch(0.805 0.148 188);')
expect(globalStyles).toContain('--color-bg-1: oklch(0.155 0.012 250);')
expect(globalStyles).toContain('--color-border-1: oklch(0.305 0.018 242);')
})
it('exports the frozen Arco theme tokens', async () => {
@@ -30,17 +42,19 @@ describe('client style system', () => {
expect(arcoTheme.colorBgLayout).toBeTruthy()
expect(arcoTheme.colorBgContainer).toBeTruthy()
expect(arcoTheme.borderRadius).toBeTypeOf('number')
expect(arcoTheme.fontFamily).toContain('Inter')
expect(arcoTheme.fontFamily).toContain('PingFang SC')
expect(arcoTheme.fontFamilyMono).toContain('JetBrains Mono')
})
it('carries dark mode through the arco-theme attribute on the root element', () => {
it('pins Arco to dark mode on the root element', () => {
// 不 import useTheme:它在模块顶层读 localStorage,node 环境下会抛。
// 断言源码契约即可,真实行为由 useTheme 自己的测试覆盖。
const source = readClientFile('composables/useTheme.ts')
expect(source).toContain("setAttribute('arco-theme'")
expect(source).toMatch(/'dark' : 'light'|'dark'|'light'/)
// 单主题:不再有 light / comic 变体,只固定 dark。
expect(source).not.toMatch(/classList\.toggle\(['"](dark|comic)/)
expect(source).not.toMatch(/localStorage\.getItem\(['"]hermes_(brightness|style)/)
})
it('keeps mobile inputs at 16px so iOS does not zoom on focus', () => {