dsh-theme-palette 1.9.10 → 1.10.0

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.10.0
8
+ 按审计报告的改进清单做了六件事:色彩数学单测、高对比跟随系统、运行时覆盖报告、色觉缺陷守门、单色派生,另外把 display-P3 的可行性量清楚后决定**本期不做**。其中「色觉缺陷守门」第一次跑就抓到一处真问题(浅色链接在模拟下低于 AA),下限因此微调。
9
+
10
+ - **色彩数学单测**(新增 `tools/check-colormath.mjs`,约 40 条断言,并入 `npm run check` 与发版回归清单):锚点(OKLab 里的白 / 黑 / 中灰)、OKLCH↔hex 往返、色域裁切「保明度保色相」、WCAG 样例值(`#777` on `#fff` = 4.48、黑白 21)、`flatten()` 的 α 合成交换性、`harmonize()` 的三条不变量(保 L、色度按比例、色相朝目标按 `pull` 走)。以前这些函数只靠「界面上看着对」,现在挪一个常数就会红。
11
+ 写单测时踩了一次:`harmonize` 的色相断言最初写成「移动量 = pull」,实际是「按剩下的角度比例移动」(`dh1 ≈ pull × dh0`)—— 是断言错了,不是代码错。
12
+ - **高对比档新增「跟随系统」并设为默认**:系统开「提高对比度」(`prefers-contrast: more`)就自动用高对比档,关掉回标准档;监听 `change` 事件即时重算。手动选过「关 / 开」的用户读回自己的选择,升级不会被改成别的档位。
13
+ - **色觉缺陷守门**(`tools/check-audit.mjs` 第 20 组):四套配色 × 明暗 × 正文/次级/三级/链接 × 三个承载面 × deutan / protan / tritan(Machado 2009),断言最低对比度 ≥ 4.5。**这条断言一开始没过**:三级与链接的浅色下限原本压在 4.62(刚过 AA),模拟把比值推动 5–9% 后链接跌到 **4.22–4.34**。修法是把浅色下限抬到三级 **4.85**、链接 **4.95**,抬完三种模拟下最低 **4.61**。代价:浅色链接与三级文字比 1.9.x 略深一档。
14
+ - **自定义配色新增「由主色派生」**:只给一个主色,按它的色相与色度推出纸面与墨色(明度按当前明暗模式取引擎已验证的区间),派生结果直接过全部下限(实测正文 14.6–15.0 / 次级 6.8 / 三级 5.3 / 链接 5.4)。主色无彩时派生中性纸面。**它是一次性的填写动作,不是一种模式**:写进去的就是普通的三色,之后照常手改、导出分享码也只带这三个值;但正因为它会填这两格,面板里加了一行可见说明 ——「会覆盖你手动设置的底色与文字,派生之后仍可逐项手改」(四语种,`tools/check-custom.mjs` 断言这句必须是可见文本,不能只挂在 tooltip 上)。
15
+ - **自定义配色新增色觉提示**:把三个色按 deutan / protan / tritan 模拟后,若强调色与文字或底色的 ΔE 塌到 **12** 以下(典型是红 / 绿搭配),面板里给一行说明 —— 只提示,不阻止保存,也不改用户的颜色。
16
+ - **诊断导出新增运行时覆盖报告** `coverage`:枚举宿主样式表里所有 `--dsw-*`,分成「本插件接管 / var 引用 / 字面彩色 / 字面灰」四类,回答「宿主还有多少 token 没跟上配色」。只读、拿不到样式表就返回 `ok: false`。
17
+ - **display-P3:量完决定不做**。同一 L/h 下 P3 比 sRGB 能多给的色度:纸面层(00/60)只多 ΔC 0.0008–0.0053(ΔE 0.08–0.53,JND 以下,看不出来);能看出差别的是强调色(indigo ΔE 5.29、moss 2.96)。但强调色同时是链接 / info 填充 / 勾选支点的来源,按 P3 抬色度就要为 sRGB 回退再标定一套。数据与取舍写在 `docs/DESIGN.md`,将来若做,只值得给强调色加一层 `color(display-p3 …)`。
18
+ - 复核:`npm run check` 全绿(含新增单测与第 20 组);`palette-dev.mjs` 两档 exit 0,1080 组配置「未达标 0 / 低于 AA 4.5 = 0 / 硬失败 0」。
19
+
7
20
  ### 1.9.10
8
21
  撤除「右侧栏底色」这一档,并把它的技术债一起清干净(用户拍板)。**这不是修 bug,是承认做不到**:宿主右栏没有可染的落点,留着这个开关只会一直看起来像坏的。
9
22
 
package/README-es.md CHANGED
@@ -31,7 +31,7 @@ El plugin solo apila tokens de color mediante `ctx.theme.overrideTokens`; no reg
31
31
  - 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;
32
32
  - ajustable: intensidad del texto (3), profundidad de la barra lateral (3), énfasis de Markdown, barras de desplazamiento, color de interruptores;
33
33
  - **autorreparable**: si el host descarta la capa de override, la paleta se vuelve a aplicar;
34
- - **promesas de contraste**: el cuerpo tiene la promesa de 12:1 en claro y 10:1 en oscuro (15:1 en alto contraste), el secundario ≥ 6.2 en claro y 8.0 en oscuro, y el terciario y los enlaces ≥ 4.62 en claro y 6.4 en oscuro (los enlaces además pasan una puerta APCA Lc ≥ 60 en oscuro / 75 en HC), medidos sobre las superficies donde el texto realmente cae (el lado oscuro necesita más: un cociente WCAG sobreestima la legibilidad sobre fondo oscuro);
34
+ - **promesas de contraste**: el cuerpo tiene la promesa de 12:1 en claro y 10:1 en oscuro (15:1 en alto contraste), el secundario ≥ 6.2 en claro y 8.0 en oscuro, y el terciario ≥ 4.85 y los enlaces ≥ 4.95 en claro, y 6.4 en oscuro (los mínimos claros llevan un margen para déficits de visión cromática; los enlaces además pasan una puerta APCA Lc ≥ 60 en oscuro / 75 en HC), medidos sobre las superficies donde el texto realmente cae (el lado oscuro necesita más: un cociente WCAG sobreestima la legibilidad sobre fondo oscuro);
35
35
  - las superficies prometidas son 00/60 en claro y 950/875 en oscuro; las oscuras 800 (superficie de módulo) y 850 (sub-superficie) **no** respaldan el texto del cuerpo: el informe las cuenta aparte como «fuera de política» (ni cuentan como aprobado ni como fallo);
36
36
  - interfaz en 简体中文 / 繁體中文 / English / 日本語 (sigue el idioma del host y se puede fijar).
37
37
 
@@ -69,7 +69,7 @@ OKLCH(L, C, tono) ──► ajuste al gamut (búsqueda binaria en C) ──► h
69
69
 
70
70
  - 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;
71
71
  - superficies claras usan los pasos 00–300, las oscuras 800–950;
72
- - los mínimos de texto se exigen en tiempo de construcción: cuerpo ≥ 12:1 en claro y ≥ 10:1 en oscuro (alto contraste: 15:1), secundario ≥ 6.2 claro / 8.0 oscuro (HC 9 / 9.6), terciario/enlace ≥ 4.62 claro / 6.4 oscuro (HC 7 / 7.6), enlace con APCA Lc ≥ 60 oscuro (HC 75), texto en barra lateral ≥ 7, texto en burbuja ≥ 7;
72
+ - los mínimos de texto se exigen en tiempo de construcción: cuerpo ≥ 12:1 en claro y ≥ 10:1 en oscuro (alto contraste: 15:1), secundario ≥ 6.2 claro / 8.0 oscuro (HC 9 / 9.6), terciario/enlace ≥ 4.85 / 4.95 claro / 6.4 oscuro (HC 7 / 7.6), enlace con APCA Lc ≥ 60 oscuro (HC 75), texto en barra lateral ≥ 7, texto en burbuja ≥ 7;
73
73
  - las superficies prometidas son 00/60 en claro y 950/875 en oscuro; las oscuras 800 (superficie de módulo) y 850 (sub-superficie) **no** respaldan el texto del cuerpo: el informe las cuenta aparte como «fuera de política» (ni cuentan como aprobado ni como fallo), y toda superficie sigue teniendo que superar el suelo AA de 4.5;
74
74
  - el informe separa tres cosas: el objetivo declarado, el techo físico (lo que el blanco o el negro alcanzan en esa superficie) y la exención de política; 0.93 es solo margen del solucionador, para decidir si un objetivo queda por encima del techo;
75
75
  - `node palette-dev.mjs` comprueba 2160 combinaciones (5 paletas × 216 ajustes × los dos niveles de contraste).
package/README-hi.md CHANGED
@@ -31,7 +31,7 @@ DeepSeek Harness (वेब और डेस्कटॉप) के लिए
31
31
  - 27,000+ टोकन संदर्भ शामिल: मुख्य टेक्स्ट, द्वितीयक, तृतीयक, कैप्शन, लिंक, साइडबार, कार्ड, उपयोगकर्ता बबल, प्राइमरी बटन, स्क्रॉलबार, स्विच, Markdown शीर्षक/बोल्ड, कोड ब्लॉक;
32
32
  - समायोज्य: टेक्स्ट की तीव्रता (3), साइडबार की गहराई (3), Markdown ज़ोर, स्क्रॉलबार, स्विच का रंग;
33
33
  - **स्व-उपचार**: यदि होस्ट ओवरराइड परत हटा दे, पैलेट फिर से लगा दी जाती है;
34
- - **कंट्रास्ट के वादे**: मुख्य टेक्स्ट के लिए लाइट में 12:1 और डार्क में 10:1 (उच्च-कंट्रास्ट में 15:1), द्वितीयक ≥ 6.2 (लाइट) / 8.0 (डार्क), तृतीयक और लिंक ≥ 4.62 (लाइट) / 6.4 (डार्क), लिंक अतिरिक्त APCA Lc ≥ 60 (डार्क) / 75 (HC) — उन्हीं सतहों पर मापे गए जहाँ टेक्स्ट वास्तव में बैठता है (डार्क में ज़्यादा चाहिए: गहरे बैकग्राउंड पर WCAG अनुपात पढ़ने की सुविधा को बढ़ा-चढ़ा कर दिखाता है);
34
+ - **कंट्रास्ट के वादे**: मुख्य टेक्स्ट के लिए लाइट में 12:1 और डार्क में 10:1 (उच्च-कंट्रास्ट में 15:1), द्वितीयक ≥ 6.2 (लाइट) / 8.0 (डार्क), तृतीयक ≥ 4.85 और लिंक ≥ 4.95 (लाइट) / 6.4 (डार्क) — लाइट की सीमाओं में रंग-दृष्टि दोष के लिए गुंजाइश रखी गई है; लिंक अतिरिक्त APCA Lc ≥ 60 (डार्क) / 75 (HC) — उन्हीं सतहों पर मापे गए जहाँ टेक्स्ट वास्तव में बैठता है (डार्क में ज़्यादा चाहिए: गहरे बैकग्राउंड पर WCAG अनुपात पढ़ने की सुविधा को बढ़ा-चढ़ा कर दिखाता है);
35
35
  - वादे की गई सतहें लाइट में 00/60 और डार्क में 950/875 हैं; डार्क 800 (मॉड्यूल सतह) और 850 (उप-सतह) मुख्य टेक्स्ट को नहीं सहनाते — रिपोर्ट उन्हें अलग «नीति से बाहर» के रूप में गिनती है (न तो पास, न ही फेल);
36
36
  - इंटरफ़ेस 简体中文 / 繁體中文 / English / 日本語 में (होस्ट की भाषा का पालन करता है, हाथ से भी चुना जा सकता है)।
37
37
 
@@ -69,7 +69,7 @@ OKLCH(L, C, ह्यू) ──► गैमट क्लैंप (C पर
69
69
 
70
70
  - सेमांटिक एलियस `var(--dsw-static-neutral-bluish-NN)` श्रृंखलाएँ बनाए रखते हैं, इसलिए जो भी टोकन का पालन करता है वह पैलेट का भी करता है;
71
71
  - लाइट सतहें स्टेप 00–300, डार्क सतहें 800–950;
72
- - टेक्स्ट की न्यूनतम सीमा बनाते समय लागू: मुख्य लाइट में ≥ 12:1 और डार्क में ≥ 10:1 (उच्च-कंट्रास्ट: 15:1), द्वितीयक ≥ 6.2 लाइट / 8.0 डार्क (HC 9 / 9.6), तृतीयक/लिंक ≥ 4.62 लाइट / 6.4 डार्क (HC 7 / 7.6), लिंक APCA Lc ≥ 60 डार्क (HC 75), साइडबार में ≥ 7, बबल में ≥ 7;
72
+ - टेक्स्ट की न्यूनतम सीमा बनाते समय लागू: मुख्य लाइट में ≥ 12:1 और डार्क में ≥ 10:1 (उच्च-कंट्रास्ट: 15:1), द्वितीयक ≥ 6.2 लाइट / 8.0 डार्क (HC 9 / 9.6), तृतीयक/लिंक ≥ 4.85 / 4.95 लाइट / 6.4 डार्क (HC 7 / 7.6), लिंक APCA Lc ≥ 60 डार्क (HC 75), साइडबार में ≥ 7, बबल में ≥ 7;
73
73
  - वादे की गई सतहें लाइट में 00/60 और डार्क में 950/875 हैं; डार्क 800 (मॉड्यूल सतह) और 850 (उप-सतह) मुख्य टेक्स्ट को नहीं सहनाते — रिपोर्ट उन्हें अलग «नीति से बाहर» के रूप में गिनती है (न तो पास, न ही फेल), और हर सतह को AA की न्यूनतम सीमा 4.5 से गुज़रना ही होता है;
74
74
  - रिपोर्ट तीन बातें अलग रखती है: घोषित लक्ष्य, भौतिक सीमा (उस सतह पर सफ़ेद या काला कितना हासिल कर सकता है) और नीति छूट; 0.93 केवल सॉल्वर का मार्जिन है, इससे तय होता है कि लक्ष्य सीमा से ऊपर है या नहीं;
75
75
  - `node palette-dev.mjs` 2160 संयोजन जाँचता है (5 पैलेट × 216 सेटिंग × दोनों कंट्रास्ट स्तर)।
package/README-pt.md CHANGED
@@ -31,7 +31,7 @@ O plugin apenas empilha tokens de cor via `ctx.theme.overrideTokens`; não regis
31
31
  - 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;
32
32
  - ajustável: intensidade do texto (3), profundidade da barra lateral (3), ênfase de Markdown, barras de rolagem, cor dos interruptores;
33
33
  - **autorrecuperação**: se o host descartar a camada de override, a paleta é reaplicada;
34
- - **promessas de contraste**: o corpo tem a promessa de 12:1 no claro e 10:1 no escuro (15:1 em alto contraste), o secundário ≥ 6.2 no claro e 8.0 no escuro, e o terciário e os links ≥ 4.62 no claro e 6.4 no escuro (os links ainda passam por um portão APCA Lc ≥ 60 no escuro / 75 em HC), medidos nas superfícies onde o texto realmente cai (o lado escuro precisa de mais: uma razão WCAG superestima a legibilidade sobre fundo escuro);
34
+ - **promessas de contraste**: o corpo tem a promessa de 12:1 no claro e 10:1 no escuro (15:1 em alto contraste), o secundário ≥ 6.2 no claro e 8.0 no escuro, e o terciário ≥ 4.85 e os links ≥ 4.95 no claro, e 6.4 no escuro (os pisos do lado claro têm margem para deficiências de visão cromática; os links ainda passam por um portão APCA Lc ≥ 60 no escuro / 75 em HC), medidos nas superfícies onde o texto realmente cai (o lado escuro precisa de mais: uma razão WCAG superestima a legibilidade sobre fundo escuro);
35
35
  - as superfícies prometidas são 00/60 no claro e 950/875 no escuro; as escuras 800 (superfície de módulo) e 850 (sub-superfície) **não** sustentam o texto do corpo: o relatório as conta à parte como «fora de política» (nem contam como aprovado nem como falha);
36
36
  - interface em 简体中文 / 繁體中文 / English / 日本語 (segue o idioma do host e pode ser fixada).
37
37
 
@@ -69,7 +69,7 @@ OKLCH(L, C, matiz) ──► limite de gamut (busca binária em C) ──► hex
69
69
 
70
70
  - os aliases semânticos mantêm cadeias `var(--dsw-static-neutral-bluish-NN)`, então tudo que segue um token segue a paleta;
71
71
  - superfícies claras usam os passos 00–300, as escuras 800–950;
72
- - os pisos de texto são exigidos na construção: corpo ≥ 12:1 no claro e ≥ 10:1 no escuro (alto contraste: 15:1), secundário ≥ 6.2 claro / 8.0 escuro (HC 9 / 9.6), terciário/link ≥ 4.62 claro / 6.4 escuro (HC 7 / 7.6), link com APCA Lc ≥ 60 escuro (HC 75), texto na barra lateral ≥ 7, texto no balão ≥ 7;
72
+ - os pisos de texto são exigidos na construção: corpo ≥ 12:1 no claro e ≥ 10:1 no escuro (alto contraste: 15:1), secundário ≥ 6.2 claro / 8.0 escuro (HC 9 / 9.6), terciário/link ≥ 4.85 / 4.95 claro / 6.4 escuro (HC 7 / 7.6), link com APCA Lc ≥ 60 escuro (HC 75), texto na barra lateral ≥ 7, texto no balão ≥ 7;
73
73
  - as superfícies prometidas são 00/60 no claro e 950/875 no escuro; as escuras 800 (superfície de módulo) e 850 (sub-superfície) **não** sustentam o texto do corpo: o relatório as conta à parte como «fora de política» (nem contam como aprovado nem como falha), e toda superfície ainda precisa passar pelo piso AA de 4.5;
74
74
  - o relatório separa três coisas: a meta declarada, o teto físico (o que o branco ou o preto alcançam nessa superfície) e a isenção de política; 0.93 é apenas margem do solucionador, usada para decidir se uma meta fica acima do teto;
75
75
  - `node palette-dev.mjs` verifica 2160 combinações (5 paletas × 216 ajustes × os dois níveis de contraste).
package/README-zh.md CHANGED
@@ -32,7 +32,7 @@ package.json 的 `engines` 字段写的是 `^22.19.0 || >=24.0.0`——与上表
32
32
  - 可调:文字着色三档、侧栏三档、Markdown 强调、滚动条、开关配色;
33
33
  - **自定义配色**(给 底色/文字/强调 三色,文字色相也参与派生;过亮/过暗的强调色会归一化)、**配置导入导出**(`dshs1.` 短码含三色)、**高对比**档、4 种界面语言;
34
34
  - **自愈**:宿主丢弃 override 层时自动重放;
35
- - **对比度承诺**:正文浅色 12:1、深色 10:1(高对比 15:1),次级 ≥ 6.2(深色 8.0),三级/链接 ≥ 4.62(深色 6.4),链接另过 APCA Lc ≥ 60(高对比 75)—— 都在文字真实落到的承载面上实测(深色要高一些:WCAG 比值在暗底上会高估可读性);
35
+ - **对比度承诺**:正文浅色 12:1、深色 10:1(高对比 15:1),次级 ≥ 6.2(深色 8.0),三级 ≥ 4.85、链接 ≥ 4.95(深色 6.4;浅色留了色觉缺陷余量),链接另过 APCA Lc ≥ 60(高对比 75)—— 都在文字真实落到的承载面上实测(深色要高一些:WCAG 比值在暗底上会高估可读性);
36
36
  - 承诺面是浅色 00/60 与深色 950/875;深色 800(模块面)与 850(次级深面)**不为正文兜底**,报表单列为「政策外」(既不算达标也不算失败);
37
37
  - 界面支持 简体中文 / 繁體中文 / English / 日本語(跟随宿主,也可手选)。
38
38
 
@@ -70,7 +70,7 @@ OKLCH(L, C, hue) ──► 色域钳位(对 C 二分) ──► sRGB hex
70
70
 
71
71
  - 语义别名保留 `var(--dsw-static-neutral-bluish-NN)` 引用链,跟 token 的元素就跟着配色走;
72
72
  - 浅色面用色阶 00–300,深色面用 800–950;
73
- - 文字下限在构建期强制:正文浅色 ≥ 12:1、深色 ≥ 10:1(高对比 15:1),次级 ≥ 6.2 浅 / 8.0 深(HC 9 / 9.6),三级/链接 ≥ 4.62 浅 / 6.4 深(HC 7 / 7.6),链接另过 APCA Lc ≥ 60 深 / 75 HC,侧栏内 ≥ 7,气泡内 ≥ 7;
73
+ - 文字下限在构建期强制:正文浅色 ≥ 12:1、深色 ≥ 10:1(高对比 15:1),次级 ≥ 6.2 浅 / 8.0 深(HC 9 / 9.6),三级/链接 ≥ 4.85 / 4.95 浅 / 6.4 深(HC 7 / 7.6),链接另过 APCA Lc ≥ 60 深 / 75 HC,侧栏内 ≥ 7,气泡内 ≥ 7;
74
74
  - 承诺面是浅色 00/60 与深色 950/875;深色 800(模块面)与 850(次级深面)**不为正文兜底**,报表单列为「政策外」(既不算达标也不算失败),且每一档承载面仍须过 AA 底线 4.5;
75
75
  - 报表把三件事分开:声明目标、物理上限(该承载面上黑或白前景能达到的对比度)、政策豁免;0.93 只是求解余量,仅用于判断目标是否高于上限;
76
76
  - `node palette-dev.mjs` 扫全部 2160 组配置(5 套配色 × 216 组设置 × 标准/高对比两档)并打印承载面报表,`--hc` 把主报表切到高对比档;硬失败时退出码 1。
package/README.md CHANGED
@@ -32,7 +32,7 @@ The plugin only stacks colour tokens through `ctx.theme.overrideTokens`; it does
32
32
  - adjustable: text tint (3), sidebar depth (3), Markdown emphasis, scrollbars, switch colour;
33
33
  - **custom palette** (pick surface / text / accent — the engine keeps its contrast guarantees), **config export/import** via a `dshs1.` code, **high-contrast** mode, and 4 UI languages;
34
34
  - **self-healing**: if the host drops the override layer, the palette is re-applied;
35
- - **contrast promises**: body text is promised 12:1 in light and 10:1 in dark (15:1 in high contrast), secondary ≥ 6.2 in light / 8.0 in dark, tertiary and links ≥ 4.62 in light / 6.4 in dark (links also clear an APCA Lc ≥ 60 gate in dark / 75 in HC) — measured on the surfaces the text actually lands on (the dark side needs more, because a WCAG ratio overstates readability on a dark background);
35
+ - **contrast promises**: body text is promised 12:1 in light and 10:1 in dark (15:1 in high contrast), secondary ≥ 6.2 in light / 8.0 in dark, tertiary ≥ 4.85 and links ≥ 4.95 in light / 6.4 in dark (the light floors carry a colour-vision headroom; links also clear an APCA Lc ≥ 60 gate in dark / 75 in HC) — measured on the surfaces the text actually lands on (the dark side needs more, because a WCAG ratio overstates readability on a dark background);
36
36
  - the promised surfaces are light 00/60 and dark 950/875; dark 800 (module surface) and 850 (sub-surface) do **not** back body text — the report counts them apart as "out of policy" (neither pass nor fail);
37
37
  - UI in 简体中文 / 繁體中文 / English / 日本語 (follows the host language, can be pinned).
38
38
 
@@ -70,7 +70,7 @@ OKLCH(L, C, hue) ──► gamut clamp (binary search on C) ──► sRGB hex
70
70
 
71
71
  - semantic aliases keep `var(--dsw-static-neutral-bluish-NN)` chains, so anything following a token follows the palette;
72
72
  - light surfaces use steps 00–300, dark surfaces 800–950;
73
- - text floors are enforced at build time: body ≥ 12:1 in light and ≥ 10:1 in dark (high contrast: 15:1), secondary ≥ 6.2 in light / 8.0 in dark (HC 9 / 9.6), tertiary/link ≥ 4.62 in light / 6.4 in dark (HC 7 / 7.6), link APCA Lc ≥ 60 in dark (HC 75), sidebar text ≥ 7, bubble text ≥ 7;
73
+ - text floors are enforced at build time: body ≥ 12:1 in light and ≥ 10:1 in dark (high contrast: 15:1), secondary ≥ 6.2 in light / 8.0 in dark (HC 9 / 9.6), tertiary/link ≥ 4.85 / 4.95 in light / 6.4 in dark (HC 7 / 7.6), link APCA Lc ≥ 60 in dark (HC 75), sidebar text ≥ 7, bubble text ≥ 7;
74
74
  - the promised surfaces are light 00/60 and dark 950/875; dark 800 (module surface) and 850 (sub-surface) do **not** back body text — the report counts them apart as "out of policy" (neither pass nor fail), and every surface still has to clear the AA floor of 4.5;
75
75
  - the report separates three things: the declared target, the physical ceiling (what black or white can reach on that surface) and the policy exemption; 0.93 is only a solver margin, used to decide whether a target sits above the ceiling;
76
76
  - 2160 configuration combos (5 palettes × 216 settings × both contrast tiers) are checked by `node palette-dev.mjs`.
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.9.10'
30
+ const PLUGIN_VERSION = '1.10.0'
31
31
  const STORE_KEY = 'dsh-theme-palette.v1'
32
32
  const OVERRIDE_SOURCE = 'dsh-theme-palette'
33
33
 
@@ -238,10 +238,36 @@ window.__ModuleLoader__.load({
238
238
  }
239
239
  }
240
240
 
241
+ /**
242
+ * 只给一个主色时派生整套自定义配色(1.10.0,审计建议 4)。
243
+ *
244
+ * 用户往往只有一个「想要的品牌色」,让他自己配底色/文字色既难又容易把对比度配坏。
245
+ * 这里按主色的色相与色度推出一对「纸面 + 墨色」:
246
+ * · 色相 = 主色色相;色度取主色的 15%–22% 并夹住上限 —— 纸面只该有一点点色,不该是整片主色;
247
+ * · 明度由明暗模式决定(亮底纸面 0.968 / 墨 0.235;暗底 0.165 / 0.935),这是引擎已验证的区间;
248
+ * · 主色原样保留,交给 customScheme 去钳位与求解(它会给链接/强调色兜对比度)。
249
+ * 主色无彩(C < 0.004)时退化为中性纸面,避免「黑底 + 白字 + 黄强调派生出一套淡粉」那类老问题。
250
+ */
251
+ function deriveCustomFromAccent(accentHex, pref) {
252
+ if (typeof accentHex !== 'string' || !HEX_RE.test(accentHex)) return null
253
+ const [L, C, h] = toOklch(accentHex)
254
+ const neutral = C < 0.004
255
+ const dark = pref === 'dark'
256
+ const tint = (scale, lo, hi) => (neutral ? 0 : Math.min(hi, Math.max(lo, C * scale)))
257
+ const bg = fromOklch(dark ? 0.165 : 0.968, tint(0.22, 0.006, 0.014), h)
258
+ const text = fromOklch(dark ? 0.935 : 0.235, tint(0.18, 0.004, 0.012), h)
259
+ return { bg, text, accent: String(accentHex).toLowerCase(), accentL: L }
260
+ }
261
+
241
262
  const MIN_MAIN = 12
242
263
  const MIN_SECONDARY = 6.2
243
- const MIN_TERTIARY = 4.62
244
- const MIN_LINK = 4.62
264
+ /* 三级/链接的浅色下限从 4.62 抬到 4.85 / 4.95(1.10.0):4.62 是「刚好过 AA」,
265
+ * 一点余量都不留 —— 而色觉缺陷模拟(Machado 2009,deutan/protan)会让同一对
266
+ * 前景/背景的比值上下浮动 5–9%,实测链接在 4.62 时就跌到 4.22–4.34(低于 AA)。
267
+ * 抬到 4.95 后三种色觉模拟下的最低值仍有 4.5+(tools/check-audit.mjs 第 19 组守着)。
268
+ * 代价:浅色链接与三级文字比 1.9.x 略深一档,肉眼几乎无差别。 */
269
+ const MIN_TERTIARY = 4.85
270
+ const MIN_LINK = 4.95
245
271
  /* 深色链接不再用 WCAG 比值兜底(1.9.9 补):深色面上同一比值换到的 Lc 差得远,
246
272
  4.62 在自定义配色的 875 面上只有 Lc 52.7,低于本插件给文字定的 Lc 60 下限。
247
273
  tuneRamps 里改为沿亮度把 Lc 顶到 60(高对比 75),这里只保留浅色侧的下限。 */
@@ -500,12 +526,16 @@ window.__ModuleLoader__.load({
500
526
  { id: 'off', label: '同正文' },
501
527
  ]
502
528
  const DEFAULT_MD = 'on'
503
- /** 高对比开关(反哺 A-4):不改配色,把文字下限抬到 AAA。 */
529
+ /** 高对比开关(反哺 A-4):不改配色,把文字下限抬到 AAA。
530
+ * auto(1.10.0,审计 3.11):跟随系统 prefers-contrast: more ——
531
+ * 系统开了「提高对比度」就自动用高对比档,没开就用标准档。
532
+ * 已存过 off/on 的老用户不受影响(读回自己的选择)。 */
504
533
  const HC_MODES = [
534
+ { id: 'auto', label: '跟随系统' },
505
535
  { id: 'off', label: '关' },
506
536
  { id: 'on', label: '开' },
507
537
  ]
508
- const DEFAULT_HC = 'off'
538
+ const DEFAULT_HC = 'auto'
509
539
  const MD_KEY = 'dsh-theme-palette.md.v1'
510
540
 
511
541
  /**
@@ -720,6 +750,40 @@ window.__ModuleLoader__.load({
720
750
  return rgbToHex(fg.map((v, i) => v * a + bg[i] * (1 - a)))
721
751
  }
722
752
 
753
+ /* ---------- 色觉缺陷模拟(Machado 2009,严重度 1.0)----------
754
+ * 只用于「告诉用户这个配色对色觉缺陷者是否还分得开」,不参与任何求解:
755
+ * 求解按明度走就已经对色觉友好,这里是把结论显示出来。
756
+ * 矩阵作用在线性 sRGB 上,和公开实现一致。
757
+ */
758
+ const CVD_MATRICES = {
759
+ deutan: [[0.367322, 0.860646, -0.227968], [0.280085, 0.672501, 0.047413], [-0.011820, 0.042940, 0.968881]],
760
+ protan: [[0.152286, 1.052583, -0.204868], [0.114503, 0.786281, 0.099216], [-0.003882, -0.048116, 1.051998]],
761
+ tritan: [[1.255528, -0.076749, -0.178779], [-0.078411, 0.930809, 0.147602], [0.004733, 0.691367, 0.303900]],
762
+ }
763
+ function simulateCvd(hex, kind) {
764
+ const m = CVD_MATRICES[kind]
765
+ if (!m || typeof hex !== 'string') return hex
766
+ const lin = hexToRgb(hex).map((v) => {
767
+ const s = v / 255
768
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)
769
+ })
770
+ const out = m.map((row) => row[0] * lin[0] + row[1] * lin[1] + row[2] * lin[2])
771
+ return rgbToHex(out.map((v) => {
772
+ const c = v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055
773
+ return clamp(c * 255, 0, 255)
774
+ }))
775
+ }
776
+ /** 两个颜色在给定色觉模拟下的 OKLab 距离(×100,与设计软件的 ΔE 同量级)。 */
777
+ function deltaE(a, b, kind) {
778
+ const x = toOklch(simulateCvd(a, kind))
779
+ const y = toOklch(simulateCvd(b, kind))
780
+ const toLab = ([L, C, h]) => [L, C * Math.cos((h * Math.PI) / 180), C * Math.sin((h * Math.PI) / 180)]
781
+ const p = toLab(x)
782
+ const q = toLab(y)
783
+ return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]) * 100
784
+ }
785
+
786
+
723
787
  /**
724
788
  * 语义色温度调和:保留色相为主(绿仍是绿、红仍是红),只把色相往配色的暖/冷方向
725
789
  * 拉一小段、并压低饱和度。用于 file diff 这种大面积色块——不能整块换成主题色,
@@ -1862,6 +1926,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1862
1926
  .dsh-tp-hexerror{font-size:11.5px;line-height:16px;color:var(--dsw-alias-state-error-primary)}
1863
1927
  .dsh-tp-hexnote{font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary)}
1864
1928
  .dsh-tp-custom-hint{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
1929
+ /* 「由主色派生」的副作用说明:左侧一道细边,读起来是提示而不是正文(1.10.0) */
1930
+ .dsh-tp-custom-note{margin:0;padding:6px 0 6px 8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);border-left:2px solid var(--dsw-alias-border-l3)}
1865
1931
  .dsh-tp-note{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}
1866
1932
  .dsh-tp-status{margin:0;font-size:11.5px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
1867
1933
  .dsh-tp-status.is-bad{color:var(--dsw-alias-state-error-primary)}
@@ -2084,7 +2150,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2084
2150
  function readHcStored() {
2085
2151
  try {
2086
2152
  const raw = localStorage.getItem(HC_KEY)
2087
- if (raw === 'on' || raw === 'off') return raw
2153
+ if (raw === 'on' || raw === 'off' || raw === 'auto') return raw
2088
2154
  } catch (err) { /* 退回默认 */ }
2089
2155
  return DEFAULT_HC
2090
2156
  }
@@ -2222,10 +2288,14 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2222
2288
  hc: '高对比',
2223
2289
  custom: '自定义配色',
2224
2290
  customHint: '取三个色的色相与色度;过亮/过暗的强调色会归一化以保证可读,明度由引擎按模式计算。',
2225
- notice: '1.9.9 按独立审计逐条修订:深色文字下限单独提高(次级 8 / 三级 6.4)、文字着色三档对预置配色真正生效、色阶不再出现「编号越大反而越亮」、高对比档的边框与滚动条也真提高了;报表新增 APCA 列。',
2291
+ derive: '由主色派生',
2292
+ deriveHint: '用当前强调色的色相与色度推导底色与文字(明暗跟随当前模式)',
2293
+ deriveNote: '「由主色派生」以强调色为唯一输入,自动算出底色与文字 —— 会覆盖你手动设置的底色与文字;派生之后仍可逐项手改。',
2294
+ notice: '1.10.0:高对比档新增「跟随系统」(系统开提高对比度就自动切高对比);自定义配色可「由主色派生」底色与文字;三级与链接的浅色下限抬高,色觉缺陷模拟下也守 AA;诊断信息新增覆盖报告。',
2226
2295
  dismiss: '知道了',
2227
2296
  explainClamp: '强调色已归一化:{from} → {to}(保证按钮与勾选可读)',
2228
2297
  explainLink: '链接已为可读性压暗:{ratio}:1(目标 {target}:1)',
2298
+ explainCvd: '色觉缺陷提示:{kind} 视觉下强调色与底色/文字几乎同色(ΔE {dE})。文字仍可读,但按钮与色块会分不开;换一个更亮或更暗的强调色即可。',
2229
2299
  preview: '预览',
2230
2300
  sample: '试试示例',
2231
2301
  copyDiag: '复制诊断',
@@ -2261,7 +2331,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2261
2331
  importBad: '短码无效,已忽略',
2262
2332
  badHex: 'HEX 需为 #RRGGBB',
2263
2333
  hexStale: '这个值刚被别处改过,已保留最新值',
2264
- hcSub: '尽量把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三级 ≥7);深色 800/850 不为正文兜底(政策外),但仍守 AA 底线。',
2334
+ hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三级 ≥7);「跟随系统」= 系统「提高对比度」开启时自动切高对比档;深色 800/850 不为正文兜底(政策外),但仍守 AA 底线。',
2265
2335
  },
2266
2336
  'zh-TW': {
2267
2337
  nav: '主題色',
@@ -2286,10 +2356,14 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2286
2356
  hc: '高對比',
2287
2357
  custom: '自訂配色',
2288
2358
  customHint: '取三個色的色相與色度;過亮/過暗的強調色會正規化以確保可讀性,明度由引擎依模式計算。',
2289
- notice: '1.9.9 依獨立稽核逐條修訂:深色文字下限另行提高(次要 8 / 三級 6.4)、文字著色三檔對預設配色真正生效、色階不再出現「編號越大反而越亮」、高對比檔的邊框與捲軸也真提高了;報表新增 APCA 欄。',
2359
+ derive: '由主色派生',
2360
+ deriveHint: '用目前強調色的色相與彩度推導底色與文字(明暗跟隨目前模式)',
2361
+ deriveNote: '「由主色派生」以強調色為唯一輸入,自動算出底色與文字 —— 會覆蓋你手動設置的底色與文字;派生之後仍可逐項手改。',
2362
+ notice: '1.10.0:高對比檔新增「跟隨系統」(系統開提高對比度就自動切高對比);自訂配色可「由主色派生」底色與文字;三級與連結的淺色下限抬高,色覺缺陷模擬下也守 AA;診斷資訊新增覆蓋報告。',
2290
2363
  dismiss: '知道了',
2291
2364
  explainClamp: '強調色已正規化:{from} → {to}(確保按鈕與勾選可讀)',
2292
2365
  explainLink: '連結已為可讀性壓暗:{ratio}:1(目標 {target}:1)',
2366
+ explainCvd: '色覺缺陷提示:{kind} 視覺下強調色與底色/文字幾乎同色(ΔE {dE})。文字仍可讀,但按鈕與色塊會分不開;換一個更亮或更暗的強調色即可。',
2293
2367
  preview: '預覽',
2294
2368
  sample: '試試示例',
2295
2369
  copyDiag: '複製診斷',
@@ -2325,7 +2399,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2325
2399
  importBad: '短碼無效,已忽略',
2326
2400
  badHex: 'HEX 需為 #RRGGBB',
2327
2401
  hexStale: '這個值剛被別處改過,已保留最新值',
2328
- hcSub: '盡量把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三級 ≥7);深色 800/850 不為正文兜底(政策外),但仍守 AA 底線。',
2402
+ hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三級 ≥7);「跟隨系統」= 系統「提高對比度」開啟時自動切高對比檔;深色 800/850 不為正文兜底(政策外),但仍守 AA 底線。',
2329
2403
  },
2330
2404
  en: {
2331
2405
  nav: 'Color theme',
@@ -2350,10 +2424,14 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2350
2424
  hc: 'High contrast',
2351
2425
  custom: 'Custom palette',
2352
2426
  customHint: 'Takes hue and chroma from three colours; extreme accents are normalised for readability; lightness comes from the engine per mode.',
2353
- notice: '1.9.9 fixes the issues from an independent audit: dark text gets its own higher floors (secondary 8 / tertiary 6.4), the text-tint tiers now really work on presets, ramps no longer go "higher step, lighter colour", and high-contrast borders and scrollbars are genuinely raised; the report gains an APCA column.',
2427
+ derive: 'Derive from accent',
2428
+ deriveHint: 'Derive background and text from the current accent hue and chroma (follows the current light/dark mode)',
2429
+ deriveNote: '"Derive from accent" takes the accent as its only input and computes background and text from it — this overwrites the background and text you set by hand; you can still edit each of them afterwards.',
2430
+ notice: '1.10.0: high contrast gains "follow the system" (switches automatically when the OS increases contrast); custom palettes can "derive from accent" to fill in background and text; lighter-tier floors for tertiary text and links are raised so the AA floor also holds under colour-vision simulation; diagnostics now include a coverage report.',
2354
2431
  dismiss: 'Got it',
2355
2432
  explainClamp: 'Accent normalised: {from} → {to} (keeps buttons and checks readable)',
2356
2433
  explainLink: 'Link darkened for readability: {ratio}:1 (target {target}:1)',
2434
+ explainCvd: 'Colour-vision note: with {kind} vision the accent is nearly the same colour as the background/text (ΔE {dE}). Text stays readable, but buttons and swatches blur together — pick a lighter or darker accent.',
2357
2435
  preview: 'Preview',
2358
2436
  sample: 'Try a sample',
2359
2437
  copyDiag: 'Copy diagnostics',
@@ -2389,7 +2467,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2389
2467
  importBad: 'Invalid code, ignored',
2390
2468
  badHex: 'HEX must be #RRGGBB',
2391
2469
  hexStale: 'This value was changed elsewhere; the latest value was kept',
2392
- hcSub: 'Raises text floors toward AAA (body ≥15, secondary ≥9, tertiary ≥7); dark 800/850 no longer back the body text (out of policy) but still hold the AA floor.',
2470
+ hcSub: 'Raises text floors toward AAA (body ≥15, secondary ≥9, tertiary ≥7); "System" = automatically switches to the high-contrast tier when the OS "increase contrast" setting is on; dark 800/850 no longer back the body text (out of policy) but still hold the AA floor.',
2393
2471
  },
2394
2472
  ja: {
2395
2473
  nav: 'カラーテーマ',
@@ -2414,10 +2492,14 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2414
2492
  hc: 'ハイコントラスト',
2415
2493
  custom: 'カスタム配色',
2416
2494
  customHint: '3 色の色相と彩度を使用。極端なアクセント色は可読性のため正規化され、明度はモードごとにエンジンが決めます。',
2417
- notice: '1.9.9 は独立監査の指摘を順に修正しました:ダークの文字下限を個別に引き上げ(二次 8 / 三次 6.4)、テキストの色味 3 段階がプリセットでも実際に効くようになり、色階が「番号が大きいほど明るい」現象も解消、ハイコントラストの枠線とスクロールバーも実際に強まりました。レポートに APCA 列を追加。',
2495
+ derive: 'メイン色から生成',
2496
+ deriveHint: '現在のアクセント色の色相と彩度から背景色と文字色を生成します(明暗は現在のモードに従います)',
2497
+ deriveNote: '「メイン色から生成」はアクセント色だけを入力として背景色と文字色を自動計算します。手動で設定した背景色と文字色は上書きされます(生成後に個別に変更できます)。',
2498
+ notice: '1.10.0:ハイコントラストに「システムに従う」を追加(OS のコントラスト強化に自動追従)。カスタム配色は「メイン色から生成」で背景色と文字色を導出できます。三次テキストとリンクの明るい側の下限を引き上げ、色覚シミュレーション下でも AA を維持。診断情報にカバレッジレポートを追加。',
2418
2499
  dismiss: '了解',
2419
2500
  explainClamp: 'アクセント色を正規化しました:{from} → {to}(ボタンとチェックの可読性のため)',
2420
2501
  explainLink: 'リンクを可読性のため暗くしました:{ratio}:1(目標 {target}:1)',
2502
+ explainCvd: '色覚のヒント:{kind} ではアクセント色が背景・文字とほぼ同色になります(ΔE {dE})。文字は読めますが、ボタンと色面は見分けにくくなります。もっと明るい/暗いアクセント色に変えてください。',
2421
2503
  preview: 'プレビュー',
2422
2504
  sample: 'サンプルを試す',
2423
2505
  copyDiag: '診断をコピー',
@@ -2453,7 +2535,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2453
2535
  importBad: '無効なコードです',
2454
2536
  badHex: 'HEX は #RRGGBB 形式で入力してください',
2455
2537
  hexStale: 'この値は別の場所で変更されました。最新の値を保持しています',
2456
- hcSub: '本文の下限を AAA に近づけます(本文 ≥15、副次 ≥9、三次 ≥7)。暗い 800/850 は本文を受け皿にしません(対象外)ですが、AA 下限は守ります。',
2538
+ hcSub: '本文の下限を AAA に近づけます(本文 ≥15、副次 ≥9、三次 ≥7)。「システム」= OS の「コントラストを上げる」が有効なとき自動的にハイコントラストになります。暗い 800/850 は本文を受け皿にしません(対象外)ですが、AA 下限は守ります。',
2457
2539
  },
2458
2540
  }
2459
2541
 
@@ -2468,7 +2550,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2468
2550
  control: { accent: '主题色', mono: '黑白' },
2469
2551
  md: { on: '着色', off: '同正文' },
2470
2552
  lang: { auto: '自动' },
2471
- hc: { off: '关', on: '开' },
2553
+ hc: { auto: '跟随系统', off: '关', on: '开' },
2472
2554
  custom: { bg: '底色', text: '文字', accent: '强调' },
2473
2555
  },
2474
2556
  'zh-TW': {
@@ -2480,7 +2562,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2480
2562
  control: { accent: '主題色', mono: '黑白' },
2481
2563
  md: { on: '著色', off: '同本文' },
2482
2564
  lang: { auto: '自動' },
2483
- hc: { off: '關', on: '開' },
2565
+ hc: { auto: '跟隨系統', off: '關', on: '開' },
2484
2566
  custom: { bg: '底色', text: '文字', accent: '強調' },
2485
2567
  },
2486
2568
  en: {
@@ -2492,7 +2574,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2492
2574
  control: { accent: 'Accent', mono: 'Mono' },
2493
2575
  md: { on: 'Tinted', off: 'Body colour' },
2494
2576
  lang: { auto: 'Auto' },
2495
- hc: { off: 'Off', on: 'On' },
2577
+ hc: { auto: 'System', off: 'Off', on: 'On' },
2496
2578
  custom: { bg: 'Surface', text: 'Text', accent: 'Accent' },
2497
2579
  },
2498
2580
  ja: {
@@ -2504,7 +2586,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2504
2586
  control: { accent: 'アクセント', mono: 'モノクロ' },
2505
2587
  md: { on: '色づけ', off: '本文と同じ' },
2506
2588
  lang: { auto: '自動' },
2507
- hc: { off: 'オフ', on: 'オン' },
2589
+ hc: { auto: 'システム', off: 'オフ', on: 'オン' },
2508
2590
  custom: { bg: '背景', text: '本文', accent: 'アクセント' },
2509
2591
  },
2510
2592
  }
@@ -2641,6 +2723,12 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2641
2723
  { className: 'dsh-tp-custom' },
2642
2724
  h('div', { className: 'dsh-tp-custom-head' },
2643
2725
  h('b', null, txt.custom),
2726
+ h('button', {
2727
+ type: 'button',
2728
+ className: 'dsh-tp-mini',
2729
+ title: txt.deriveHint,
2730
+ onClick: () => props.deriveFromAccent(),
2731
+ }, txt.derive),
2644
2732
  h('button', {
2645
2733
  type: 'button',
2646
2734
  className: 'dsh-tp-mini',
@@ -2705,14 +2793,17 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2705
2793
  invalid && h('span', { id: errId, className: 'dsh-tp-hexerror', role: 'alert' }, txt.badHex),
2706
2794
  stale && h('span', { id: staleId, className: 'dsh-tp-hexnote', role: 'status' }, txt.hexStale))
2707
2795
  })),
2796
+ h('p', { className: 'dsh-tp-custom-note' }, txt.deriveNote),
2708
2797
  h('p', { className: 'dsh-tp-custom-hint' }, txt.customHint),
2709
- explain && (explain.accentClamped || explain.linkShifted) && h(
2798
+ explain && (explain.accentClamped || explain.linkShifted || explain.cvdRisk) && h(
2710
2799
  'div',
2711
2800
  { className: 'dsh-tp-explain', role: 'status' },
2712
2801
  explain.accentClamped && h('span', null,
2713
2802
  fmt(txt.explainClamp, { from: explain.accentFrom, to: explain.accentTo })),
2714
2803
  explain.linkShifted && h('span', null,
2715
2804
  fmt(txt.explainLink, { ratio: explain.linkRatio.toFixed(2), target: explain.linkTarget })),
2805
+ explain.cvdRisk && h('span', null,
2806
+ fmt(txt.explainCvd, { kind: explain.cvdKind, dE: explain.cvdDeltaE })),
2716
2807
  ),
2717
2808
  )
2718
2809
 
@@ -3031,7 +3122,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3031
3122
 
3032
3123
  // 开发期接口:palette-dev.mjs 通过它拿到同一套引擎(避免「报表好看、上线不一样」)
3033
3124
  if (DEV_OPT_IN && typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
3034
- window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, fromOklch, flatten, rampsFor, floors, encodeConfig, decodeConfig, customScheme, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, buildContrastReport, devContrastReport, CONTRAST_TARGETS, CONTRAST_EXEMPT, CONTRAST_FLOOR, CONTRAST_SOLVER_MARGIN, LAYER_MARK, settingsCss: SETTINGS_CSS, markdownCss: MARKDOWN_CSS, cardColors })
3125
+ window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, fromOklch, flatten, luminance, withAlpha, mixHex, harmonize, deltaE, simulateCvd, rampsFor, floors, encodeConfig, decodeConfig, customScheme, deriveCustomFromAccent, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, buildContrastReport, devContrastReport, CONTRAST_TARGETS, CONTRAST_EXEMPT, CONTRAST_FLOOR, CONTRAST_SOLVER_MARGIN, LAYER_MARK, settingsCss: SETTINGS_CSS, markdownCss: MARKDOWN_CSS, cardColors })
3035
3126
  }
3036
3127
 
3037
3128
  return {
@@ -3141,9 +3232,17 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3141
3232
  // 但覆盖层写失败时屏幕上仍是旧配色。分开报,别让用户以为换成功了。
3142
3233
  let applyError = null
3143
3234
 
3235
+ // 系统对比度偏好(审计 3.11):auto 档监听 prefers-contrast: more。
3236
+ // 测试沙箱里没有 matchMedia —— 判空退回「系统没要求」,auto 等于标准档。
3237
+ const contrastMq = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
3238
+ ? window.matchMedia('(prefers-contrast: more)')
3239
+ : null
3240
+ const systemWantsHc = () => !!(contrastMq && contrastMq.matches)
3241
+ const effectiveHc = () => hcMode === 'on' || (hcMode === 'auto' && systemWantsHc())
3242
+
3144
3243
  const applyAll = (reason) => {
3145
3244
  const scheme = SCHEMES.find((s) => s.id === current) || SCHEMES[0]
3146
- const tokens = buildTokens(scheme, sidebarMode, textMode, scrollMode, controlMode, mdMode, hcMode === 'on')
3245
+ const tokens = buildTokens(scheme, sidebarMode, textMode, scrollMode, controlMode, mdMode, effectiveHc())
3147
3246
  let next = () => {}
3148
3247
  if (tokens !== undefined) {
3149
3248
  try {
@@ -3347,7 +3446,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3347
3446
  const getCustomExplain = () => {
3348
3447
  const colors = readCustom()
3349
3448
  const scheme = customScheme(colors)
3350
- const hc = hcMode === 'on'
3449
+ const hc = effectiveHc()
3351
3450
  const t = buildTokens(scheme, sidebarMode, textMode, scrollMode, controlMode, mdMode, hc)
3352
3451
  const link = t['--dsw-alias-link'].light
3353
3452
  const base = t['--dsw-static-deepseek-500'].light
@@ -3355,6 +3454,11 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3355
3454
  const ratio = Math.min(...surfaces.map((bg) => contrast(link, bg)))
3356
3455
  const acL = toOklch(colors.accent)[0]
3357
3456
  const accentTo = Math.min(0.64, Math.max(0.30, acL))
3457
+ // 色觉缺陷下的可分辨度:强调色 vs 底色 / 文字,三种模拟取最差的那种。
3458
+ // 只提示不拦:用户的配色归用户,插件只负责把风险说出来。
3459
+ const cvd = ['deutan', 'protan', 'tritan']
3460
+ .map((kind) => ({ kind, dE: Math.min(deltaE(colors.accent, colors.bg, kind), deltaE(colors.accent, colors.text, kind)) }))
3461
+ .sort((a, b) => a.dE - b.dE)[0]
3358
3462
  return {
3359
3463
  accentClamped: Math.abs(acL - accentTo) > 0.005,
3360
3464
  accentFrom: Math.round(acL * 100) / 100,
@@ -3362,6 +3466,9 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3362
3466
  linkShifted: link.toLowerCase() !== base.toLowerCase(),
3363
3467
  linkRatio: Math.round(ratio * 100) / 100,
3364
3468
  linkTarget: floors(hc).link,
3469
+ cvdRisk: cvd.dE < 12,
3470
+ cvdKind: cvd.kind,
3471
+ cvdDeltaE: Math.round(cvd.dE * 10) / 10,
3365
3472
  }
3366
3473
  }
3367
3474
  /** 诊断导出:把当前配色与对比度矩阵打成可粘贴的 JSON(提 issue 用)。 */
@@ -3370,7 +3477,57 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3370
3477
  version: PLUGIN_VERSION,
3371
3478
  settings: { palette: current, sidebar: sidebarMode, text: textMode, scroll: scrollMode, control: controlMode, md: mdMode, hc: hcMode, lang: langPref },
3372
3479
  report: getContrastReport(),
3480
+ coverage: getCoverageReport(),
3373
3481
  })
3482
+ /**
3483
+ * 运行时覆盖报告(审计建议 5):枚举宿主样式表里的 --dsw-* 自定义属性,按四类分:
3484
+ * ours —— 本插件覆盖层接管(lastTokens 里);
3485
+ * derived —— 值是 var(--dsw-…) 引用(宿主别名,跟着基础 token 走,无需接管);
3486
+ * literal-chromatic —— 字面色值且带色彩(宿主语义色等,有意不染);
3487
+ * literal-gray —— 字面灰值(跟配色的候选:接管后体验更一致)。
3488
+ * 只读、尽力而为:沙箱/跨域拿不到 styleSheets 时返回 { ok:false },不影响其它诊断。
3489
+ */
3490
+ const getCoverageReport = () => {
3491
+ const found = new Map()
3492
+ try {
3493
+ const sheets = typeof document !== 'undefined' && document.styleSheets ? Array.from(document.styleSheets) : []
3494
+ const walk = (rules) => {
3495
+ for (const rule of rules) {
3496
+ if (!rule) continue
3497
+ if (rule.cssRules) { try { walk(rule.cssRules) } catch (err) { /* 跨域子表 */ } continue }
3498
+ const style = rule.style
3499
+ if (!style || typeof style.length !== 'number') continue
3500
+ for (let i = 0; i < style.length; i++) {
3501
+ const prop = style[i]
3502
+ if (typeof prop === 'string' && prop.indexOf('--dsw-') === 0) {
3503
+ found.set(prop, style.getPropertyValue(prop).trim())
3504
+ }
3505
+ }
3506
+ }
3507
+ }
3508
+ for (const sheet of sheets) { try { walk(sheet.cssRules) } catch (err) { /* 跨域表跳过 */ } }
3509
+ } catch (err) {
3510
+ return { ok: false, reason: String((err && err.message) || err) }
3511
+ }
3512
+ const ours = new Set(lastTokens ? Object.keys(lastTokens) : [])
3513
+ const HEX_LIT = /^\s*#[0-9a-f]{3,8}\s*$/i
3514
+ const out = { ok: true, total: found.size, ours: [], derived: [], literalChromatic: [], literalGray: [] }
3515
+ for (const [name, value] of found) {
3516
+ if (ours.has(name)) { out.ours.push(name); continue }
3517
+ if (value.indexOf('var(') !== -1) { out.derived.push(name); continue }
3518
+ if (HEX_LIT.test(value)) {
3519
+ const hex = value.trim().slice(1)
3520
+ const full = hex.length === 3 ? hex.split('').map((c) => c + c).join('') : hex
3521
+ const r = parseInt(full.slice(0, 2), 16), g = parseInt(full.slice(2, 4), 16), b = parseInt(full.slice(4, 6), 16)
3522
+ if (Math.abs(r - g) <= 2 && Math.abs(g - b) <= 2 && Math.abs(r - b) <= 2) out.literalGray.push(name)
3523
+ else out.literalChromatic.push(name)
3524
+ continue
3525
+ }
3526
+ out.derived.push(name) // rgb()/oklch() 等非 var 引用:归「其它声明」,暂不细分
3527
+ }
3528
+ out.counts = { total: out.total, ours: out.ours.length, derived: out.derived.length, literalChromatic: out.literalChromatic.length, literalGray: out.literalGray.length }
3529
+ return out
3530
+ }
3374
3531
  const previewConfigCode = (code) => {
3375
3532
  const cfg = decodeConfig(code)
3376
3533
  return cfg ? { ok: true, config: cfg } : { ok: false }
@@ -3383,7 +3540,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3383
3540
  */
3384
3541
  const getContrastReport = () => {
3385
3542
  const scheme = SCHEMES.find((s) => s.id === current) || SCHEMES[0]
3386
- const hc = hcMode === 'on'
3543
+ const hc = effectiveHc()
3387
3544
  const t = buildTokens(scheme, sidebarMode, textMode, scrollMode, controlMode, mdMode, hc)
3388
3545
  return buildContrastReport(t, hc, { mono: !scheme.tinted })
3389
3546
  }
@@ -3410,6 +3567,22 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3410
3567
  emitCustom()
3411
3568
  return true
3412
3569
  }
3570
+ /** 以当前主色为唯一输入,重算底色与文字色(1.10.0,审计建议 4)。 */
3571
+ const deriveFromAccent = () => {
3572
+ const pref = (() => {
3573
+ try {
3574
+ const p = theme.getTheme().preference
3575
+ if (p === 'dark' || p === 'light') return p
3576
+ } catch (err) { /* 读不到偏好就按页面实际明暗判断 */ }
3577
+ try { return document.body.hasAttribute('data-ds-dark-theme') ? 'dark' : 'light' } catch (err) { return 'light' }
3578
+ })()
3579
+ const next = deriveCustomFromAccent(readCustom().accent, pref)
3580
+ if (!next) return false
3581
+ writeCustom({ bg: next.bg, text: next.text, accent: next.accent })
3582
+ applyAll('custom:derive')
3583
+ emitCustom()
3584
+ return true
3585
+ }
3413
3586
  const setHcMode = (id) => {
3414
3587
  if (!HC_MODES.some((m) => m.id === id)) return
3415
3588
  hcMode = id
@@ -3477,6 +3650,14 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3477
3650
  window.addEventListener('pageshow', healOnShow)
3478
3651
  } catch (err) { /* 无 DOM 时跳过 */ }
3479
3652
  try { ctx.on('theme/change', healOnTheme) } catch (err) { /* 事件名变了就只靠定时器 */ }
3653
+ // auto 档:系统对比度偏好一变就重算(开 → 高对比档,关 → 回标准档)。
3654
+ // 老浏览器没有 addEventListener 就退化为「下次任意重算时跟进」。
3655
+ try {
3656
+ if (contrastMq && typeof contrastMq.addEventListener === 'function') {
3657
+ const onContrastChange = () => { applyAll('prefers-contrast'); emitHc() }
3658
+ contrastMq.addEventListener('change', onContrastChange)
3659
+ }
3660
+ } catch (err) { /* 无 matchMedia 环境跳过 */ }
3480
3661
  try {
3481
3662
  healTimer = setInterval(() => {
3482
3663
  // 页面不可见时不做样式回读:容器里长时间后台驻留时,4 秒一次 getComputedStyle 是纯浪费
@@ -3549,6 +3730,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3549
3730
  dismissNotice,
3550
3731
  subscribeCustom,
3551
3732
  setCustomColor,
3733
+ deriveFromAccent,
3552
3734
  resetCustom,
3553
3735
  getLangPref,
3554
3736
  getLocale,
package/docs/DESIGN.md CHANGED
@@ -17,7 +17,7 @@
17
17
  正文对比度:浅色承诺面(`00`/`60`)13.50–15.57(目标 12:1),深色承诺面(`950`/`875`)10.13–12.05(目标 10:1)。
18
18
  深色 `800`(模块面)与 `850`(次级深面)**不为正文兜底** —— 报表把它们按「政策外」单列(既不算达标也不算失败):
19
19
  实测 6.20–9.70,物理上限 10.22–15.45,**AA 底线与 APCA 底线仍须过**(见下节)。
20
- 三级与链接的浅色承诺目标都是 4.62(HC 7),深色分别是 **6.4**(HC 7.6)与 **6.4**(HC 7.6)。
20
+ 三级与链接的浅色承诺目标是 **4.85 / 4.95**(HC 7;4.62 的旧值在色觉缺陷模拟下会掉到 AA 以下,见「文字对比度在 CVD 下也守 AA」),深色分别是 **6.4**(HC 7.6)与 **6.4**(HC 7.6)。
21
21
  链接另加一道 APCA 门(深色 Lc ≥60 / HC ≥75)—— 它常在 `800` 模块面上出现,而 4.62 在深色 `800` 上
22
22
  只相当于 Lc≈48,见「链接」一节。
23
23
 
@@ -118,7 +118,7 @@
118
118
  | **物理上限** | 该承载面上黑或白前景能达到的对比度 | 纯计算,**不含**求解余量 |
119
119
  | **政策豁免** | 我们不承诺该面承载正文(深色 `800` / `850`) | 产品策略 |
120
120
 
121
- - 承诺目标(浅色):正文 **12:1**、次级 **6.2**、三级 **4.62**、链接 **4.62**(高对比 15 / 9 / 7 / 7)。
121
+ - 承诺目标(浅色):正文 **12:1**、次级 **6.2**、三级 **4.85**、链接 **4.95**(高对比 15 / 9 / 7 / 7;后两项比 1.9.x 各抬了一档,见 CVD 一节)。
122
122
  - 承诺目标(深色):正文 **10:1**、次级 **8.0**、三级 **6.4**、链接 **6.4**(高对比 15 / 9.6 / 7.6 / 7.6)。
123
123
  深色侧单独一套更高的下限,理由见「为什么深色要更高的比值」。
124
124
  - 承诺面:浅色 **00/60**,深色 **950/875**。
@@ -249,6 +249,63 @@ WCAG 2.x 的相对亮度比在暗底上**系统性高估**可读性。同一个
249
249
 
250
250
  三套预置在 achromat(全色盲)下的层间明度差仍保留(ΔE 3.7–5.9),层级不依赖色度。
251
251
 
252
+ ### 文字对比度在 CVD 下也守 AA(1.10.0)
253
+
254
+ 文字与承载面的对比度由**明度差**决定,配色只改色度与色相 —— 所以色觉缺陷理论上不该影响读数。
255
+ `tools/check-audit.mjs` 第 20 组把这个「理论上」变成实测:四套配色 × 明暗 × 正文/次级/三级/链接 ×
256
+ 三个承载面 × deutan / protan / tritan(Machado 2009 线性矩阵),断言最低值 ≥ 4.5。
257
+
258
+ 这条断言当初**没过**,而且原因不在 CVD 本身:三级与链接的浅色下限原本就压在 **4.62**(刚过 AA、
259
+ 一点余量不留),而模拟会把同一对前景/背景的比值上下推动 5–9% —— 链接实测跌到 **4.22–4.34**,
260
+ 比 AA 还低。修法是把浅色下限抬到三级 **4.85**、链接 **4.95**,把这段浮动留给模型误差;
261
+ 抬完三种模拟下的最低值是 **4.61**。代价只是浅色链接与三级文字比 1.9.x 略深一档。
262
+
263
+ ## 三项「少让人做决定」的机制(1.10.0)
264
+
265
+ ### 高对比:跟随系统(`auto`)
266
+
267
+ 高对比档原有「关 / 开」两态,现在加 `auto` 并设为默认:系统开了「提高对比度」
268
+ (`prefers-contrast: more`)就自动用高对比档,关掉就回标准档。`matchMedia` 的 `change` 事件一响
269
+ 就重算(`applyAll('prefers-contrast')`),不用刷新页面。
270
+ 手动选过「关 / 开」的用户读回的是自己的选择 —— 升级不会把他们的档位改掉;
271
+ 只有从没碰过这一项的用户落到 `auto`。
272
+
273
+ ### 自定义配色:由主色派生底色与文字
274
+
275
+ 用户往往只有一个「想要的品牌色」,让他自己配纸面与墨色既难也容易把对比度配坏。
276
+ 设置页的自定义面板多了一个「由主色派生」:拿当前强调色的**色相**、色度的 15%–22%(夹上限)
277
+ 推出纸面与墨色,明度按当前明暗模式取(亮 0.968 / 0.235,暗 0.165 / 0.935)——
278
+ 这几个值是引擎已验证过的区间,派生结果直接过全部下限(实测正文 14.6–15.0、次级 6.8–6.9、
279
+ 三级 5.3–5.4、链接 5.4–5.5)。主色无彩(C < 0.004)时派生中性纸面,
280
+ 避免「黑底 + 白字 + 黄强调派生出一套淡粉」那类老问题。
281
+
282
+ ### 运行时覆盖报告(`getCoverageReport`)
283
+
284
+ 诊断导出(设置页「复制诊断信息」)多一份 `coverage`:遍历宿主样式表里所有 `--dsw-*` 自定义属性,
285
+ 按四类分账 —— `ours`(本插件覆盖层接管)、`derived`(值是 `var(--dsw-…)` 引用,跟着基础 token 走)、
286
+ `literalChromatic`(字面带彩色值,宿主语义色等,有意不染)、`literalGray`(字面灰值,是「跟配色」
287
+ 的候选)。它回答的是「宿主还有多少 token 没跟上配色」,只读、尽力而为:
288
+ 拿不到 `styleSheets`(沙箱、跨域)就返回 `{ ok: false }`,不影响其它诊断。
289
+
290
+ ## 宽色域(display-P3):本期不做(1.10.0)
291
+
292
+ sRGB 之外还有 P3 的色度余量,问题是「同一个 L/h 下 P3 能多给多少色度」以及「这些色度用不用得上」。
293
+ 用 OKLCH → XYZ → sRGB / P3 的数值求逆量了各档位(脚本在审计目录 `probe-p3-headroom-v2.mjs`):
294
+
295
+ | 档位 | 现用色度 C | sRGB 上限 | P3 上限 | 可加 ΔC | ΔE |
296
+ |---|---|---|---|---|---|
297
+ | 画布 00(暖砂 / 靛蓝 / 苔绿) | 0.0045 / 0.0034 / 0.0061 | 0.0099 / 0.0080 / 0.0289 | 0.0123 / 0.0088 / 0.0317 | 0.0025 / 0.0008 / 0.0028 | 0.25 / 0.08 / 0.28 |
298
+ | 卡片 60 | 0.0107 / 0.0074 / 0.0114 | 0.0209 / 0.0132 / 0.0605 | 0.0262 / 0.0145 / 0.0658 | 0.0053 / 0.0013 / 0.0052 | 0.53 / 0.13 / 0.52 |
299
+ | 内容面 150 | 0.0236 / 0.0139 / 0.0249 | 0.0898 / 0.0385 / 0.1697 | 0.1115 / 0.0422 / 0.1880 | 0.0217 / 0.0037 / 0.0183 | 2.17 / 0.37 / 1.83 |
300
+ | 强调 500 | 0.1013 / 0.1071 / 0.0962 | 0.1369 / 0.2001 / 0.1684 | 0.1566 / 0.2530 / 0.1980 | 0.0196 / 0.0529 / 0.0296 | 1.96 / 5.29 / 2.96 |
301
+ | 正文 1000 | 0.0127 / 0.0120 / 0.0105 | 0.0532 / 0.1548 / 0.0529 | 0.0616 / 0.1558 / 0.0727 | 0.0084 / 0.0009 / 0.0198 | 0.84 / 0.09 / 1.98 |
302
+
303
+ 结论:**纸面层(00/60)的 P3 余量在 JND 以下**(ΔE 0.08–0.53),用不用看不出来;
304
+ 真正能看出差别的是强调色(indigo ΔE 5.29、moss 2.96)。但强调色同时是链接、info 填充与勾选支点的来源,
305
+ 一旦按 P3 抬色度,就得为 sRGB 回退再标定一套(色域裁切会改色相,链接的 AA 读数要重算)。
306
+ 这一版的取舍是**不做**,把余量记在这里:将来若要做,只值得给强调色加
307
+ `color(display-p3 …)` 一层并在 sRGB 里保持现值的双口径,纸面层不必动。
308
+
252
309
  ## 报表量的是屏幕上的字(1.9.8)
253
310
 
254
311
  报表要回答的是「屏幕上这条字看不看得清」,所以量的必须是**真正渲染出来的那个 token**,
package/package.json CHANGED
@@ -1,114 +1,115 @@
1
- {
2
- "name": "dsh-theme-palette",
3
- "version": "1.9.10",
4
- "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
- "type": "module",
6
- "main": "index.js",
7
- "exports": {
8
- ".": "./index.js",
9
- "./client": "./client.js",
10
- "./client.js": "./client.js",
11
- "./index.js": "./index.js",
12
- "./cordis.patch.yml": "./cordis.patch.yml",
13
- "./locale/*.json": "./locale/*.json",
14
- "./icon": "./icon.svg",
15
- "./package.json": "./package.json"
16
- },
17
- "files": [
18
- "index.js",
19
- "client.js",
20
- "cordis.patch.yml",
21
- "icon.svg",
22
- "README.md",
23
- "LICENSE",
24
- "CHANGELOG.md",
25
- "docs/DESIGN.md",
26
- "README-zh.md",
27
- "README-es.md",
28
- "README-pt.md",
29
- "README-hi.md",
30
- "locale"
31
- ],
32
- "peerDependencies": {
33
- "@deepseek-ai/cordis": "^4.0.0",
34
- "@deepseek-ai/dsh-client-ui-theme": "^0.2.1-rc.1",
35
- "@deepseek-ai/dsh-client-ui-settings": "^0.2.1-rc.1"
36
- },
37
- "engines": {
38
- "node": "^22.19.0 || >=24.0.0",
39
- "dsh": ">=0.2.1-rc.1"
40
- },
41
- "scripts": {
42
- "check": "node tools/check-audit.mjs && node tools/check-custom.mjs && node tools/check-ui.mjs && node tools/check-host-contract.mjs && node tools/check-selfheal.mjs",
43
- "report": "node palette-dev.mjs && node palette-dev.mjs --hc",
44
- "packs": "node tools/export-theme-pack.mjs"
45
- },
46
- "dsh": {
47
- "bundle": {
48
- "patch": "./cordis.patch.yml"
49
- },
50
- "client": {
51
- "platform": "web",
52
- "immediately": true,
53
- "inject": [
54
- "@deepseek-ai/dsh-client-ui-theme",
55
- "@deepseek-ai/dsh-client-ui-settings"
56
- ]
57
- },
58
- "compatibility": {
59
- "dsh": ">=0.2.1-rc.1",
60
- "dshReleases": {
61
- "0.2.1-rc.2": "compatible"
62
- },
63
- "dshOperations": {
64
- "0.2.1-rc.2": {
65
- "install": "passed",
66
- "start": "passed",
67
- "uninstall": "unknown",
68
- "rollback": "unknown"
69
- }
70
- },
71
- "profiles": [
72
- "desktop",
73
- "web"
74
- ]
75
- }
76
- },
77
- "keywords": [
78
- "dsh",
79
- "dsh-plugin",
80
- "deepseek-harness",
81
- "theme",
82
- "palette",
83
- "oklch",
84
- "dark-mode"
85
- ],
86
- "license": "MIT",
87
- "repository": {
88
- "type": "git",
89
- "url": "git+https://github.com/zuoyunlai/dsh-theme-palette.git"
90
- },
91
- "homepage": "https://github.com/zuoyunlai/dsh-theme-palette#readme",
92
- "bugs": {
93
- "url": "https://github.com/zuoyunlai/dsh-theme-palette/issues"
94
- },
95
- "publishConfig": {
96
- "access": "public",
97
- "registry": "https://registry.npmjs.org"
98
- },
99
- "packageManager": "pnpm@11.7.0",
100
- "peerDependenciesMeta": {
101
- "@deepseek-ai/cordis": {
102
- "optional": true
103
- },
104
- "@deepseek-ai/dsh-client-ui-theme": {
105
- "optional": true
106
- },
107
- "@deepseek-ai/dsh-client-ui-settings": {
108
- "optional": true
109
- }
110
- },
111
- "devDependencies": {
112
- "semver": "^7.7.3"
113
- }
114
- }
1
+ {
2
+ "name": "dsh-theme-palette",
3
+ "version": "1.10.0",
4
+ "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./client": "./client.js",
10
+ "./client.js": "./client.js",
11
+ "./index.js": "./index.js",
12
+ "./cordis.patch.yml": "./cordis.patch.yml",
13
+ "./locale/*.json": "./locale/*.json",
14
+ "./icon": "./icon.svg",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "index.js",
19
+ "client.js",
20
+ "cordis.patch.yml",
21
+ "icon.svg",
22
+ "README.md",
23
+ "LICENSE",
24
+ "CHANGELOG.md",
25
+ "docs/DESIGN.md",
26
+ "README-zh.md",
27
+ "README-es.md",
28
+ "README-pt.md",
29
+ "README-hi.md",
30
+ "locale"
31
+ ],
32
+ "peerDependencies": {
33
+ "@deepseek-ai/cordis": "^4.0.0",
34
+ "@deepseek-ai/dsh-client-ui-theme": "^0.2.1-rc.1",
35
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.1-rc.1"
36
+ },
37
+ "engines": {
38
+ "node": "^22.19.0 || >=24.0.0",
39
+ "dsh": ">=0.2.1-rc.1"
40
+ },
41
+ "scripts": {
42
+ "check": "node tools/check-audit.mjs && node tools/check-custom.mjs && node tools/check-ui.mjs && node tools/check-host-contract.mjs && node tools/check-selfheal.mjs && node tools/check-colormath.mjs",
43
+ "report": "node palette-dev.mjs && node palette-dev.mjs --hc",
44
+ "packs": "node tools/export-theme-pack.mjs",
45
+ "shots": "node tools/shoot-settings.mjs"
46
+ },
47
+ "dsh": {
48
+ "bundle": {
49
+ "patch": "./cordis.patch.yml"
50
+ },
51
+ "client": {
52
+ "platform": "web",
53
+ "immediately": true,
54
+ "inject": [
55
+ "@deepseek-ai/dsh-client-ui-theme",
56
+ "@deepseek-ai/dsh-client-ui-settings"
57
+ ]
58
+ },
59
+ "compatibility": {
60
+ "dsh": ">=0.2.1-rc.1",
61
+ "dshReleases": {
62
+ "0.2.1-rc.2": "compatible"
63
+ },
64
+ "dshOperations": {
65
+ "0.2.1-rc.2": {
66
+ "install": "passed",
67
+ "start": "passed",
68
+ "uninstall": "unknown",
69
+ "rollback": "unknown"
70
+ }
71
+ },
72
+ "profiles": [
73
+ "desktop",
74
+ "web"
75
+ ]
76
+ }
77
+ },
78
+ "keywords": [
79
+ "dsh",
80
+ "dsh-plugin",
81
+ "deepseek-harness",
82
+ "theme",
83
+ "palette",
84
+ "oklch",
85
+ "dark-mode"
86
+ ],
87
+ "license": "MIT",
88
+ "repository": {
89
+ "type": "git",
90
+ "url": "git+https://github.com/zuoyunlai/dsh-theme-palette.git"
91
+ },
92
+ "homepage": "https://github.com/zuoyunlai/dsh-theme-palette#readme",
93
+ "bugs": {
94
+ "url": "https://github.com/zuoyunlai/dsh-theme-palette/issues"
95
+ },
96
+ "publishConfig": {
97
+ "access": "public",
98
+ "registry": "https://registry.npmjs.org"
99
+ },
100
+ "packageManager": "pnpm@11.7.0",
101
+ "peerDependenciesMeta": {
102
+ "@deepseek-ai/cordis": {
103
+ "optional": true
104
+ },
105
+ "@deepseek-ai/dsh-client-ui-theme": {
106
+ "optional": true
107
+ },
108
+ "@deepseek-ai/dsh-client-ui-settings": {
109
+ "optional": true
110
+ }
111
+ },
112
+ "devDependencies": {
113
+ "semver": "^7.7.3"
114
+ }
115
+ }