dsh-theme-palette 1.3.3 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README-es.md +87 -0
- package/README-hi.md +87 -0
- package/README-pt.md +87 -0
- package/README-zh.md +87 -0
- package/README.md +54 -177
- package/client.js +246 -35
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +11 -5
- package/tools/preview-settings.mjs +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
## 版本一览
|
|
6
6
|
|
|
7
|
+
### 1.4.1
|
|
8
|
+
- README 按 DSH 插件标准拆成五语:`README.md`(英文源)/ `README-zh.md` / `README-es.md` / `README-pt.md` / `README-hi.md`,章节结构完全一致
|
|
9
|
+
- 补展示元数据:`locale/{en,zh}.json` 的 `meta.title`/`meta.description` + `exports` 暴露 `./locale/*.json` 与 `./icon`
|
|
10
|
+
- 对齐官方工程门禁:`engines.node` → `^22.19.0 || >=24.0.0`、`packageManager` 钉 `pnpm@11.7.0`
|
|
11
|
+
- `dsh-plugin-dev check`:**11 passed / 0 failed / 0 warned**
|
|
12
|
+
|
|
13
|
+
### 1.4.0
|
|
14
|
+
- 多语言:简体中文 / 繁體中文 / English / 日本語;「自动」跟随宿主的 `documentElement.lang`
|
|
15
|
+
- 配色名(黑白/暖砂/靛蓝/苔绿)、各档位文案、状态行都本地化,不是只翻标题
|
|
16
|
+
- 宿主切换界面语言时用 MutationObserver 立刻重渲染
|
|
17
|
+
- 自愈加重试退避(4s → 15s → 1m → 3m → 10m)并封顶:token 真被改名时不再每 4 秒重放
|
|
18
|
+
- `tools/preview-settings.mjs` 支持 `--lang`
|
|
19
|
+
|
|
7
20
|
### 1.3.3
|
|
8
21
|
- 文档拆分:README 只留用法与当前设计(759 → 210 行),版本记录移到 `CHANGELOG.md`,逐组件设计说明移到 `docs/DESIGN.md`
|
|
9
22
|
- 修正 README 里过期的「当前版本 0.8.2」
|
package/README-es.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# dsh-theme-palette
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
|
|
4
|
+
|
|
5
|
+
Paleta de tema para DeepSeek Harness (web y escritorio): **4 paletas × claro/oscuro**, dos ejes independientes.
|
|
6
|
+
|
|
7
|
+
## Compatibility
|
|
8
|
+
|
|
9
|
+
| Superficie | Estado |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Cliente DSH de escritorio / web | 0.2.x |
|
|
12
|
+
| Node.js | ≥ 20 |
|
|
13
|
+
| Cambios en el código del host | ninguno |
|
|
14
|
+
|
|
15
|
+
El plugin solo apila tokens de color mediante `ctx.theme.overrideTokens`; no registra un tema, no parchea el host y no toca la preferencia de apariencia.
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
- **4 paletas** — Mono, Arena cálida, Índigo, Musgo — cada una adaptada de forma independiente a claro y oscuro;
|
|
20
|
+
- la escala se **recalcula** completa: reconstrucción OKLCH → croma por rol → **ajuste al gamut sRGB** → contraste resuelto sobre superficies reales;
|
|
21
|
+
- más de 27.000 referencias de token: texto, secundario, terciario, notas, enlaces, barras laterales, tarjetas, burbuja del usuario, botón primario, barras de desplazamiento, interruptores, títulos/negritas de Markdown, bloques de código;
|
|
22
|
+
- ajustable: intensidad del texto (3), profundidad de la barra lateral (3), panel derecho (2), énfasis de Markdown, barras de desplazamiento, color de interruptores;
|
|
23
|
+
- **autorreparable**: si el host descarta la capa de override, la paleta se vuelve a aplicar;
|
|
24
|
+
- interfaz en 简体中文 / 繁體中文 / English / 日本語 (sigue el idioma del host y se puede fijar).
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# desde npm
|
|
30
|
+
cd ~/.dsh/profiles/desktop
|
|
31
|
+
pnpm add dsh-theme-palette
|
|
32
|
+
|
|
33
|
+
# o una versión concreta
|
|
34
|
+
pnpm add dsh-theme-palette@1.4.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Desde el tarball del release de GitHub:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Después **reinicia la interfaz**: un proceso en marcha conserva el módulo antiguo. Se abre en avatar → Ajustes → **Color theme**.
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Elemento | Opciones |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Paleta | Mono · Arena cálida · Índigo · Musgo (las miniaturas siguen claro/oscuro) |
|
|
50
|
+
| Intensidad | Suave / Estándar / Fuerte — cuánto sigue el texto a la paleta |
|
|
51
|
+
| Superficies | Barra lateral (Más clara / Igual / Más oscura), panel derecho (Igual que la barra / Seguir el lienzo) |
|
|
52
|
+
| Más opciones | Idioma, énfasis de Markdown, barras de desplazamiento, interruptores |
|
|
53
|
+
|
|
54
|
+
Cada elección se aplica al instante y se guarda en `localStorage`. Claro/oscuro sigue en el control «Apariencia» del host.
|
|
55
|
+
|
|
56
|
+
## Palette engine
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
OKLCH(L, C, tono) ──► ajuste al gamut (búsqueda binaria en C) ──► hex sRGB
|
|
60
|
+
▲
|
|
61
|
+
└── croma por rol × intensidad, luego contraste sobre {lienzo, tarjeta, barra, superposición}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- los alias semánticos mantienen cadenas `var(--dsw-static-neutral-bluish-NN)`, así que todo lo que sigue a un token sigue a la paleta;
|
|
65
|
+
- superficies claras usan los pasos 00–300, las oscuras 800–950;
|
|
66
|
+
- los mínimos de texto se exigen en tiempo de construcción: cuerpo ≥ 12, secundario ≥ 6.1, terciario/enlace ≥ 4.6, texto en barra lateral ≥ 7, texto en burbuja ≥ 7;
|
|
67
|
+
- `node palette-dev.mjs` comprueba 432 combinaciones.
|
|
68
|
+
|
|
69
|
+
Las notas de diseño, el criterio por componente y las tablas de contraste están en [docs/DESIGN.md](docs/DESIGN.md). Historial: [CHANGELOG.md](CHANGELOG.md).
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node palette-dev.mjs # informe de tokens de todas las paletas
|
|
75
|
+
node tools/sheet.mjs # tablero de paletas → tools/sheet.html
|
|
76
|
+
node tools/shoot-sheet.mjs # captura del tablero (Chrome headless)
|
|
77
|
+
node tools/compare.mjs --dir tools/out/<run> # comparar con la línea base
|
|
78
|
+
node tools/preview-settings.mjs --lang en # renderizar la página de ajustes
|
|
79
|
+
node tools/release.mjs --dry # preflight de publicación
|
|
80
|
+
node tools/release.mjs --tag # publicar en npm + tag
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
La regresión visual tiene dos capas: el tablero de paletas (sin autenticación) y la ventana real del escritorio vía CDP (`tools/shoot-desktop.mjs`, requiere `--remote-debugging-port`). Ver [tools/README.md](tools/README.md).
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT © 2026 Zuoyunlai
|
package/README-hi.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# dsh-theme-palette
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness (वेब और डेस्कटॉप) के लिए थीम पैलेट: **4 पैलेट × लाइट/डार्क**, दो स्वतंत्र अक्ष।
|
|
6
|
+
|
|
7
|
+
## Compatibility
|
|
8
|
+
|
|
9
|
+
| सतह | स्थिति |
|
|
10
|
+
|---|---|
|
|
11
|
+
| DSH डेस्कटॉप / वेब क्लाइंट | 0.2.x |
|
|
12
|
+
| Node.js | ≥ 20 |
|
|
13
|
+
| होस्ट सोर्स में बदलाव | कोई नहीं |
|
|
14
|
+
|
|
15
|
+
प्लगइन केवल `ctx.theme.overrideTokens` से रंग टोकन की एक परत जोड़ता है; यह नया थीम पंजीकृत नहीं करता, होस्ट को पैच नहीं करता और अपीयरेंस सेटिंग नहीं बदलता।
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
- **4 पैलेट** — मोनो, वार्म सैंड, इंडिगो, मॉस — हर एक लाइट और डार्क दोनों के लिए अलग से अनुकूलित;
|
|
20
|
+
- पूरी रैंप **दोबारा गणना** होती है, बदली नहीं जाती: OKLCH पुनर्निर्माण → भूमिका-आधारित क्रोमा → **sRGB गैमट क्लैंप** → वास्तविक सतहों पर कंट्रास्ट हल;
|
|
21
|
+
- 27,000+ टोकन संदर्भ शामिल: मुख्य टेक्स्ट, द्वितीयक, तृतीयक, कैप्शन, लिंक, साइडबार, कार्ड, उपयोगकर्ता बबल, प्राइमरी बटन, स्क्रॉलबार, स्विच, Markdown शीर्षक/बोल्ड, कोड ब्लॉक;
|
|
22
|
+
- समायोज्य: टेक्स्ट की तीव्रता (3), साइडबार की गहराई (3), दायाँ पैनल (2), Markdown ज़ोर, स्क्रॉलबार, स्विच का रंग;
|
|
23
|
+
- **स्व-उपचार**: यदि होस्ट ओवरराइड परत हटा दे, पैलेट फिर से लगा दी जाती है;
|
|
24
|
+
- इंटरफ़ेस 简体中文 / 繁體中文 / English / 日本語 में (होस्ट की भाषा का पालन करता है, हाथ से भी चुना जा सकता है)।
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# npm से
|
|
30
|
+
cd ~/.dsh/profiles/desktop
|
|
31
|
+
pnpm add dsh-theme-palette
|
|
32
|
+
|
|
33
|
+
# या कोई विशेष संस्करण
|
|
34
|
+
pnpm add dsh-theme-palette@1.4.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
GitHub रिलीज़ के tarball से:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
इसके बाद **GUI को फिर से शुरू करें** — चल रहा प्रोसेस पुराना मॉड्यूल रखता है। खोलें: अवतार → सेटिंग्स → **Color theme**।
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| विकल्प | विकल्प |
|
|
48
|
+
|---|---|
|
|
49
|
+
| पैलेट | मोनो · वार्म सैंड · इंडिगो · मॉस (थंबनेल लाइट/डार्क का पालन करते हैं) |
|
|
50
|
+
| तीव्रता | शांत / मानक / गहरा — टेक्स्ट पैलेट का कितना पालन करे |
|
|
51
|
+
| सतहें | साइडबार (हल्का / समान / गहरा), दायाँ पैनल (साइडबार जैसा / कैनवास जैसा) |
|
|
52
|
+
| और विकल्प | भाषा, Markdown ज़ोर, स्क्रॉलबार, स्विच/चेक |
|
|
53
|
+
|
|
54
|
+
हर चुनाव तुरंत लागू होता है और `localStorage` में सहेजा जाता है। लाइट/डार्क होस्ट की «अपीयरेंस» सेटिंग में ही रहता है।
|
|
55
|
+
|
|
56
|
+
## Palette engine
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
OKLCH(L, C, ह्यू) ──► गैमट क्लैंप (C पर बाइनरी खोज) ──► sRGB हेक्स
|
|
60
|
+
▲
|
|
61
|
+
└── भूमिका क्रोमा × तीव्रता, फिर {कैनवास, कार्ड, साइडबार, ओवरले} पर कंट्रास्ट
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- सेमांटिक एलियस `var(--dsw-static-neutral-bluish-NN)` श्रृंखलाएँ बनाए रखते हैं, इसलिए जो भी टोकन का पालन करता है वह पैलेट का भी करता है;
|
|
65
|
+
- लाइट सतहें स्टेप 00–300, डार्क सतहें 800–950;
|
|
66
|
+
- टेक्स्ट की न्यूनतम सीमा बनाते समय लागू: मुख्य ≥ 12, द्वितीयक ≥ 6.1, तृतीयक/लिंक ≥ 4.6, साइडबार में ≥ 7, बबल में ≥ 7;
|
|
67
|
+
- `node palette-dev.mjs` 432 संयोजन जाँचता है।
|
|
68
|
+
|
|
69
|
+
डिज़ाइन नोट्स, घटक-वार तर्क और कंट्रास्ट तालिकाएँ [docs/DESIGN.md](docs/DESIGN.md) में। इतिहास: [CHANGELOG.md](CHANGELOG.md)।
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node palette-dev.mjs # सभी पैलेट का टोकन रिपोर्ट
|
|
75
|
+
node tools/sheet.mjs # पैलेट बोर्ड → tools/sheet.html
|
|
76
|
+
node tools/shoot-sheet.mjs # बोर्ड का स्क्रीनशॉट (हेडलेस Chrome)
|
|
77
|
+
node tools/compare.mjs --dir tools/out/<run> # बेसलाइन से तुलना
|
|
78
|
+
node tools/preview-settings.mjs --lang en # सेटिंग्स पेज रेंडर करें
|
|
79
|
+
node tools/release.mjs --dry # रिलीज़ प्रीफ़्लाइट
|
|
80
|
+
node tools/release.mjs --tag # npm पर प्रकाशित + टैग
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
स्क्रीनशॉट रिग्रेशन की दो परतें हैं: पैलेट बोर्ड (बिना लॉगिन) और CDP से असली डेस्कटॉप विंडो (`tools/shoot-desktop.mjs`, `--remote-debugging-port` चाहिए)। देखें [tools/README.md](tools/README.md)।
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT © 2026 Zuoyunlai
|
package/README-pt.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# dsh-theme-palette
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
|
|
4
|
+
|
|
5
|
+
Paleta de tema para o DeepSeek Harness (web e desktop): **4 paletas × claro/escuro**, dois eixos independentes.
|
|
6
|
+
|
|
7
|
+
## Compatibility
|
|
8
|
+
|
|
9
|
+
| Superfície | Estado |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Cliente DSH desktop / web | 0.2.x |
|
|
12
|
+
| Node.js | ≥ 20 |
|
|
13
|
+
| Alterações no código do host | nenhuma |
|
|
14
|
+
|
|
15
|
+
O plugin apenas empilha tokens de cor via `ctx.theme.overrideTokens`; não registra um tema, não altera o host e não toca na preferência de aparência.
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
- **4 paletas** — Mono, Areia quente, Índigo, Musgo — cada uma adaptada de forma independente ao claro e ao escuro;
|
|
20
|
+
- a escala é **recalculada** por completo: reconstrução OKLCH → croma por papel → **limite de gamut sRGB** → contraste resolvido sobre superfícies reais;
|
|
21
|
+
- mais de 27.000 referências de token: texto, secundário, terciário, legendas, links, barras laterais, cartões, balão do usuário, botão primário, barras de rolagem, interruptores, títulos/negrito de Markdown, blocos de código;
|
|
22
|
+
- ajustável: intensidade do texto (3), profundidade da barra lateral (3), painel direito (2), ênfase de Markdown, barras de rolagem, cor dos interruptores;
|
|
23
|
+
- **autorrecuperação**: se o host descartar a camada de override, a paleta é reaplicada;
|
|
24
|
+
- interface em 简体中文 / 繁體中文 / English / 日本語 (segue o idioma do host e pode ser fixada).
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# via npm
|
|
30
|
+
cd ~/.dsh/profiles/desktop
|
|
31
|
+
pnpm add dsh-theme-palette
|
|
32
|
+
|
|
33
|
+
# ou uma versão específica
|
|
34
|
+
pnpm add dsh-theme-palette@1.4.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A partir do tarball do release do GitHub:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Depois **reinicie a interface**: um processo em execução mantém o módulo antigo. Abra em avatar → Configurações → **Color theme**.
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Item | Opções |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Paleta | Mono · Areia quente · Índigo · Musgo (as miniaturas seguem claro/escuro) |
|
|
50
|
+
| Intensidade | Suave / Padrão / Forte — quanto o texto segue a paleta |
|
|
51
|
+
| Superfícies | Barra lateral (Mais clara / Igual / Mais escura), painel direito (Igual à barra / Seguir a tela) |
|
|
52
|
+
| Mais opções | Idioma, ênfase de Markdown, barras de rolagem, interruptores |
|
|
53
|
+
|
|
54
|
+
Cada escolha aplica na hora e fica em `localStorage`. Claro/escuro continua no controle «Aparência» do host.
|
|
55
|
+
|
|
56
|
+
## Palette engine
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
OKLCH(L, C, matiz) ──► limite de gamut (busca binária em C) ──► hex sRGB
|
|
60
|
+
▲
|
|
61
|
+
└── croma por papel × intensidade, depois contraste sobre {tela, cartão, barra, sobreposição}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- os aliases semânticos mantêm cadeias `var(--dsw-static-neutral-bluish-NN)`, então tudo que segue um token segue a paleta;
|
|
65
|
+
- superfícies claras usam os passos 00–300, as escuras 800–950;
|
|
66
|
+
- os pisos de texto são exigidos na construção: corpo ≥ 12, secundário ≥ 6.1, terciário/link ≥ 4.6, texto na barra lateral ≥ 7, texto no balão ≥ 7;
|
|
67
|
+
- `node palette-dev.mjs` verifica 432 combinações.
|
|
68
|
+
|
|
69
|
+
Notas de design, critério por componente e tabelas de contraste em [docs/DESIGN.md](docs/DESIGN.md). Histórico: [CHANGELOG.md](CHANGELOG.md).
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node palette-dev.mjs # relatório de tokens de todas as paletas
|
|
75
|
+
node tools/sheet.mjs # painel de paletas → tools/sheet.html
|
|
76
|
+
node tools/shoot-sheet.mjs # captura do painel (Chrome headless)
|
|
77
|
+
node tools/compare.mjs --dir tools/out/<run> # comparar com a linha de base
|
|
78
|
+
node tools/preview-settings.mjs --lang en # renderizar a página de ajustes
|
|
79
|
+
node tools/release.mjs --dry # pré-checagem de release
|
|
80
|
+
node tools/release.mjs --tag # publicar no npm + tag
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A regressão visual tem duas camadas: o painel de paletas (sem autenticação) e a janela real do desktop via CDP (`tools/shoot-desktop.mjs`, requer `--remote-debugging-port`). Veja [tools/README.md](tools/README.md).
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT © 2026 Zuoyunlai
|
package/README-zh.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# dsh-theme-palette
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness(Web / 桌面)的主题配色插件:**4 套配色 × 明暗两模式**,两个轴互相独立。
|
|
6
|
+
|
|
7
|
+
## Compatibility
|
|
8
|
+
|
|
9
|
+
| 项目 | 状态 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| DSH 桌面 / Web 客户端 | 0.2.x |
|
|
12
|
+
| Node.js | ≥ 20 |
|
|
13
|
+
| 对宿主源码的改动 | 无 |
|
|
14
|
+
|
|
15
|
+
插件只用 `ctx.theme.overrideTokens` 叠一层颜色 token:不注册主题、不改宿主、不动「外观」偏好。
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
- **4 套配色** —— 黑白 / 暖砂 / 靛蓝 / 苔绿,各自独立适配明暗两侧;
|
|
20
|
+
- 色阶**整体重算**而不是替换:OKLCH 重建 → 角色化色度 → **sRGB 色域钳位** → 在真实承载面上求解对比度;
|
|
21
|
+
- 覆盖 27,000+ 处 token 引用:正文、次级、三级、注释、链接、侧栏、卡片、主人气泡、主按钮、滚动条、开关、Markdown 标题/加粗、代码块;
|
|
22
|
+
- 可调:文字着色三档、侧栏三档、右栏两档、Markdown 强调、滚动条、开关配色;
|
|
23
|
+
- **自愈**:宿主丢弃 override 层时自动重放;
|
|
24
|
+
- 界面支持 简体中文 / 繁體中文 / English / 日本語(跟随宿主,也可手选)。
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 从 npm
|
|
30
|
+
cd ~/.dsh/profiles/desktop
|
|
31
|
+
pnpm add dsh-theme-palette
|
|
32
|
+
|
|
33
|
+
# 或指定版本
|
|
34
|
+
pnpm add dsh-theme-palette@1.4.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
从 GitHub Release 的 tarball:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
装完**必须重启 GUI**(运行中的进程仍持旧模块)。入口:左下角头像 → **设置** → 「**主题色**」。
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| 项 | 选项 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| 配色 | 黑白 · 暖砂 · 靛蓝 · 苔绿(卡片缩略图跟随明暗) |
|
|
50
|
+
| 强度 | 静 / 标准 / 浓 —— 正文与强调的着色程度 |
|
|
51
|
+
| 面板表面 | 左侧栏(更浅 / 同色 / 更深)、右侧栏(同左栏 / 跟随画布) |
|
|
52
|
+
| 更多选项 | 语言、Markdown 强调、滚动条、开关 / 勾选 |
|
|
53
|
+
|
|
54
|
+
选择即时生效并存在 `localStorage`;明暗仍由宿主「外观」控制。
|
|
55
|
+
|
|
56
|
+
## Palette engine
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
OKLCH(L, C, hue) ──► 色域钳位(对 C 二分) ──► sRGB hex
|
|
60
|
+
▲
|
|
61
|
+
└── 角色色度 × 强度,再在 {画布, 卡层, 侧栏, 悬浮层} 上求解对比度
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- 语义别名保留 `var(--dsw-static-neutral-bluish-NN)` 引用链,跟 token 的元素就跟着配色走;
|
|
65
|
+
- 浅色面用色阶 00–300,深色面用 800–950;
|
|
66
|
+
- 文字下限在构建期强制:正文 ≥ 12、次级 ≥ 6.1、三级/链接 ≥ 4.6、侧栏内 ≥ 7、气泡内 ≥ 7;
|
|
67
|
+
- `node palette-dev.mjs` 跑 432 组配置。
|
|
68
|
+
|
|
69
|
+
逐组件设计取舍与对比度表见 [docs/DESIGN.md](docs/DESIGN.md),版本记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node palette-dev.mjs # 全部配色的 token 报表
|
|
75
|
+
node tools/sheet.mjs # 配色看板 → tools/sheet.html
|
|
76
|
+
node tools/shoot-sheet.mjs # 无头 Chrome 截看板
|
|
77
|
+
node tools/compare.mjs --dir tools/out/<run> # 与基线比
|
|
78
|
+
node tools/preview-settings.mjs --lang en # 渲染设置页
|
|
79
|
+
node tools/release.mjs --dry # 发版预检
|
|
80
|
+
node tools/release.mjs --tag # 发 npm + 打 tag
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
截图回归分两层:配色看板(免登录)与桌面真机窗口(`tools/shoot-desktop.mjs`,需要 `--remote-debugging-port`)。见 [tools/README.md](tools/README.md)。
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT © 2026 Zuoyunlai
|
package/README.md
CHANGED
|
@@ -1,210 +1,87 @@
|
|
|
1
1
|
# dsh-theme-palette
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
选择即时生效,存在 `localStorage`。
|
|
5
|
+
Theme palette for DeepSeek Harness (web & desktop): **4 palettes × light/dark**, two independent axes.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
## Compatibility
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **4 套配色**(黑白 / 暖砂 / 靛蓝 / 苔绿)× **明暗两模式**,两个轴互相独立;
|
|
13
|
-
- 色阶**整体重算**(OKLCH 重建 + 色域钳位 + 在真实承载面上求解对比度),不是换几个变量;
|
|
14
|
-
- 覆盖 27,000+ 处 token 引用:正文、次级、三级、注释、链接、侧栏、卡片、气泡、主按钮、滚动条、开关、Markdown 标题/加粗、代码块;
|
|
15
|
-
- **自愈**:宿主丢弃 override 层时自动重放。
|
|
16
|
-
|
|
17
|
-
## 使用
|
|
18
|
-
|
|
19
|
-
左下角头像 → **设置** → 左侧导航「**主题色**」:
|
|
20
|
-
|
|
21
|
-
| 项 | 选项 |
|
|
9
|
+
| Surface | Status |
|
|
22
10
|
|---|---|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
11
|
+
| DSH desktop / web client | 0.2.x |
|
|
12
|
+
| Node.js | ≥ 20 |
|
|
13
|
+
| Changes to host source | none |
|
|
14
|
+
|
|
15
|
+
The plugin only stacks colour tokens through `ctx.theme.overrideTokens`; it does not register a theme, patch the host, or touch the appearance preference.
|
|
27
16
|
|
|
28
|
-
|
|
17
|
+
## What it does
|
|
29
18
|
|
|
30
|
-
|
|
19
|
+
- **4 palettes** — Mono, Warm sand, Indigo, Moss — each adapting to light *and* dark independently;
|
|
20
|
+
- the whole ramp is **recomputed**, not swapped: OKLCH rebuild → role-based chroma → **sRGB gamut clamp** → contrast solved on real surfaces;
|
|
21
|
+
- 27,000+ token references covered: body, secondary, tertiary, captions, links, sidebars, cards, user bubble, primary button, scrollbars, switches, Markdown headings/bold, code blocks;
|
|
22
|
+
- adjustable: text tint (3), sidebar depth (3), right panel (2), Markdown emphasis, scrollbars, switch colour;
|
|
23
|
+
- **self-healing**: if the host drops the override layer, the palette is re-applied;
|
|
24
|
+
- UI in 简体中文 / 繁體中文 / English / 日本語 (follows the host language, can be pinned).
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
## Install
|
|
33
27
|
|
|
34
28
|
```bash
|
|
35
|
-
#
|
|
29
|
+
# from npm
|
|
36
30
|
cd ~/.dsh/profiles/desktop
|
|
37
31
|
pnpm add dsh-theme-palette
|
|
38
32
|
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
从 GitHub Release 的 tarball(npm 不可达时):
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.3.1/dsh-theme-palette-1.3.1.tgz"
|
|
33
|
+
# or a specific version
|
|
34
|
+
pnpm add dsh-theme-palette@1.4.0
|
|
47
35
|
```
|
|
48
36
|
|
|
49
|
-
|
|
37
|
+
From the GitHub Release tarball:
|
|
50
38
|
|
|
51
39
|
```bash
|
|
52
|
-
|
|
53
|
-
dsh plugin --profile desktop add "$PWD/dsh-theme-palette"
|
|
40
|
+
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
|
|
54
41
|
```
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
> 开发时注意:`node_modules/dsh-theme-palette` 是**副本**,改源码后要
|
|
59
|
-
> `rm -rf node_modules/dsh-theme-palette && pnpm install` —— `pnpm add` 对已存在的
|
|
60
|
-
> `file:` 目录不会刷新内容。
|
|
61
|
-
|
|
62
|
-
安装后:左下角头像 → 设置 → 左侧「**主题色**」。
|
|
63
|
-
|
|
64
|
-
## 配色怎么算
|
|
65
|
-
|
|
66
|
-
1. **OKLCH 重建色阶**:保留每一步的感知亮度,只换色相与色度。
|
|
67
|
-
2. **色度按 UI 角色给**:浅色底 0.013–0.024、中间灰 0.006、浅色正文 0.004、
|
|
68
|
-
深色层 0.014–0.028、深色正文 0.009–0.014。正文只给极低色度以保证字形锐利。
|
|
69
|
-
**深色层的色度必须按色相分别给**,不能三套共用一个数:绝对色度不决定观感,占比才决定 ——
|
|
70
|
-
L 0.23 处可用色域是暖砂 0.048 / 苔绿 0.061 / 靛蓝 0.157,同一个 0.016 会得到
|
|
71
|
-
35% / 23% / 11% 三种相对饱和度(暖砂发脏、靛蓝发灰)。现在三套分别落在 30% / 24% / 19%。
|
|
72
|
-
深色层的色度权重也**不随明度递减**(`800/850/875` = 1.0 / 1.0 / 0.95):层与层之间只靠明度分层,
|
|
73
|
-
权重递减会让卡片/模块比画布灰 30–45%,读作「换了一种材质」。
|
|
74
|
-
3. **近白端等量下移 0.032**(`L ≥ 0.92`):不是压缩斜率。压缩会把「画布↔卡层」的 ΔL
|
|
75
|
-
从 0.031 压到 0.0147,而 `L<0.92` 那段又没压,明度节奏会在边界处断层。
|
|
76
|
-
4. **对比度求解到真实承载面**:三级文字与链接大量落在卡层 `60`、悬浮层 `150`、深模块 `800` 上,
|
|
77
|
-
所以靶点用这些面而不是画布;上限 ±0.13(从 4.41 抬到 4.62 需要 ΔL≈0.096)。
|
|
78
|
-
|
|
79
|
-
覆盖 **75 个 token / 套**:4 条静态色阶(19+16+10+12)+ 14 个**字面量**别名
|
|
80
|
-
(边框 `border-l1..l4`、`bg-skeleton`、`menu-group-surface`、`interactive-bg-hover/-active/-hover-accent`、
|
|
81
|
-
`button-tool-bar-*`、`bg-mask-drop` 等)+ 3 个侧栏 token。
|
|
82
|
-
|
|
83
|
-
## 对比度(按真实承载面实测)
|
|
84
|
-
|
|
85
|
-
暖砂一行为例(三套同构,差在 0.01 量级):
|
|
86
|
-
|
|
87
|
-
| 承载面 | 底 | 主文本 | 次文本 | 三级 | 链接 |
|
|
88
|
-
|---|---|---|---|---|---|
|
|
89
|
-
| 画布 `00` | `#fbf4e3` | 17.23 | 6.78 | 5.02 | 5.57 |
|
|
90
|
-
| 卡层 `60` | `#f2ebdb` | 15.90 | 6.26 | 4.63 | 5.14 |
|
|
91
|
-
| 深画布 `950` | `#1e1b14` | 14.98 | 11.39 | 8.02 | 7.36 |
|
|
92
|
-
| 深层1 `875` | `#2c2922` | 12.65 | 9.62 | 6.77 | 6.22 |
|
|
93
|
-
| 深卡 `850` | `#36332b` | 10.99 | 8.36 | 5.89 | 5.40 |
|
|
94
|
-
| 深模块 `800` | `#403c35` | 9.56 | 7.27 | 5.12 | 4.70 |
|
|
43
|
+
Then **restart the GUI** — a running process keeps the old module. Open it from avatar → Settings → **Color theme**.
|
|
95
44
|
|
|
96
|
-
|
|
97
|
-
见 §品牌基准色与链接。
|
|
45
|
+
## Configuration
|
|
98
46
|
|
|
99
|
-
|
|
100
|
-
所以深色的三级文字与链接要按**最亮的那个面**(`800`)兜底,而不是按画布。
|
|
101
|
-
|
|
102
|
-
**承诺范围**:`00 / 60 / 800 / 850 / 875 / 950` 全部 ≥ AA(三级与链接 4.62 起),
|
|
103
|
-
正文在所有深色面上 ≥ 8.95(AAA 的 7 线有余量)。三套配色 **0 处低于下限**。
|
|
104
|
-
|
|
105
|
-
`palette-dev.mjs` 把「合规下限」与「设计目标」分开记:低于 WCAG 线打 `✗` 并计数,
|
|
106
|
-
只低于设计目标(深色正文 ≥ 10)打 `△`、不计违规。现在唯一带 `△` 的是 `800` 面的正文
|
|
107
|
-
(8.95–9.56)—— 那是「最亮的深色面 + 近白文字」的固有代价,不是还没修好的违规。
|
|
108
|
-
悬浮层 `150`(toast / tooltip / popover)**不在承诺内**:宿主在这些面上本来就用白字,
|
|
109
|
-
在参数上补偿会把强调色冲淡;需要时由宿主自己保证。
|
|
110
|
-
|
|
111
|
-
## 四套配色的落色
|
|
112
|
-
|
|
113
|
-
| | 画布 | 卡层 | 正文 | 次文 | 三级 | 强调 | 深底 | 深卡 | 深正文 | 深强调 |
|
|
114
|
-
|---|---|---|---|---|---|---|---|---|---|---|
|
|
115
|
-
| 暖砂 | `#fbf4e3` | `#f2ebdb` | `#12110d` | `#575551` | `#696967` | **`#a9683e`** | `#1e1b14` | `#36332b` | `#f5efe0` | `#c8a28b` |
|
|
116
|
-
| 靛蓝 | `#eff5ff` | `#e7ebf6` | `#0f1114` | `#53555a` | `#676869` | **`#3f66a3`** | `#161c2a` | `#2e3443` | `#ebeff9` | `#95acd0` |
|
|
117
|
-
| 苔绿 | `#eef7f0` | `#e6eee7` | `#0f120f` | `#525753` | `#686968` | **`#4f7a42`** | `#1a211b` | `#313933` | `#e7f3e9` | `#9cb695` |
|
|
118
|
-
|
|
119
|
-
「强调」列是设计稿给的**基准色**(色阶 `500`),「深强调」是由它派生的深色侧强调(色阶 `400`)。
|
|
120
|
-
|
|
121
|
-
深底三套的明度**故意不同**(L 0.223 / 0.227 / 0.239):低色度下色相换不来区分度,
|
|
122
|
-
三套原本都落在 0.230,结果暖砂↔苔绿在深色层上只差 ΔE 1.15(低于 JND 2,等于分不出)。
|
|
123
|
-
错开明度后最小一对是 2.05。
|
|
124
|
-
|
|
125
|
-
黑白走宿主原值(`#ffffff` / `#151517`),只叠侧栏档位 —— 它没有色调可继承。
|
|
126
|
-
|
|
127
|
-
## 挂在哪一层
|
|
128
|
-
|
|
129
|
-
注册 **`settings.section`**(设置侧栏独立一节)。官方内置节顺序 account -10 / general 0 / models 10 /
|
|
130
|
-
plugins 15 / agent-presets 20,本插件 `order: 5`,紧跟「通用」之后。页面自己画标题(`h2` 18px/600),
|
|
131
|
-
与官方 `AgentPresetSection` 一致 —— `settings.section` 只渲染侧栏条目(文字取 `label()`),不渲染内容标题。
|
|
132
|
-
|
|
133
|
-
## 结构
|
|
134
|
-
|
|
135
|
-
| 文件 | 作用 |
|
|
47
|
+
| Item | Options |
|
|
136
48
|
|---|---|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
142
|
-
`palette-dev.mjs` 通过 `window.__DSH_THEME_PALETTE_DEV__` 这个开发期钩子拿到
|
|
143
|
-
`buildTokens / SCHEMES / SIDEBAR_MODES`,不再保留第二份配色实现 ——
|
|
144
|
-
早先它自带一套旧引擎,报表和上线不是同一套。
|
|
145
|
-
|
|
146
|
-
## 调参
|
|
147
|
-
|
|
148
|
-
```js
|
|
149
|
-
{ id: 'warm', label: '暖砂', desc: '米白 + 棕调', tinted: true,
|
|
150
|
-
hue: 88, lightC: 0.024, midC: 0.006, textC: 0.004, // 灰阶:色相 + 三档色度
|
|
151
|
-
darkC: 0.014, darkLift: 0.026, paperC: 0.014, // 深色:层色度 / 明度错位 / 正文色度
|
|
152
|
-
accentHue: 52.4, accentC: 0.101, accentL: 0.58, // 强调:色相 / 色度 / 基准档明度
|
|
153
|
-
accentScale: 1.0 } // 整条强调色阶的明度倍率(保持 1.0)
|
|
154
|
-
```
|
|
49
|
+
| Palette | Mono · Warm sand · Indigo · Moss (thumbnails follow light/dark) |
|
|
50
|
+
| Strength | Quiet / Standard / Strong — how much body text follows the palette |
|
|
51
|
+
| Panel surfaces | Sidebar (Lighter / Same / Deeper), right panel (Match sidebar / Follow canvas) |
|
|
52
|
+
| More options | Language, Markdown emphasis, scrollbars, switches/checks |
|
|
155
53
|
|
|
156
|
-
|
|
157
|
-
卡片预览色、`info` 按钮、焦点环全部自动跟随;链接会自己派生一条够 AA 的(见 §品牌基准色与链接)。
|
|
158
|
-
把目标颜色换成 OKLCH 的现成方法:`toOklch('#a9683e')` → `[0.5799, 0.1012, 52.3]`,
|
|
159
|
-
分别填进 `accentL` / `accentC` / `accentHue`,再用 `node palette-dev.mjs` 核 `500` 是否逐位对上
|
|
160
|
-
(8-bit 取整偶尔差 1,把 `accentHue` 挪 0.1–0.5° 即可)。
|
|
54
|
+
Every choice applies immediately and is stored in `localStorage`. Light/dark stays under the host’s Appearance control.
|
|
161
55
|
|
|
162
|
-
|
|
56
|
+
## Palette engine
|
|
163
57
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
58
|
+
```
|
|
59
|
+
OKLCH(L, C, hue) ──► gamut clamp (binary search on C) ──► sRGB hex
|
|
60
|
+
▲
|
|
61
|
+
└── role chroma × intensity, then contrast solve on {canvas, card, sidebar, overlay}
|
|
62
|
+
```
|
|
169
63
|
|
|
170
|
-
|
|
171
|
-
|
|
64
|
+
- semantic aliases keep `var(--dsw-static-neutral-bluish-NN)` chains, so anything following a token follows the palette;
|
|
65
|
+
- light surfaces use steps 00–300, dark surfaces 800–950;
|
|
66
|
+
- text floors are enforced at build time: body ≥ 12, secondary ≥ 6.1, tertiary/link ≥ 4.6, sidebar text ≥ 7, bubble text ≥ 7;
|
|
67
|
+
- 432 configuration combos are checked by `node palette-dev.mjs`.
|
|
172
68
|
|
|
173
|
-
|
|
174
|
-
文字着色倍率在 `TEXT_MODES` 里(`textScale` 管浅色正文、`paperScale` 管深色正文)。
|
|
175
|
-
`accentScale` 保持 1.0 —— 它是乘性的,压低会把强调色浅端(`deepseek-50/100/200`)整条压塌,
|
|
176
|
-
而这几个正是宿主的「选中态软底」。
|
|
69
|
+
Design notes, per-component rationale and the contrast tables live in [docs/DESIGN.md](docs/DESIGN.md). Release history: [CHANGELOG.md](CHANGELOG.md).
|
|
177
70
|
|
|
178
|
-
##
|
|
71
|
+
## Development
|
|
179
72
|
|
|
180
73
|
```bash
|
|
181
|
-
node
|
|
182
|
-
node tools/
|
|
183
|
-
node tools/
|
|
74
|
+
node palette-dev.mjs # token report for all palettes
|
|
75
|
+
node tools/sheet.mjs # palette board → tools/sheet.html
|
|
76
|
+
node tools/shoot-sheet.mjs # screenshot the board (headless Chrome)
|
|
77
|
+
node tools/compare.mjs --dir tools/out/<run> # diff against baseline
|
|
78
|
+
node tools/preview-settings.mjs --lang en # render the settings page
|
|
79
|
+
node tools/release.mjs --dry # release preflight
|
|
80
|
+
node tools/release.mjs --tag # publish to npm + tag
|
|
184
81
|
```
|
|
185
82
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- 悬浮层 `150` 上三级文字 4.22–4.27,不在承诺内(见上)。
|
|
193
|
-
- **暖砂基准色上的白字只有 4.43**(靛蓝 5.77、苔绿 5.01)。宿主把 `deepseek-500` 当
|
|
194
|
-
`button-info-fill` 的填充色,若那里写白字,4.43 差 0.07 到 AA —— 这是 `#a9683e` 这个颜色
|
|
195
|
-
本身的属性(赭石色偏亮),不是参数能修的。要过就把该按钮的文字换成深端(`deepseek-900`
|
|
196
|
-
那侧)或改 15px 以下的字号策略。
|
|
197
|
-
- **基准色与链接色是两条线**:`--dsw-static-deepseek-500` 是设计标准值(填充/焦点环/预览色),
|
|
198
|
-
`--dsw-alias-link` 是派生色。任何新加的 UI 若把 500 当**文字**用,都会踩 AA(暖砂 3.40)。
|
|
199
|
-
- 深色最亮的承载面 `800`(layer-3 / module)上链接是 4.65(0.8.2 后 4.64–4.70),只比阈值高
|
|
200
|
-
0.02–0.08 —— 那是求解器靶点决定的(`MIN_LINK_DARK = 4.62`)。**以后若再抬 `darkLift`,
|
|
201
|
-
必须同步抬 `MIN_LINK_DARK`**,否则链接会在 AA 线上横跳。
|
|
202
|
-
- `paperC` 只服务 `50` 号色,而 `50` 号在浅色侧同时是菜单表面的基色(`menu-surface-fill`):
|
|
203
|
-
`paperScale` 任何放大都会顺带把浅色菜单染深。0.8.1 把 `paperC` 降下来后这个耦合的影响变小了,
|
|
204
|
-
但结构还在。真要解耦得让 `50` 号不再兼任「深色正文」与「浅色表面」。
|
|
205
|
-
- 深色三档文字色拉不开(ΔE 0.4–1.0)是 sRGB 近白天花板决定的物理限制,
|
|
206
|
-
不是参数没调对 —— 别为拉档差去抬 `paperScale`。
|
|
207
|
-
- 黑白配色完全走宿主值,含宿主本身的 3.71 / 4.23(AA 未达标)。要修需要给 mono 建一条
|
|
208
|
-
走引擎的色阶,属于观感变更,另行确认。
|
|
209
|
-
- `blue` 与 `deepseek` 两条色阶被染到同一色相,信息态与品牌色不可分;需要时给 `blue` 单独的色度系数。
|
|
210
|
-
- 渲染效果未在真实 GUI 里验证过(本机没有可控浏览器会话)。
|
|
83
|
+
Screenshot regression has two layers: the palette board (no auth needed) and the real desktop window via CDP (`tools/shoot-desktop.mjs`, needs `--remote-debugging-port`). See [tools/README.md](tools/README.md).
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT © 2026 Zuoyunlai
|
package/client.js
CHANGED
|
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const React = require('react')
|
|
28
28
|
const h = React.createElement
|
|
29
29
|
|
|
30
|
-
const PLUGIN_VERSION = '1.
|
|
30
|
+
const PLUGIN_VERSION = '1.4.1'
|
|
31
31
|
const STORE_KEY = 'dsh-theme-palette.v1'
|
|
32
32
|
const OVERRIDE_SOURCE = 'dsh-theme-palette'
|
|
33
33
|
|
|
@@ -977,26 +977,63 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
977
977
|
}
|
|
978
978
|
|
|
979
979
|
/* ================= 文案 ================= */
|
|
980
|
-
|
|
980
|
+
/* ================= 多语言 ================= */
|
|
981
|
+
/**
|
|
982
|
+
* 语言来源优先级:用户手选 > 宿主 documentElement.lang(locale 服务写的)> navigator.language。
|
|
983
|
+
* 宿主本身只有 zh/en 两套,但插件设置页可以多给几种;「自动」永远跟随宿主。
|
|
984
|
+
*/
|
|
985
|
+
const LOCALE_KEY = 'dsh-theme-palette.lang.v1'
|
|
986
|
+
const LOCALES = ['zh-CN', 'zh-TW', 'en', 'ja']
|
|
987
|
+
// 语言名用它自己的文字写(中/繁/EN/JA),只有「自动」需要按界面语言翻译。
|
|
988
|
+
const LANG_OPTIONS = [
|
|
989
|
+
{ id: 'auto' },
|
|
990
|
+
{ id: 'zh-CN', label: '中' },
|
|
991
|
+
{ id: 'zh-TW', label: '繁' },
|
|
992
|
+
{ id: 'en', label: 'EN' },
|
|
993
|
+
{ id: 'ja', label: 'JA' },
|
|
994
|
+
]
|
|
995
|
+
|
|
996
|
+
function readLangPref() {
|
|
997
|
+
try {
|
|
998
|
+
const raw = localStorage.getItem(LOCALE_KEY)
|
|
999
|
+
if (raw === 'auto' || LOCALES.indexOf(raw) !== -1) return raw
|
|
1000
|
+
} catch (err) { /* 退回自动 */ }
|
|
1001
|
+
return 'auto'
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function writeLangPref(id) {
|
|
1005
|
+
try { localStorage.setItem(LOCALE_KEY, id) } catch (err) { /* 忽略 */ }
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function hostLocale() {
|
|
981
1009
|
try {
|
|
982
|
-
const lang = document.documentElement.lang || navigator.language || ''
|
|
983
|
-
|
|
984
|
-
|
|
1010
|
+
const lang = (document.documentElement.lang || '').trim() || navigator.language || ''
|
|
1011
|
+
if (/^zh-(hant|tw|hk|mo)/i.test(lang)) return 'zh-TW'
|
|
1012
|
+
if (/^zh/i.test(lang)) return 'zh-CN'
|
|
1013
|
+
if (/^ja/i.test(lang)) return 'ja'
|
|
1014
|
+
if (/^en/i.test(lang)) return 'en'
|
|
1015
|
+
return null
|
|
1016
|
+
} catch (err) { return null }
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function pickLocale(pref) {
|
|
1020
|
+
if (pref !== 'auto') return pref
|
|
1021
|
+
return hostLocale() || 'zh-CN'
|
|
985
1022
|
}
|
|
986
1023
|
|
|
987
1024
|
const TEXT = {
|
|
988
|
-
zh: {
|
|
1025
|
+
'zh-CN': {
|
|
989
1026
|
nav: '主题色',
|
|
990
1027
|
hint: '配色与明暗模式独立;切换后立即生效并保存在 localStorage。',
|
|
991
1028
|
md: 'Markdown 强调',
|
|
992
|
-
mdHint: '
|
|
1029
|
+
mdHint: '标题与加粗用「正文色混强调色」,比正文更有色;关掉则与正文同色。',
|
|
993
1030
|
control: '开关 / 勾选',
|
|
994
|
-
controlHint: '
|
|
1031
|
+
controlHint: '宿主的开关、复选框、单选都压在近黑/近白上;开启后改用强调色。',
|
|
995
1032
|
scroll: '滚动条',
|
|
996
|
-
scrollHint: '
|
|
1033
|
+
scrollHint: '默认中性灰;「跟随」上配色色相,「强调」用强调色。明暗关系不变。',
|
|
997
1034
|
scrollHintMono: '当前是黑白配色,没有色相可跟随,滚动条保持中性灰。',
|
|
998
1035
|
rightbar: '右侧栏底色',
|
|
999
|
-
rightbarHint: '
|
|
1036
|
+
rightbarHint: '右栏本来没有自己的背景,露的是会话区画布色。',
|
|
1000
1037
|
advanced: '更多选项',
|
|
1001
1038
|
textSub: '正文与强调的着色程度',
|
|
1002
1039
|
sidebarSub: '相对会话区的层次',
|
|
@@ -1004,14 +1041,50 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1004
1041
|
mdSub: '标题与加粗是否着色',
|
|
1005
1042
|
scrollSub: '滚动条与滑轨的色相',
|
|
1006
1043
|
controlSub: '开关与复选框是否跟强调色',
|
|
1044
|
+
langSub: '自动跟随宿主界面语言',
|
|
1007
1045
|
surface: '面板表面',
|
|
1008
|
-
surfaceHint: '
|
|
1046
|
+
surfaceHint: '决定侧栏与右栏相对会话区是「浮起」还是「凹陷」。',
|
|
1009
1047
|
ok: '✓ 配色已生效',
|
|
1010
1048
|
bad: '⚠ 部分 token 未生效(宿主可能改名),请查看控制台',
|
|
1049
|
+
healed: '配色层被宿主丢过一次,已自动恢复 ',
|
|
1050
|
+
healedSuffix: ' 次',
|
|
1011
1051
|
text: '强度',
|
|
1012
|
-
textHint: '
|
|
1052
|
+
textHint: '让正文/次级文字带配色色味;三档都满足 AA。',
|
|
1013
1053
|
sidebar: '左侧栏底色',
|
|
1014
|
-
sidebarHint: '
|
|
1054
|
+
sidebarHint: '让侧栏比会话区更浅(或更深),做出区块层次。',
|
|
1055
|
+
lang: '语言',
|
|
1056
|
+
},
|
|
1057
|
+
'zh-TW': {
|
|
1058
|
+
nav: '主題色',
|
|
1059
|
+
hint: '配色與明暗模式獨立;切換後立即生效並存在 localStorage。',
|
|
1060
|
+
md: 'Markdown 強調',
|
|
1061
|
+
mdHint: '標題與粗體用「本文色混強調色」,比本文更有色;關掉則與本文同色。',
|
|
1062
|
+
control: '開關 / 勾選',
|
|
1063
|
+
controlHint: '宿主開關、核取方塊、單選都用近黑/近白;開啟後改用強調色。',
|
|
1064
|
+
scroll: '捲軸',
|
|
1065
|
+
scrollHint: '預設中性灰;「跟隨」上配色色相,「強調」用強調色。明暗關係不變。',
|
|
1066
|
+
scrollHintMono: '目前是黑白配色,沒有色相可跟隨,捲軸保持中性灰。',
|
|
1067
|
+
rightbar: '右側欄底色',
|
|
1068
|
+
rightbarHint: '右欄本來沒有自己的背景,露的是會話區畫布色。',
|
|
1069
|
+
advanced: '更多選項',
|
|
1070
|
+
textSub: '本文與強調的著色程度',
|
|
1071
|
+
sidebarSub: '相對會話區的層次',
|
|
1072
|
+
rightbarSub: '面板底色',
|
|
1073
|
+
mdSub: '標題與粗體是否著色',
|
|
1074
|
+
scrollSub: '捲軸與軌道的色相',
|
|
1075
|
+
controlSub: '開關與核取方塊是否跟強調色',
|
|
1076
|
+
langSub: '自動跟隨宿主介面語言',
|
|
1077
|
+
surface: '面板表面',
|
|
1078
|
+
surfaceHint: '決定側欄與右欄相對會話區是「浮起」還是「凹陷」。',
|
|
1079
|
+
ok: '✓ 配色已生效',
|
|
1080
|
+
bad: '⚠ 部分 token 未生效(宿主可能改名),請看主控台',
|
|
1081
|
+
healed: '配色層被宿主丟棄過一次,已自動恢復 ',
|
|
1082
|
+
healedSuffix: ' 次',
|
|
1083
|
+
text: '強度',
|
|
1084
|
+
textHint: '讓本文/次級文字帶配色色味;三檔都符合 AA。',
|
|
1085
|
+
sidebar: '左側欄底色',
|
|
1086
|
+
sidebarHint: '讓側欄比會話區更淺(或更深),做出區塊層次。',
|
|
1087
|
+
lang: '語言',
|
|
1015
1088
|
},
|
|
1016
1089
|
en: {
|
|
1017
1090
|
nav: 'Color theme',
|
|
@@ -1019,12 +1092,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1019
1092
|
md: 'Markdown emphasis',
|
|
1020
1093
|
mdHint: 'Headings and bold mix the accent into body colour; off keeps them identical to body text.',
|
|
1021
1094
|
control: 'Switches / checks',
|
|
1022
|
-
controlHint: 'Switches, checkboxes and radios
|
|
1095
|
+
controlHint: 'Switches, checkboxes and radios use near-black/white in the host. Turn this on to use the accent colour.',
|
|
1023
1096
|
scroll: 'Scrollbars',
|
|
1024
|
-
scrollHint: '
|
|
1097
|
+
scrollHint: 'Neutral grey by default. Tint follows the palette hue; Accent uses the accent colour. Lightness is unchanged.',
|
|
1025
1098
|
scrollHintMono: 'Monochrome has no hue to follow; scrollbars stay neutral.',
|
|
1026
1099
|
rightbar: 'Right panel surface',
|
|
1027
|
-
rightbarHint: 'The right panel has no fill of its own; it shows the canvas.
|
|
1100
|
+
rightbarHint: 'The right panel has no fill of its own; it shows the canvas.',
|
|
1028
1101
|
advanced: 'More options',
|
|
1029
1102
|
textSub: 'How strongly text follows the palette',
|
|
1030
1103
|
sidebarSub: 'Layer relative to the conversation',
|
|
@@ -1032,14 +1105,98 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1032
1105
|
mdSub: 'Tint headings and bold',
|
|
1033
1106
|
scrollSub: 'Hue of the scrollbar',
|
|
1034
1107
|
controlSub: 'Switches and checkboxes',
|
|
1108
|
+
langSub: 'Follow the host interface language',
|
|
1035
1109
|
surface: 'Panel surfaces',
|
|
1036
|
-
surfaceHint: 'Whether the sidebars sit above or below the conversation area
|
|
1110
|
+
surfaceHint: 'Whether the sidebars sit above or below the conversation area.',
|
|
1037
1111
|
ok: '✓ Palette applied',
|
|
1038
1112
|
bad: '⚠ Some tokens did not apply (host may have renamed them); check the console',
|
|
1113
|
+
healed: 'Palette layer was dropped by the host; restored ',
|
|
1114
|
+
healedSuffix: 'x',
|
|
1039
1115
|
text: 'Strength',
|
|
1040
|
-
textHint: 'Tints body and secondary text toward the palette; all three levels
|
|
1116
|
+
textHint: 'Tints body and secondary text toward the palette; all three levels meet AA.',
|
|
1041
1117
|
sidebar: 'Sidebar surface',
|
|
1042
|
-
sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area
|
|
1118
|
+
sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area.',
|
|
1119
|
+
lang: 'Language',
|
|
1120
|
+
},
|
|
1121
|
+
ja: {
|
|
1122
|
+
nav: 'カラーテーマ',
|
|
1123
|
+
hint: 'ライト/ダークとは独立です。変更は即時反映され、localStorage に保存されます。',
|
|
1124
|
+
md: 'Markdown の強調',
|
|
1125
|
+
mdHint: '見出しと太字にアクセント色を混ぜます。オフにすると本文と同じ色になります。',
|
|
1126
|
+
control: 'スイッチ / チェック',
|
|
1127
|
+
controlHint: 'ホストのスイッチ・チェックボックスはほぼ白黒です。オンにするとアクセント色になります。',
|
|
1128
|
+
scroll: 'スクロールバー',
|
|
1129
|
+
scrollHint: '既定はニュートラルグレー。「追従」でテーマの色相、「アクセント」でアクセント色になります。',
|
|
1130
|
+
scrollHintMono: 'モノクロには色相がないため、スクロールバーはグレーのままです。',
|
|
1131
|
+
rightbar: '右パネルの背景',
|
|
1132
|
+
rightbarHint: '右パネルは独自の背景を持たず、キャンバス色が見えています。',
|
|
1133
|
+
advanced: 'その他のオプション',
|
|
1134
|
+
textSub: '本文と強調の色づき具合',
|
|
1135
|
+
sidebarSub: '会話エリアとの段差',
|
|
1136
|
+
rightbarSub: 'パネルの背景',
|
|
1137
|
+
mdSub: '見出しと太字を色づけする',
|
|
1138
|
+
scrollSub: 'スクロールバーの色相',
|
|
1139
|
+
controlSub: 'スイッチとチェックの色',
|
|
1140
|
+
langSub: 'ホストの表示言語に合わせます',
|
|
1141
|
+
surface: 'パネル表面',
|
|
1142
|
+
surfaceHint: 'サイドバーが会話エリアより浮くか沈むかを決めます。',
|
|
1143
|
+
ok: '✓ 配色を適用しました',
|
|
1144
|
+
bad: '⚠ 一部の token が適用されていません(ホスト側で改名された可能性)。コンソールを確認してください',
|
|
1145
|
+
healed: '配色レイヤーがホストに破棄されたため、自動で復元しました(',
|
|
1146
|
+
healedSuffix: ' 回)',
|
|
1147
|
+
text: '強さ',
|
|
1148
|
+
textHint: '本文と副次テキストをテーマ色に寄せます。3 段階とも AA を満たします。',
|
|
1149
|
+
sidebar: '左サイドバーの背景',
|
|
1150
|
+
sidebarHint: 'サイドバーを会話エリアより明るく(または暗く)して段差を作ります。',
|
|
1151
|
+
lang: '言語',
|
|
1152
|
+
},
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/** 配色名与各档位的显示名:id 不变,只换文案。 */
|
|
1156
|
+
const LABELS = {
|
|
1157
|
+
'zh-CN': {
|
|
1158
|
+
scheme: { mono: '黑白', warm: '暖砂', indigo: '靛蓝', moss: '苔绿' },
|
|
1159
|
+
desc: { mono: '无色调 · 默认', warm: '米白 + 棕调', indigo: '冷白 + 蓝调', moss: '浅灰绿 + 绿调' },
|
|
1160
|
+
text: { neutral: '静', tinted: '标准', deep: '浓' },
|
|
1161
|
+
sidebar: { light: '更浅', match: '同色', deep: '更深' },
|
|
1162
|
+
rightbar: { sidebar: '同左栏', canvas: '跟随画布' },
|
|
1163
|
+
scroll: { neutral: '中性', tinted: '跟随', accent: '强调' },
|
|
1164
|
+
control: { accent: '主题色', mono: '黑白' },
|
|
1165
|
+
md: { on: '着色', off: '同正文' },
|
|
1166
|
+
lang: { auto: '自动' },
|
|
1167
|
+
},
|
|
1168
|
+
'zh-TW': {
|
|
1169
|
+
scheme: { mono: '黑白', warm: '暖砂', indigo: '靛藍', moss: '苔綠' },
|
|
1170
|
+
desc: { mono: '無色調 · 預設', warm: '米白 + 棕調', indigo: '冷白 + 藍調', moss: '淺灰綠 + 綠調' },
|
|
1171
|
+
text: { neutral: '靜', tinted: '標準', deep: '濃' },
|
|
1172
|
+
sidebar: { light: '更淺', match: '同色', deep: '更深' },
|
|
1173
|
+
rightbar: { sidebar: '同左欄', canvas: '跟隨畫布' },
|
|
1174
|
+
scroll: { neutral: '中性', tinted: '跟隨', accent: '強調' },
|
|
1175
|
+
control: { accent: '主題色', mono: '黑白' },
|
|
1176
|
+
md: { on: '著色', off: '同本文' },
|
|
1177
|
+
lang: { auto: '自動' },
|
|
1178
|
+
},
|
|
1179
|
+
en: {
|
|
1180
|
+
scheme: { mono: 'Mono', warm: 'Warm sand', indigo: 'Indigo', moss: 'Moss' },
|
|
1181
|
+
desc: { mono: 'Neutral · default', warm: 'Cream + brown', indigo: 'Cool white + blue', moss: 'Grey-green' },
|
|
1182
|
+
text: { neutral: 'Quiet', tinted: 'Standard', deep: 'Strong' },
|
|
1183
|
+
sidebar: { light: 'Lighter', match: 'Same', deep: 'Deeper' },
|
|
1184
|
+
rightbar: { sidebar: 'Match sidebar', canvas: 'Follow canvas' },
|
|
1185
|
+
scroll: { neutral: 'Neutral', tinted: 'Tint', accent: 'Accent' },
|
|
1186
|
+
control: { accent: 'Accent', mono: 'Mono' },
|
|
1187
|
+
md: { on: 'Tinted', off: 'Body colour' },
|
|
1188
|
+
lang: { auto: 'Auto' },
|
|
1189
|
+
},
|
|
1190
|
+
ja: {
|
|
1191
|
+
scheme: { mono: 'モノクロ', warm: 'ウォームサンド', indigo: 'インディゴ', moss: 'モスグリーン' },
|
|
1192
|
+
desc: { mono: '色なし · 既定', warm: 'クリーム + ブラウン', indigo: 'クールホワイト + ブルー', moss: 'グレーグリーン' },
|
|
1193
|
+
text: { neutral: '控えめ', tinted: '標準', deep: '強め' },
|
|
1194
|
+
sidebar: { light: '明るく', match: '同じ', deep: '暗く' },
|
|
1195
|
+
rightbar: { sidebar: 'サイドバーに合わせる', canvas: 'キャンバスに従う' },
|
|
1196
|
+
scroll: { neutral: 'ニュートラル', tinted: '追従', accent: 'アクセント' },
|
|
1197
|
+
control: { accent: 'アクセント', mono: 'モノクロ' },
|
|
1198
|
+
md: { on: '色づけ', off: '本文と同じ' },
|
|
1199
|
+
lang: { auto: '自動' },
|
|
1043
1200
|
},
|
|
1044
1201
|
}
|
|
1045
1202
|
|
|
@@ -1054,9 +1211,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1054
1211
|
const mdMode = React.useSyncExternalStore(props.subscribeMd, props.getMdMode)
|
|
1055
1212
|
const health = React.useSyncExternalStore(props.subscribeHealth, props.getHealth)
|
|
1056
1213
|
const [advanced, setAdvanced] = React.useState(false)
|
|
1057
|
-
const
|
|
1214
|
+
const locale = React.useSyncExternalStore(props.subscribeLocale, props.getLocale)
|
|
1215
|
+
const langPref = React.useSyncExternalStore(props.subscribeLocale, props.getLangPref)
|
|
1216
|
+
const txt = TEXT[locale] || TEXT['zh-CN']
|
|
1217
|
+
const lb = LABELS[locale] || LABELS['zh-CN']
|
|
1058
1218
|
|
|
1059
|
-
const seg = (modes, value, onPick) => h(
|
|
1219
|
+
const seg = (modes, value, onPick, labels) => h(
|
|
1060
1220
|
'div',
|
|
1061
1221
|
{ className: 'dsh-tp-seg', role: 'group' },
|
|
1062
1222
|
modes.map((mode) => h(
|
|
@@ -1068,7 +1228,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1068
1228
|
'aria-pressed': mode.id === value,
|
|
1069
1229
|
onClick: () => onPick(mode.id),
|
|
1070
1230
|
},
|
|
1071
|
-
mode.label,
|
|
1231
|
+
(labels && labels[mode.id]) || mode.label,
|
|
1072
1232
|
)),
|
|
1073
1233
|
)
|
|
1074
1234
|
|
|
@@ -1101,8 +1261,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1101
1261
|
h('span', { className: 'dsh-tp-thumb-side' }),
|
|
1102
1262
|
h('span', { className: 'dsh-tp-thumb-main' }, h('b', null), h('b', null), h('b', null))),
|
|
1103
1263
|
h('span', { className: 'dsh-tp-card-meta' },
|
|
1104
|
-
h('span', { className: 'dsh-tp-card-name' }, scheme.label),
|
|
1105
|
-
h('span', { className: 'dsh-tp-card-desc' }, scheme.desc)),
|
|
1264
|
+
h('span', { className: 'dsh-tp-card-name' }, lb.scheme[scheme.id] || scheme.label),
|
|
1265
|
+
h('span', { className: 'dsh-tp-card-desc' }, lb.desc[scheme.id] || scheme.desc)),
|
|
1106
1266
|
h('span', { className: 'dsh-tp-check', 'aria-hidden': 'true' }, '✓'),
|
|
1107
1267
|
)
|
|
1108
1268
|
}
|
|
@@ -1115,14 +1275,14 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1115
1275
|
h('span', { className: 'dsh-tp-badge' }, 'v' + PLUGIN_VERSION)),
|
|
1116
1276
|
h('p', { className: 'dsh-tp-sub' }, txt.hint),
|
|
1117
1277
|
h('div', { className: 'dsh-tp-grid' }, SCHEMES.map(card)),
|
|
1118
|
-
row(txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode)),
|
|
1119
|
-
row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode)),
|
|
1120
|
-
row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode)),
|
|
1278
|
+
row(txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode, lb.text)),
|
|
1279
|
+
row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode, lb.sidebar)),
|
|
1280
|
+
row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode, lb.rightbar)),
|
|
1121
1281
|
health && !health.ok && h('p', { className: 'dsh-tp-status is-bad' }, txt.bad),
|
|
1122
1282
|
health && health.ok && health.reapplies > 0 && h(
|
|
1123
1283
|
'p',
|
|
1124
1284
|
{ className: 'dsh-tp-status' },
|
|
1125
|
-
|
|
1285
|
+
txt.healed + health.reapplies + txt.healedSuffix,
|
|
1126
1286
|
),
|
|
1127
1287
|
h('button', {
|
|
1128
1288
|
type: 'button',
|
|
@@ -1133,9 +1293,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1133
1293
|
advanced && h(
|
|
1134
1294
|
'div',
|
|
1135
1295
|
{ className: 'dsh-tp-adv' },
|
|
1136
|
-
row(txt.
|
|
1137
|
-
row(txt.
|
|
1138
|
-
row(txt.
|
|
1296
|
+
row(txt.lang, txt.langSub, seg(LANG_OPTIONS, langPref, props.setLangPref, lb.lang)),
|
|
1297
|
+
row(txt.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode, lb.md)),
|
|
1298
|
+
row(txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode, lb.scroll)),
|
|
1299
|
+
row(txt.control, txt.controlSub, seg(CONTROL_MODES, controlMode, props.setControlMode, lb.control)),
|
|
1139
1300
|
),
|
|
1140
1301
|
)
|
|
1141
1302
|
}
|
|
@@ -1162,6 +1323,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1162
1323
|
let scrollMode = readScrollStored()
|
|
1163
1324
|
let controlMode = readControlStored()
|
|
1164
1325
|
let mdMode = readMdStored()
|
|
1326
|
+
let langPref = readLangPref()
|
|
1327
|
+
const localeListeners = new Set()
|
|
1328
|
+
const currentLocale = () => pickLocale(langPref)
|
|
1329
|
+
const emitLocale = () => { for (const fn of localeListeners) { try { fn() } catch (err) { /* 忽略 */ } } }
|
|
1165
1330
|
const listeners = new Set()
|
|
1166
1331
|
const sidebarListeners = new Set()
|
|
1167
1332
|
const textListeners = new Set()
|
|
@@ -1250,9 +1415,18 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1250
1415
|
* 这一层就没了 —— 表现正是"配色自己跳回默认",而 localStorage 里的选择还在。
|
|
1251
1416
|
* 这里定期(以及窗口重新可见/获焦时)回读 body:token 不在了就把最后一层重放。
|
|
1252
1417
|
*/
|
|
1418
|
+
// 连续失败要退避:宿主真把 token 改名时,每 4 秒重放一次既刷日志、又会堆 override 层。
|
|
1419
|
+
const HEAL_BACKOFF = [4000, 15000, 60000, 180000, 600000]
|
|
1420
|
+
let healFails = 0
|
|
1421
|
+
let lastHealAt = 0
|
|
1422
|
+
let healDisabled = false
|
|
1423
|
+
|
|
1253
1424
|
const ensureApplied = (reason) => {
|
|
1254
|
-
if (lastTokens === null) return
|
|
1425
|
+
if (lastTokens === null || healDisabled) return
|
|
1255
1426
|
if (Date.now() - lastAppliedAt < 900) return
|
|
1427
|
+
const wait = HEAL_BACKOFF[Math.min(healFails, HEAL_BACKOFF.length - 1)]
|
|
1428
|
+
if (healFails > 0 && Date.now() - lastHealAt < wait) return
|
|
1429
|
+
lastHealAt = Date.now()
|
|
1256
1430
|
let missing = false
|
|
1257
1431
|
try {
|
|
1258
1432
|
const style = getComputedStyle(document.body)
|
|
@@ -1263,7 +1437,16 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1263
1437
|
} catch (err) {
|
|
1264
1438
|
missing = true
|
|
1265
1439
|
}
|
|
1266
|
-
if (!missing)
|
|
1440
|
+
if (!missing) {
|
|
1441
|
+
healFails = 0
|
|
1442
|
+
return
|
|
1443
|
+
}
|
|
1444
|
+
if (healFails >= HEAL_BACKOFF.length) {
|
|
1445
|
+
healDisabled = true
|
|
1446
|
+
console.error('[dsh-theme-palette] 配色层反复失效(' + reapplyCount + ' 次),已停止自动重放;请检查宿主 token 是否改名')
|
|
1447
|
+
return
|
|
1448
|
+
}
|
|
1449
|
+
healFails += 1
|
|
1267
1450
|
reapplyCount += 1
|
|
1268
1451
|
console.info('[dsh-theme-palette] 配色层不见了(' + reason + '),已重放第 ' + reapplyCount + ' 次')
|
|
1269
1452
|
applyAll('self-heal:' + reason)
|
|
@@ -1340,6 +1523,24 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1340
1523
|
applyAll()
|
|
1341
1524
|
emitMd()
|
|
1342
1525
|
}
|
|
1526
|
+
const getLangPref = () => langPref
|
|
1527
|
+
const getLocale = () => currentLocale()
|
|
1528
|
+
const subscribeLocale = (fn) => {
|
|
1529
|
+
localeListeners.add(fn)
|
|
1530
|
+
return () => { localeListeners.delete(fn) }
|
|
1531
|
+
}
|
|
1532
|
+
const setLangPref = (id) => {
|
|
1533
|
+
if (id !== 'auto' && LOCALES.indexOf(id) === -1) return
|
|
1534
|
+
langPref = id
|
|
1535
|
+
writeLangPref(id)
|
|
1536
|
+
emitLocale()
|
|
1537
|
+
}
|
|
1538
|
+
// 宿主切换界面语言时(locale 服务会改 documentElement.lang),设置页跟着重渲染。
|
|
1539
|
+
let langObserver = null
|
|
1540
|
+
try {
|
|
1541
|
+
langObserver = new MutationObserver(() => emitLocale())
|
|
1542
|
+
langObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })
|
|
1543
|
+
} catch (err) { /* 没有 MutationObserver 就只在打开设置页时读一次 */ }
|
|
1343
1544
|
|
|
1344
1545
|
// 开发期自动化接口:截图回归要从外部驱动「换配色 / 换明暗」,而这两件事
|
|
1345
1546
|
// 平时只能点界面。暴露一组 setter,让 CDP 的 Runtime.evaluate 能直接调用。
|
|
@@ -1354,6 +1555,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1354
1555
|
setScrollMode,
|
|
1355
1556
|
setControlMode,
|
|
1356
1557
|
getPreference: () => theme.getTheme().preference,
|
|
1558
|
+
setLangPref,
|
|
1559
|
+
locales: LOCALES,
|
|
1357
1560
|
setTheme: (id) => theme.setTheme(id),
|
|
1358
1561
|
schemes: SCHEMES.map((s) => s.id),
|
|
1359
1562
|
})
|
|
@@ -1375,7 +1578,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1375
1578
|
window.addEventListener('pageshow', healOnShow)
|
|
1376
1579
|
} catch (err) { /* 无 DOM 时跳过 */ }
|
|
1377
1580
|
try { ctx.on('theme/change', healOnTheme) } catch (err) { /* 事件名变了就只靠定时器 */ }
|
|
1378
|
-
try {
|
|
1581
|
+
try {
|
|
1582
|
+
healTimer = setInterval(() => ensureApplied('interval'), 4000)
|
|
1583
|
+
if (healTimer && typeof healTimer.unref === 'function') healTimer.unref()
|
|
1584
|
+
} catch (err) { /* 无定时器环境 */ }
|
|
1379
1585
|
|
|
1380
1586
|
ctx.effect(() => {
|
|
1381
1587
|
applyAll('boot')
|
|
@@ -1391,6 +1597,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1391
1597
|
window.removeEventListener('pageshow', healOnShow)
|
|
1392
1598
|
} catch (err) { /* 同上 */ }
|
|
1393
1599
|
if (healTimer !== null) { clearInterval(healTimer); healTimer = null }
|
|
1600
|
+
if (langObserver !== null) { langObserver.disconnect(); langObserver = null }
|
|
1394
1601
|
}
|
|
1395
1602
|
}, 'dsh-theme-palette: token layer + 右栏样式')
|
|
1396
1603
|
|
|
@@ -1402,7 +1609,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1402
1609
|
// 内置节顺序:account -10 / general 0 / models 10 / plugins 15 / agent-presets 20;
|
|
1403
1610
|
// 5 = 紧跟「通用」之后。
|
|
1404
1611
|
order: 5,
|
|
1405
|
-
label: () => (
|
|
1612
|
+
label: () => TEXT[pickLocale(readLangPref())].nav,
|
|
1406
1613
|
inject: () => ({
|
|
1407
1614
|
getCurrent,
|
|
1408
1615
|
subscribe,
|
|
@@ -1425,6 +1632,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1425
1632
|
getMdMode,
|
|
1426
1633
|
subscribeMd,
|
|
1427
1634
|
setMdMode,
|
|
1635
|
+
getLangPref,
|
|
1636
|
+
getLocale,
|
|
1637
|
+
subscribeLocale,
|
|
1638
|
+
setLangPref,
|
|
1428
1639
|
getHealth,
|
|
1429
1640
|
subscribeHealth,
|
|
1430
1641
|
}),
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-theme-palette",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.js",
|
|
9
|
-
"./
|
|
9
|
+
"./locale/*.json": "./locale/*.json",
|
|
10
10
|
"./icon": "./icon.svg"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
@@ -25,13 +25,18 @@
|
|
|
25
25
|
"tools/compare.mjs",
|
|
26
26
|
"tools/release.mjs",
|
|
27
27
|
"CHANGELOG.md",
|
|
28
|
-
"docs/DESIGN.md"
|
|
28
|
+
"docs/DESIGN.md",
|
|
29
|
+
"README-zh.md",
|
|
30
|
+
"README-es.md",
|
|
31
|
+
"README-pt.md",
|
|
32
|
+
"README-hi.md",
|
|
33
|
+
"locale"
|
|
29
34
|
],
|
|
30
35
|
"peerDependencies": {
|
|
31
36
|
"@deepseek-ai/cordis": "^4.0.0"
|
|
32
37
|
},
|
|
33
38
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
39
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
35
40
|
},
|
|
36
41
|
"dsh": {
|
|
37
42
|
"bundle": {
|
|
@@ -67,5 +72,6 @@
|
|
|
67
72
|
"publishConfig": {
|
|
68
73
|
"access": "public",
|
|
69
74
|
"registry": "https://registry.npmjs.org"
|
|
70
|
-
}
|
|
75
|
+
},
|
|
76
|
+
"packageManager": "pnpm@11.7.0"
|
|
71
77
|
}
|
|
@@ -11,6 +11,9 @@ import { fileURLToPath } from 'node:url'
|
|
|
11
11
|
|
|
12
12
|
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
13
13
|
const root = path.resolve(here, '..')
|
|
14
|
+
const argv = process.argv.slice(2)
|
|
15
|
+
const li = argv.indexOf('--lang')
|
|
16
|
+
const LANG = li >= 0 ? argv[li + 1] : 'auto' // auto | zh-CN | zh-TW | en | ja
|
|
14
17
|
const source = fs.readFileSync(path.join(root, 'client.js'), 'utf8')
|
|
15
18
|
|
|
16
19
|
let engine = null
|
|
@@ -20,7 +23,7 @@ const sandbox = {
|
|
|
20
23
|
__DSH_THEME_PALETTE_DEV__: (api) => { engine = api },
|
|
21
24
|
},
|
|
22
25
|
localStorage: { getItem: () => null, setItem: () => {} },
|
|
23
|
-
document: { head: { appendChild() {} }, createElement: () => ({ dataset: {}, remove() {} }), querySelector: () => null, documentElement: { lang: 'zh' } },
|
|
26
|
+
document: { head: { appendChild() {} }, createElement: () => ({ dataset: {}, remove() {} }), querySelector: () => null, documentElement: { lang: LANG === 'auto' ? 'zh-CN' : LANG } },
|
|
24
27
|
navigator: { language: 'zh' },
|
|
25
28
|
}
|
|
26
29
|
new Function('window', 'localStorage', 'document', 'navigator', source)(
|
|
@@ -106,6 +109,8 @@ const props = {
|
|
|
106
109
|
getScrollMode: () => 'tinted', subscribeScroll: () => () => {}, setScrollMode: () => {},
|
|
107
110
|
getControlMode: () => 'accent', subscribeControl: () => () => {}, setControlMode: () => {},
|
|
108
111
|
getMdMode: () => 'on', subscribeMd: () => () => {}, setMdMode: () => {},
|
|
112
|
+
getLangPref: () => (LANG === 'auto' ? 'auto' : LANG), getLocale: () => (LANG === 'auto' ? 'zh-CN' : LANG),
|
|
113
|
+
subscribeLocale: () => () => {}, setLangPref: () => {},
|
|
109
114
|
getHealth: () => ({ ok: true, checked: 5 }), subscribeHealth: () => () => {},
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -123,7 +128,7 @@ html,body{margin:0;background:#0a0a0a;font-family:-apple-system,"Segoe UI","Micr
|
|
|
123
128
|
.wrap{padding:18px;border-radius:16px;border:1px solid rgba(128,128,128,.3);background:${canvas};${style};color:var(--dsw-alias-label-primary)}
|
|
124
129
|
</style><style>${engine.settingsCss}</style></head>
|
|
125
130
|
<body${dark}><div class="page"><div class="wrap">${toHtml(body)}</div></div></body></html>`
|
|
126
|
-
const out = path.join(here, `preview-${mode}.html`)
|
|
131
|
+
const out = path.join(here, `preview-${mode}${LANG === 'auto' ? '' : '-' + LANG}.html`)
|
|
127
132
|
fs.writeFileSync(out, html)
|
|
128
133
|
console.log('✓ ' + out + '(' + Math.round(html.length / 1024) + ' KB)')
|
|
129
134
|
}
|