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 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
- DSH Web / Desktop 的主题配色插件:**4 套配色 × 明暗两模式**,两个轴互相独立。
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
- 当前版本 **1.3.3**。更新记录 [CHANGELOG.md](./CHANGELOG.md) · 逐组件设计说明 [docs/DESIGN.md](./docs/DESIGN.md)。
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
- | 更多选项 | Markdown 强调、滚动条、开关/勾选 |
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
- 选择即时生效,存在 `localStorage`;明暗仍由官方「外观」控制,两者独立。
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
- 从 npm(推荐):
26
+ ## Install
33
27
 
34
28
  ```bash
35
- # 方式 A:装进 DSH profile
29
+ # from npm
36
30
  cd ~/.dsh/profiles/desktop
37
31
  pnpm add dsh-theme-palette
38
32
 
39
- # 方式 B:用 DSH 的插件命令
40
- dsh plugin --profile desktop add dsh-theme-palette
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
- git clone https://github.com/zuoyunlai/dsh-theme-palette.git
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
- 装完**必须重启 GUI**(运行中的进程仍持旧模块)。
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
- 链接列取 `--dsw-alias-link`(从基准色派生出来的那一档),**不是**色阶中段 `500` ——
97
- 见 §品牌基准色与链接。
45
+ ## Configuration
98
46
 
99
- `875 / 850 / 800` 分别是宿主的 `bg-layer-1 / -2 / -3`(`bg-module-platform` 与 layer-3 同为 `800`),
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
- | `client.js` | 客户端:OKLCH 引擎 + `overrideTokens` 覆盖层 + `settings.section` 页面 |
138
- | `index.js` | Host 半边,空实现 |
139
- | `cordis.patch.yml` | bundle 补丁:插入本插件那一行 |
140
- | `palette-dev.mjs` | 报表脚本:`node palette-dev.mjs`,**直接加载 client.js 的生产引擎** |
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
- 换强调色只改这三个数(`accentHue` / `accentC` / `accentL`),色阶其余档、深色侧强调、
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
- | 套 | darkC | 占可用色域 | darkLift | 深底 L | paperC |
165
- |---|---|---|---|---|---|
166
- | 暖砂 | 0.014 | 30% | 0.026 | 0.223 | 0.014 |
167
- | 靛蓝 | 0.028 | 19% | 0.032 | 0.227 | 0.009 |
168
- | 苔绿 | 0.015 | 24% | 0.042 | 0.239 | 0.0125 |
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
- 调参时先看**占比**再看数值:`darkC` 除以该 L 与色相下的 sRGB 可用色度上限,落在 20–30% 是
171
- 「有色但不脏」;低于 15% 会读成灰(蓝色最容易踩),高于 40% 开始读成泥(黄色最容易踩)。
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
- 加一套配色就往 `SCHEMES` 里加一条,卡片、覆盖层、对比度求解、报表全自动。
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 tools/release.mjs --dry # 本地检查:语法 / 工作区 / 打包内容 / 版本是否已存在
182
- node tools/release.mjs # 真发布(npm 已登录)+ 等 registry 放出 + 校验 latest
183
- node tools/release.mjs --tag # 发布后再打 tag 并推 main + tag
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
- 脚本要处理三件容易出错的事:本机 npm 默认源是 npmmirror(**必须显式指官方源**);
187
- 新包名的首次发布会进**暂存审核**(CLI 照打印 `+ pkg@ver`,但 registry 要过几分钟才 200);
188
- `latest` 不保证自动前移。2026-10-21 实测:提交到放出约 **8 分钟**。
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.3.3'
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
- function isZh() {
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
- return /^zh/i.test(lang)
984
- } catch (err) { return true }
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: '让正文/次级文字带配色色味;对比度由求解器兜底,三档都满足 AA。',
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 all use near-black/white in the host. Turn this on to use the accent colour.',
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: 'Scrollbars ship neutral grey. Tint follows the palette hue; Accent uses the accent colour. Lightness is unchanged.',
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. Set it to the left sidebar colour to match.',
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; both follow the palette.',
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 still meet AA.',
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, in both color modes.',
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 txt = isZh() ? TEXT.zh : TEXT.en
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
- (isZh() ? '配色层被宿主丢过一次,已自动恢复 ' : 'Palette layer was dropped by the host; restored ') + health.reapplies + (isZh() ? ' 次' : 'x'),
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.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode)),
1137
- row(txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode)),
1138
- row(txt.control, txt.controlSub, seg(CONTROL_MODES, controlMode, props.setControlMode)),
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) return
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 { healTimer = setInterval(() => ensureApplied('interval'), 4000) } catch (err) { /* 无定时器环境 */ }
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: () => (isZh() ? TEXT.zh.nav : TEXT.en.nav),
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
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Theme palette",
4
+ "description": "Four palettes (Mono, Warm sand, Indigo, Moss) across light and dark, computed in OKLCH on top of the DSH theme tokens."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "主题配色",
4
+ "description": "四套配色(黑白 / 暖砂 / 靛蓝 / 苔绿)× 明暗两模式,在 DSH 主题 token 之上用 OKLCH 重算整条色阶。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.3.3",
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
- "./client": "./client.js",
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": ">=20"
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
  }