feat: add lightweight Guanghu companion themes
This commit is contained in:
parent
508acd0f12
commit
5699882938
@ -468,7 +468,12 @@ function summarizeScenario(scenarioName, scenario, runs) {
|
||||
}
|
||||
|
||||
async function runBenchmarks(baseUrl) {
|
||||
const browser = await chromium.launch({ headless: !options.headful })
|
||||
const browser = await chromium.launch({
|
||||
headless: !options.headful,
|
||||
...(process.env.PLAYWRIGHT_EXECUTABLE_PATH
|
||||
? { executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH }
|
||||
: {}),
|
||||
})
|
||||
const summaries = {}
|
||||
try {
|
||||
for (const scenarioName of options.scenarioNames) {
|
||||
|
||||
101
src/App.css
101
src/App.css
@ -1,73 +1,42 @@
|
||||
/* App layout - minimal CSS, most styling via Tailwind */
|
||||
.app-shell {
|
||||
/* HoloLake Era visual system: the inherited Tolaria dark palette is warm
|
||||
charcoal; override semantic tokens here so every existing panel speaks
|
||||
the same deep-lake blue language as the world entrance. */
|
||||
--surface-app: #071126;
|
||||
--surface-sidebar: #0a1730;
|
||||
--surface-panel: #0d1c38;
|
||||
--surface-card: #102343;
|
||||
--surface-popover: #12284c;
|
||||
--surface-input: #0a1730;
|
||||
--surface-button: #172e55;
|
||||
--surface-dialog: #0d1d3a;
|
||||
--surface-editor: #09162d;
|
||||
--surface-overlay: rgba(2, 7, 18, .68);
|
||||
--text-primary: #edf4ff;
|
||||
--text-secondary: #b9c9e2;
|
||||
--text-tertiary: #8ea4c6;
|
||||
--text-muted: #7188ac;
|
||||
--text-heading: #f3f7ff;
|
||||
--border-default: #1d3a67;
|
||||
--border-subtle: #142b4d;
|
||||
--border-strong: #315588;
|
||||
--border-input: #274a78;
|
||||
--sidebar-border: #17365f;
|
||||
--state-hover: #142b4e;
|
||||
--state-hover-subtle: #0e213f;
|
||||
--state-selected: #173d70;
|
||||
--state-selected-strong: #24538d;
|
||||
--state-active: #1d477e;
|
||||
--accent-blue: #8dbbff;
|
||||
--accent-blue-bg: rgba(105, 165, 255, .23);
|
||||
--accent-blue-light: rgba(105, 165, 255, .16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(58rem 35rem at 82% -16%, var(--guanghu-aurora), transparent 62%),
|
||||
radial-gradient(42rem 30rem at -12% 112%, var(--guanghu-glow), transparent 65%),
|
||||
var(--surface-app);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-primary) 82%, var(--accent-blue));
|
||||
background: var(--surface-app);
|
||||
box-shadow: inset 0 0 0 1px var(--border-primary);
|
||||
}
|
||||
|
||||
.hololake-bar { height: 52px; flex: 0 0 52px; display: flex; align-items: center; gap: 14px; padding: 0 20px; color: #eef4ff; background: linear-gradient(100deg, #08122b, #172d58 52%, #0b1731); border-bottom: 1px solid rgba(153, 189, 255, .28); letter-spacing: .02em; }
|
||||
.hololake-bar { height: 44px; flex: 0 0 44px; display: flex; align-items: center; gap: 12px; padding: 0 16px; color: var(--text-primary); background: var(--surface-panel); border-bottom: 1px solid var(--border-default); letter-spacing: .02em; }
|
||||
.hololake-bar__brand, .hololake-bar__library { border: 0; background: transparent; color: inherit; cursor: pointer; }
|
||||
.hololake-bar__brand { display: inline-flex; align-items: center; gap: 9px; font-size: 16px; font-weight: 650; letter-spacing: .04em; }
|
||||
.hololake-bar__brand b { color: #9cc3ff; font-weight: 500; }
|
||||
.hololake-bar__mark { display: grid; place-items: center; width: 24px; height: 24px; color: #dceaff; border: 1px solid rgba(205, 225, 255, .48); border-radius: 50%; box-shadow: 0 0 22px rgba(96, 165, 250, .65); }
|
||||
.hololake-bar__line { width: 1px; height: 17px; background: rgba(196, 218, 255, .35); }
|
||||
.hololake-bar__context { color: #b7c8e7; font-size: 12px; }
|
||||
.hololake-bar__library { margin-left: auto; padding: 6px 10px; border: 1px solid rgba(185, 211, 255, .36); border-radius: 999px; color: #dceaff; font-size: 12px; }
|
||||
.hololake-home { position: relative; z-index: 2; width: 100%; overflow: auto; padding: clamp(32px, 6vw, 84px); color: #ecf4ff; background: radial-gradient(circle at 75% 10%, rgba(75, 135, 255, .25), transparent 28%), radial-gradient(circle at 12% 92%, rgba(83, 220, 255, .15), transparent 34%), #071126; }
|
||||
.hololake-bar__brand { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 650; letter-spacing: .02em; }
|
||||
.hololake-bar__brand b { color: var(--accent-blue); font-weight: 600; }
|
||||
.hololake-bar__mark { display: grid; place-items: center; width: 20px; height: 20px; color: var(--accent-blue); border: 1px solid var(--border-strong); border-radius: 50%; }
|
||||
.hololake-bar__line { width: 1px; height: 16px; background: var(--border-default); }
|
||||
.hololake-bar__context { color: var(--text-secondary); font-size: 12px; }
|
||||
.hololake-bar__library { margin-left: auto; padding: 5px 9px; border: 1px solid var(--border-default); border-radius: 6px; color: var(--text-secondary); font-size: 12px; }
|
||||
.hololake-bar__library:hover { background: var(--state-hover-subtle); color: var(--text-primary); }
|
||||
.hololake-bar__theme { padding: 5px 9px; border: 1px solid var(--border-default); border-radius: 6px; background: transparent; color: var(--text-secondary); font-size: 12px; cursor: pointer; }
|
||||
.hololake-bar__theme:hover { background: var(--state-hover-subtle); color: var(--text-primary); }
|
||||
.hololake-home { position: relative; z-index: 2; width: 100%; overflow: auto; padding: clamp(28px, 5vw, 64px); color: var(--text-primary); background: var(--surface-app); }
|
||||
.hololake-home__hero { position: relative; max-width: 740px; padding: 24px 0 56px; overflow: hidden; }
|
||||
.hololake-home__halo { position: absolute; right: -110px; top: -180px; width: 390px; aspect-ratio: 1; border: 1px solid rgba(166, 205, 255, .28); border-radius: 50%; box-shadow: 0 0 0 45px rgba(103, 165, 255, .05), 0 0 0 105px rgba(103, 165, 255, .035), 0 0 100px rgba(77, 143, 255, .4); }
|
||||
.hololake-home__eyebrow, .hololake-home__section-head span { color: #8db5f7; font-size: 12px; letter-spacing: .16em; text-transform: uppercase; }
|
||||
.hololake-home__halo { position: absolute; right: -80px; top: -140px; width: 300px; aspect-ratio: 1; border: 1px solid var(--border-default); border-radius: 50%; opacity: .45; }
|
||||
.hololake-home__eyebrow, .hololake-home__section-head span { color: var(--accent-blue); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; }
|
||||
.hololake-home h1 { position: relative; margin: 18px 0 0; font-family: Georgia, 'Times New Roman', serif; font-size: clamp(52px, 7vw, 92px); font-weight: 500; line-height: .95; letter-spacing: -.06em; }
|
||||
.hololake-home h1 em { color: #9cc7ff; font-style: italic; font-weight: 400; }
|
||||
.hololake-home__lead { margin: 20px 0 0; font-size: 18px; letter-spacing: .16em; }
|
||||
.hololake-home__copy { max-width: 560px; margin: 16px 0 0; color: #b9c8df; font-size: 15px; line-height: 1.9; }
|
||||
.hololake-home__copy { max-width: 560px; margin: 16px 0 0; color: var(--text-secondary); font-size: 15px; line-height: 1.8; }
|
||||
.hololake-home__actions { display: flex; align-items: center; gap: 18px; margin-top: 30px; }
|
||||
.hololake-home__primary { padding: 12px 18px; border: 1px solid #b9d5ff; border-radius: 8px; background: linear-gradient(135deg, #c8dcff, #6093e8); color: #061126; font-weight: 700; cursor: pointer; }
|
||||
.hololake-home__primary { padding: 10px 16px; border: 1px solid var(--border-strong); border-radius: 6px; background: var(--accent-blue); color: var(--text-inverse); font-weight: 650; cursor: pointer; }
|
||||
.hololake-home__primary span, .hololake-module button span { margin-left: 12px; }
|
||||
.hololake-home__status { color: #9eb3d4; font-size: 12px; }.hololake-home__status i { display: inline-block; width: 7px; height: 7px; margin-right: 7px; border-radius: 50%; background: #67e8b6; box-shadow: 0 0 12px #67e8b6; }
|
||||
.hololake-home__status { color: var(--text-secondary); font-size: 12px; }.hololake-home__status i { display: inline-block; width: 7px; height: 7px; margin-right: 7px; border-radius: 50%; background: var(--accent-green); }
|
||||
.hololake-home__signal-grid, .hololake-home__module-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
|
||||
.hololake-home__signal-grid article, .hololake-module { border: 1px solid rgba(157, 192, 244, .2); border-radius: 14px; background: linear-gradient(145deg, rgba(30, 57, 105, .55), rgba(12, 25, 51, .78)); box-shadow: inset 0 1px rgba(255,255,255,.06); }
|
||||
.hololake-home__signal-grid article { padding: 18px; }.hololake-home__signal-grid span, .hololake-home__signal-grid small { display: block; color: #91a9ce; font-size: 11px; }.hololake-home__signal-grid strong { display: block; margin: 10px 0 6px; font-size: 15px; }
|
||||
.hololake-home__modules { margin-top: 52px; }.hololake-home__section-head { display: flex; align-items: end; justify-content: space-between; margin-bottom: 14px; }.hololake-home__section-head p { margin: 0; color: #91a9ce; font-size: 13px; }
|
||||
.hololake-module { min-height: 215px; padding: 22px; display: flex; flex-direction: column; }.hololake-module__number { color: #78aaf7; font-family: monospace; font-size: 12px; }.hololake-module h2 { margin: auto 0 8px; font-size: 22px; font-weight: 600; }.hololake-module p { margin: 0; color: #aebed7; font-size: 13px; line-height: 1.7; }.hololake-module button { margin-top: 19px; padding: 0; border: 0; color: #c8dcff; background: transparent; text-align: left; font: inherit; cursor: pointer; }.hololake-module__soon { margin-top: 19px; color: #7185a8; font-size: 12px; }
|
||||
.hololake-home__signal-grid article, .hololake-module { border: 1px solid var(--border-default); border-radius: 8px; background: var(--surface-card); }
|
||||
.hololake-home__signal-grid article { padding: 18px; }.hololake-home__signal-grid span, .hololake-home__signal-grid small { display: block; color: var(--text-secondary); font-size: 11px; }.hololake-home__signal-grid strong { display: block; margin: 10px 0 6px; font-size: 15px; }
|
||||
.hololake-home__modules { margin-top: 44px; }.hololake-home__section-head { display: flex; align-items: end; justify-content: space-between; margin-bottom: 14px; }.hololake-home__section-head p { margin: 0; color: var(--text-secondary); font-size: 13px; }
|
||||
.hololake-module { min-height: 185px; padding: 20px; display: flex; flex-direction: column; }.hololake-module__number { color: var(--accent-blue); font-family: monospace; font-size: 12px; }.hololake-module h2 { margin: auto 0 8px; font-size: 20px; font-weight: 600; }.hololake-module p { margin: 0; color: var(--text-secondary); font-size: 13px; line-height: 1.7; }.hololake-module button { margin-top: 19px; padding: 0; border: 0; color: var(--accent-blue); background: transparent; text-align: left; font: inherit; cursor: pointer; }.hololake-module__soon { margin-top: 19px; color: var(--text-muted); font-size: 12px; }
|
||||
@media (max-width: 780px) { .hololake-home__signal-grid, .hololake-home__module-grid { grid-template-columns: 1fr; }.hololake-bar__context { display: none; }.hololake-home { padding: 28px; } }
|
||||
|
||||
.app {
|
||||
@ -82,7 +51,7 @@
|
||||
min-width: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #10254a, var(--surface-sidebar) 40%, #08152c);
|
||||
background: var(--surface-sidebar);
|
||||
}
|
||||
|
||||
.app__sidebar > * {
|
||||
@ -100,8 +69,7 @@
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
background: linear-gradient(180deg, #0d2040, #091730 48%, #08152c);
|
||||
box-shadow: 14px 0 30px rgba(1, 7, 20, .36);
|
||||
background: var(--surface-sidebar);
|
||||
}
|
||||
|
||||
.app__note-list > * {
|
||||
@ -118,26 +86,7 @@
|
||||
flex-direction: column;
|
||||
container-type: inline-size;
|
||||
container-name: editor;
|
||||
position: relative;
|
||||
background:
|
||||
linear-gradient(var(--guanghu-grid) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--guanghu-grid) 1px, transparent 1px),
|
||||
color-mix(in srgb, var(--surface-editor) 94%, var(--guanghu-glow));
|
||||
background-size: 32px 32px, 32px 32px, auto;
|
||||
}
|
||||
|
||||
.app__editor::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--surface-editor) 28%, transparent), transparent 13rem);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.app__editor > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: var(--surface-editor);
|
||||
}
|
||||
|
||||
.app__editor > * {
|
||||
|
||||
@ -22,6 +22,7 @@ import { McpSetupDialog } from './components/McpSetupDialog'
|
||||
import { NoteRetargetingDialogs } from './components/note-retargeting/NoteRetargetingDialogs'
|
||||
import { StartupScreen } from './components/StartupScreen'
|
||||
import { HoloLakeHome } from './components/HoloLakeHome'
|
||||
import { GUANGHU_THEME_LABELS } from './lib/guanghuTheme'
|
||||
import { useAiAgentsOnboarding } from './hooks/useAiAgentsOnboarding'
|
||||
import { useAiAgentsStatus } from './hooks/useAiAgentsStatus'
|
||||
import { useVaultAiGuidanceStatus } from './hooks/useVaultAiGuidanceStatus'
|
||||
@ -42,6 +43,7 @@ import { useVaultSwitcher } from './hooks/useVaultSwitcher'
|
||||
import { useGitHistory } from './hooks/useGitHistory'
|
||||
import { useUpdater, restartApp } from './hooks/useUpdater'
|
||||
import { useAutoSync } from './hooks/useAutoSync'
|
||||
import { useGuanghuTheme } from './hooks/useGuanghuTheme'
|
||||
import { useConflictResolver } from './hooks/useConflictResolver'
|
||||
import { useVaultConfig } from './hooks/useVaultConfig'
|
||||
import { useOnboarding } from './hooks/useOnboarding'
|
||||
@ -166,7 +168,8 @@ function App() {
|
||||
|
||||
function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | null }) {
|
||||
const aiWorkspaceWindow = false
|
||||
const [showHoloLakeHome, setShowHoloLakeHome] = useState(true)
|
||||
const [showHoloLakeHome, setShowHoloLakeHome] = useState(false)
|
||||
const { theme: guanghuTheme, cycleTheme } = useGuanghuTheme()
|
||||
const [selection, setSelection] = useState<SidebarSelection>(DEFAULT_SELECTION)
|
||||
const [noteListFilter, setNoteListFilter] = useState<NoteListFilter>('open')
|
||||
const [pendingNoteListPdfExportPath, setPendingNoteListPdfExportPath] = useState<string | null>(null)
|
||||
@ -1628,6 +1631,7 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
|
||||
<span className="hololake-bar__line" />
|
||||
<span className="hololake-bar__context">光湖语言人格驱动操作系统</span>
|
||||
<button className="hololake-bar__library" onClick={() => setShowHoloLakeHome(false)}>知识库</button>
|
||||
<button className="hololake-bar__theme" onClick={cycleTheme} title="切换光湖配套主题">主题:{GUANGHU_THEME_LABELS[guanghuTheme]}</button>
|
||||
</header>
|
||||
<div className="app">
|
||||
{sidebarVisible && (
|
||||
|
||||
@ -24,36 +24,23 @@
|
||||
|
||||
.guanghu-portal-card {
|
||||
width: 100%;
|
||||
margin: 1.1rem 0;
|
||||
padding: 1.15rem 1.25rem 1.2rem;
|
||||
border: 1px solid color-mix(in srgb, var(--colors-accent) 46%, var(--colors-border));
|
||||
border-radius: 1rem;
|
||||
background:
|
||||
linear-gradient(135deg, color-mix(in srgb, var(--colors-accent) 16%, transparent), transparent 42%),
|
||||
radial-gradient(circle at 92% 16%, color-mix(in srgb, var(--colors-accent) 25%, transparent), transparent 36%),
|
||||
var(--guanghu-card-highlight, var(--colors-surface));
|
||||
box-shadow:
|
||||
0 0.8rem 2.2rem color-mix(in srgb, black 14%, transparent),
|
||||
inset 0 1px 0 color-mix(in srgb, white 28%, transparent);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
|
||||
margin: .75rem 0;
|
||||
padding: 1rem 1.1rem;
|
||||
border: 1px solid color-mix(in srgb, var(--colors-accent) 32%, var(--colors-border));
|
||||
border-radius: .65rem;
|
||||
background: var(--colors-surface);
|
||||
}
|
||||
|
||||
.guanghu-portal-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 3px;
|
||||
background: linear-gradient(var(--colors-accent), color-mix(in srgb, var(--colors-accent) 12%, transparent));
|
||||
width: 2px;
|
||||
background: var(--colors-accent);
|
||||
}
|
||||
|
||||
.guanghu-portal-card:hover {
|
||||
border-color: color-mix(in srgb, var(--colors-accent) 72%, var(--colors-border));
|
||||
box-shadow:
|
||||
0 1rem 2.6rem color-mix(in srgb, var(--colors-accent) 18%, transparent),
|
||||
inset 0 1px 0 color-mix(in srgb, white 32%, transparent);
|
||||
transform: translateY(-1px);
|
||||
border-color: color-mix(in srgb, var(--colors-accent) 58%, var(--colors-border));
|
||||
}
|
||||
|
||||
.guanghu-portal-card__eyebrow {
|
||||
@ -215,23 +202,19 @@ body.mac-chrome .app:not(:has(.app__sidebar)):not(:has(.app__note-list)) .breadc
|
||||
|
||||
.editor__blocknote-container .bn-editor {
|
||||
width: 100%;
|
||||
padding: 36px 0 72px;
|
||||
padding: 20px 0 48px;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-editor > .bn-block-group {
|
||||
padding: 1.25rem clamp(1rem, 3vw, 2.25rem) 3rem;
|
||||
border: 1px solid color-mix(in srgb, var(--border-primary) 82%, transparent);
|
||||
border-radius: 1.25rem;
|
||||
background: color-mix(in srgb, var(--surface-editor) 86%, transparent);
|
||||
box-shadow: 0 1.3rem 3.5rem color-mix(in srgb, black 8%, transparent);
|
||||
backdrop-filter: blur(16px);
|
||||
padding: 0 clamp(1rem, 3vw, 2.25rem) 2rem;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.app__editor {
|
||||
background-size: 22px 22px, 22px 22px, auto;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-editor > .bn-block-group {
|
||||
border-inline: 0;
|
||||
border-radius: 0;
|
||||
|
||||
30
src/hooks/useGuanghuTheme.ts
Normal file
30
src/hooks/useGuanghuTheme.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
GUANGHU_THEMES,
|
||||
GUANGHU_THEME_STORAGE_KEY,
|
||||
readGuanghuTheme,
|
||||
writeGuanghuTheme,
|
||||
type GuanghuTheme,
|
||||
} from '../lib/guanghuTheme'
|
||||
|
||||
export function useGuanghuTheme(): { theme: GuanghuTheme; cycleTheme: () => void } {
|
||||
const [theme, setTheme] = useState<GuanghuTheme>(() => (
|
||||
typeof window === 'undefined' ? 'native' : readGuanghuTheme(window.localStorage)
|
||||
))
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute('data-guanghu-theme', theme)
|
||||
writeGuanghuTheme(window.localStorage, theme)
|
||||
}, [theme])
|
||||
|
||||
const cycleTheme = useCallback(() => {
|
||||
setTheme(current => {
|
||||
const index = GUANGHU_THEMES.indexOf(current)
|
||||
return GUANGHU_THEMES[(index + 1) % GUANGHU_THEMES.length]
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { theme, cycleTheme }
|
||||
}
|
||||
|
||||
export { GUANGHU_THEME_STORAGE_KEY }
|
||||
@ -180,11 +180,10 @@
|
||||
--link-color: var(--accent-blue);
|
||||
--link-hover: var(--accent-blue-hover);
|
||||
|
||||
/* Guanghu visual layer — an app-owned, ice-lake palette. */
|
||||
--guanghu-aurora: rgba(91, 181, 255, 0.2);
|
||||
--guanghu-glow: rgba(38, 131, 235, 0.15);
|
||||
--guanghu-grid: rgba(42, 113, 188, 0.06);
|
||||
--guanghu-card-highlight: rgba(255, 255, 255, 0.76);
|
||||
/* Guanghu theme extension: palettes only; layout stays Tolaria-native. */
|
||||
--guanghu-aurora: transparent;
|
||||
--guanghu-glow: transparent;
|
||||
--guanghu-grid: transparent;
|
||||
}
|
||||
|
||||
:root.dark,
|
||||
@ -337,11 +336,68 @@
|
||||
--link-color: var(--accent-blue);
|
||||
--link-hover: var(--accent-blue-hover);
|
||||
|
||||
/* Guanghu visual layer — deep lake, not a flat black canvas. */
|
||||
--guanghu-aurora: rgba(76, 174, 255, 0.2);
|
||||
--guanghu-glow: rgba(20, 118, 214, 0.24);
|
||||
--guanghu-grid: rgba(126, 202, 255, 0.055);
|
||||
--guanghu-card-highlight: rgba(28, 54, 80, 0.72);
|
||||
/* Guanghu theme extension: palettes only; layout stays Tolaria-native. */
|
||||
--guanghu-aurora: transparent;
|
||||
--guanghu-glow: transparent;
|
||||
--guanghu-grid: transparent;
|
||||
}
|
||||
|
||||
/* Guanghu companion presets. They change semantic colors only; native
|
||||
Tolaria layout, editor surfaces and interaction density remain intact. */
|
||||
[data-guanghu-theme="lake-light"][data-theme="light"] {
|
||||
--surface-app: #f6fbff;
|
||||
--surface-sidebar: #f0f6fa;
|
||||
--surface-panel: #ffffff;
|
||||
--surface-card: #ffffff;
|
||||
--surface-popover: #ffffff;
|
||||
--surface-editor: #ffffff;
|
||||
--accent-blue: #2b6ea6;
|
||||
--accent-blue-hover: #21577f;
|
||||
--accent-blue-bg: rgba(43, 110, 166, .12);
|
||||
--accent-blue-light: rgba(43, 110, 166, .08);
|
||||
--state-selected: #e4f1f8;
|
||||
--state-selected-strong: #d7eaf4;
|
||||
--state-active: #e4f1f8;
|
||||
--border-focus: #2b6ea6;
|
||||
}
|
||||
|
||||
[data-guanghu-theme="lake-dark"][data-theme="dark"] {
|
||||
--surface-app: #15191d;
|
||||
--surface-sidebar: #1b2025;
|
||||
--surface-panel: #20262c;
|
||||
--surface-card: #242c33;
|
||||
--surface-popover: #292f35;
|
||||
--surface-editor: #15191d;
|
||||
--text-primary: #e6edf2;
|
||||
--text-secondary: #aab8c2;
|
||||
--text-heading: #f1f6f8;
|
||||
--accent-blue: #72b7d6;
|
||||
--accent-blue-hover: #98cee3;
|
||||
--accent-blue-bg: rgba(114, 183, 214, .16);
|
||||
--accent-blue-light: rgba(114, 183, 214, .1);
|
||||
--state-hover: #2a333b;
|
||||
--state-hover-subtle: #252d34;
|
||||
--state-selected: #29414d;
|
||||
--state-selected-strong: #315565;
|
||||
--state-active: #29414d;
|
||||
--border-focus: #72b7d6;
|
||||
}
|
||||
|
||||
[data-guanghu-theme="lake-light"][data-theme="dark"] {
|
||||
--surface-app: #17212a;
|
||||
--surface-sidebar: #1b2833;
|
||||
--surface-panel: #21313d;
|
||||
--surface-card: #263a48;
|
||||
--surface-popover: #2c4352;
|
||||
--surface-editor: #17212a;
|
||||
--accent-blue: #8bc9e5;
|
||||
--accent-blue-hover: #b0def0;
|
||||
--accent-blue-bg: rgba(139, 201, 229, .16);
|
||||
--accent-blue-light: rgba(139, 201, 229, .1);
|
||||
--state-selected: #294858;
|
||||
--state-selected-strong: #31586b;
|
||||
--state-active: #294858;
|
||||
--border-focus: #8bc9e5;
|
||||
}
|
||||
|
||||
/* --- Tailwind v4 theme inline: register colors + radii --- */
|
||||
|
||||
21
src/lib/guanghuTheme.test.ts
Normal file
21
src/lib/guanghuTheme.test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
GUANGHU_THEMES,
|
||||
normalizeGuanghuTheme,
|
||||
} from './guanghuTheme'
|
||||
|
||||
describe('guanghuTheme', () => {
|
||||
it('keeps the companion theme choices explicit and stable', () => {
|
||||
expect(GUANGHU_THEMES).toEqual(['native', 'lake-light', 'lake-dark'])
|
||||
})
|
||||
|
||||
it('falls back to the native Tolaria theme for unknown values', () => {
|
||||
expect(normalizeGuanghuTheme('old-blue-skin')).toBe('native')
|
||||
expect(normalizeGuanghuTheme(null)).toBe('native')
|
||||
})
|
||||
|
||||
it('accepts every supported companion theme', () => {
|
||||
expect(normalizeGuanghuTheme('lake-light')).toBe('lake-light')
|
||||
expect(normalizeGuanghuTheme('lake-dark')).toBe('lake-dark')
|
||||
})
|
||||
})
|
||||
32
src/lib/guanghuTheme.ts
Normal file
32
src/lib/guanghuTheme.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export const GUANGHU_THEME_STORAGE_KEY = 'guanghu-theme'
|
||||
|
||||
export const GUANGHU_THEMES = ['native', 'lake-light', 'lake-dark'] as const
|
||||
export type GuanghuTheme = typeof GUANGHU_THEMES[number]
|
||||
|
||||
export const GUANGHU_THEME_LABELS: Record<GuanghuTheme, string> = {
|
||||
native: '原生',
|
||||
'lake-light': '湖光浅色',
|
||||
'lake-dark': '湖心深色',
|
||||
}
|
||||
|
||||
export function normalizeGuanghuTheme(value: unknown): GuanghuTheme {
|
||||
return typeof value === 'string' && (GUANGHU_THEMES as readonly string[]).includes(value)
|
||||
? value as GuanghuTheme
|
||||
: 'native'
|
||||
}
|
||||
|
||||
export function readGuanghuTheme(storage: Pick<Storage, 'getItem'>): GuanghuTheme {
|
||||
try {
|
||||
return normalizeGuanghuTheme(storage.getItem(GUANGHU_THEME_STORAGE_KEY))
|
||||
} catch {
|
||||
return 'native'
|
||||
}
|
||||
}
|
||||
|
||||
export function writeGuanghuTheme(storage: Pick<Storage, 'setItem'>, theme: GuanghuTheme): void {
|
||||
try {
|
||||
storage.setItem(GUANGHU_THEME_STORAGE_KEY, theme)
|
||||
} catch {
|
||||
// Storage may be unavailable in restricted browser contexts.
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user