dsh-theme-palette 1.13.3 → 1.13.5

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
@@ -8,6 +8,83 @@
8
8
  > 1.13.2 起逐节标注 —— 此前 27 个版本节看起来都能装,用户按 CHANGELOG 钉版本会撞 404
9
9
  > (2026-10-29 审计 P2-9)。
10
10
 
11
+ ### 1.13.5
12
+ 用户回执:「自定义色派生好像有问题?并未覆盖所有啊,好多颜色仍然是默认色」。
13
+ 这次不是猜:把宿主 `app.asar` 里的 token 表(`ui-theme` 的 `design-platform.css` + 应用 CSS)整份解出来,
14
+ 和插件实际声明的 token 逐条对表,得到确切口径 ——
15
+
16
+ - 宿主定义 **412** 个 token 名,插件声明 151 个颜色 token(无一拼写失效、无一被宿主丢弃);
17
+ - 应用 CSS 真正 `var()` 消费的只有 **66** 个颜色 token;
18
+ - 差集里 **58** 个颜色 token 插件从未声明:其中 19 个靠 `var()` 链落到已重建的色阶上(**会跟随**,
19
+ 例如 `state-business-primary` → deepseek-500、`menu-icon` → bluish-800),
20
+ **39 个落在宿主字面色值上 —— 那是真正的"还是默认色"**。
21
+
22
+ 根因:插件只重建四族色阶(bluish / neutral / deepseek / blue),
23
+ 而宿主 `design-platform.css` 里还并列着**三族语义色阶 red / green / amber**,从来没有被碰过。
24
+ 于是「步骤点(done/error/warning)、`data-tone` 标签、终端错误行、无效输入边框、
25
+ 危险菜单项悬停、**文件 diff 的 +/− 行**」在自定义配色下永远是宿主的纯红/纯绿/纯琥珀。
26
+
27
+ - **修法一(色阶)**:`SEMANTIC_BASE` 补 red / green / amber 三族(含宿主给的两个带 alpha 的 diff 浅底),
28
+ `buildRamps` 用新增的 `harmonizeSemantic` 做**温度调和**:色相往配色色相拉、**旋转封顶 8°**、
29
+ 色度压到 88–92%,**明度一律沿用宿主**。封顶是关键 —— 苔绿配色(hue 162)的"短路径"
30
+ 本来会把红(OKLCH h≈28°)往橙推 16°,报错就和警告撞色;封到 8° 后只是"同一件语义色换冷暖"。
31
+ 无彩(纯黑/纯白/正灰)的自定义配色**不做调和**,语义色一个像素都不动。
32
+ - **修法二(别名)**:`aliasOverrides` 显式落地 `state-{error,success,warn}-{primary,secondary,tertiary,label}`、
33
+ `code-diff-added/deleted`、`interactive-bg-hover-danger`(用语义红重合成宿主那两个 alpha 字面量)。
34
+ 顺带补上宿主**没定义、但构建 CSS 在用**的两个洞:`--dsw-alias-label-error`(设置页"保存失败"那行,
35
+ 缺失时 `color` 整条失效、文字回落继承色)与 `--dsw-alias-bg-layer-4`(帮助按钮 hover 底)。
36
+ - **修法三(契约守卫,防复发)**:`tools/check-host-contract.mjs` 新增颜色覆盖守卫 ——
37
+ 从宿主构建 CSS 里解出所有被 `var()` 消费的颜色 token,逐条要求"被插件声明"或"`var()` 链
38
+ 落到插件声明的 token 上"。拿 1.13.4 的 `client.js` 跑同一条断言会挂 **30 处**(这就是这次漏检的形态),
39
+ 1.13.5 为 0。黑白档的 19 个 token、遮罩/透明分隔线/首次引导页等字面色值走显式白名单。
40
+ - **断言**:`check-audit` 第 29 组 —— 三族真的产出 / 色相偏移 ≤ 8° / 红绿琥珀两两可辨(≥ 25° 与 ≥ 40°)/
41
+ 别名映射与宿主明暗档位一致 / 补回的两个缺口 / diff 行对比度与宿主同量级 /
42
+ 黑白档不重建 / 无彩自定义不调和。
43
+ - **已知边界(不修)**:语义色沿用宿主明度,因此宿主本身的两个问题照旧 ——
44
+ 浅色下成功绿当文字只有 ~2.1:1(diff `+` 行、`data-tone=success` 标签),以及宿主为这一族
45
+ 设计了**明暗同值**的单一档位,任何"靠调明度补对比度"的做法都会在另一侧反噬。
46
+ 插件不替宿主改语义色的明暗节奏;这一条写进 README 的覆盖口径里。
47
+ - 复核:`check-audit` 29 组全过;`check-host-contract` 全过(含新增守卫);两档报表 exit 0。
48
+ - **截图未重出**:设置页不渲染语义色,`screenshots.meta.json` 只同步了版本号。
49
+
50
+ #### 1.13.5 补丁 · 自定义配色的「底色」与「线条色」(同日第二次回执)
51
+
52
+ 用户回执 #2:截图里 `v1.13.5` 已生效,但「很多地方底色、线条色都是按默认的」。核查确认两条:
53
+
54
+ - **底色只被当色相来源**。`customScheme` 只取色相与色度,深色画布固定 L≈0.226;
55
+ 用户把「底色」设成 `#070b11`(L≈0.148)时,屏幕上是 `#191c21` 一档的蓝灰 —— 他读成"底色没变",
56
+ 而且这个读法是对的。
57
+ **修法**:底色偏暗(L ≤ 0.35)→ 它就是**深色模式的画布明度**(夹到 0.14–0.30);
58
+ 偏亮(L ≥ 0.75)→ **浅色模式的画布明度**(夹到 0.90–0.985);中间灰(0.35–0.75)不表达明暗意图,
59
+ 两侧都沿用引擎值。只锚画布这一档,其余三层面按同一 delta 平移(层次间距不变),
60
+ 文字对比度由求解器在新承载面上重算 —— 该配色实测正文对画布 **11.33:1**、对 875 面 **10.16:1**。
61
+ 下限 0.14 是层次余量:再往下左栏「更深」档会掉进近纯黑。
62
+ - **线条色取的是文字色 alpha**。文字一旦近中性(`#e1eced`,C≈0.012),发丝线就是一片中性灰。
63
+ **修法**:自定义配色下线条改用**配色色相 + 色度下限 0.02**(明度与 alpha 不变,对比关系不变);
64
+ 预置仍是墨/纸原色,不受影响。
65
+ - UI 提示(四语)同步改成"底色偏暗/偏亮会当作对应模式的画布"。
66
+ - **断言**:`check-audit` 第 30 组(13 条)—— 深/浅/中间灰三类输入、四层面阶梯、左栏与画布 ΔE、
67
+ 正文对比度、线条色相与最小色度、预置不受影响、纯黑底色的层次余量。
68
+ - 复核:工具链回归 8/8、两档报表 exit 0;设置页截图按新文案重拍。
69
+
70
+ ### 1.13.4
71
+ 用户第二次回执:同一张悬停卡「还是深灰底色」。这次**明暗两套截图对比**把 1.13.3 的方向证伪了 ——
72
+ 浅色模式下(画布 `#f8fcf6`)卡片仍是 `#2c2c2e` + 白字,颜色一个像素都没变:
73
+ 它根本不读调色板,所以改多少条 token 都碰不到。
74
+
75
+ 逐文件扫宿主 asar 后定位到根因:**`dsh-web-frontend` 把悬停卡底色写死在卡片元素上** ——
76
+ `._card_38jqx_9{--dsw-hovercard-bg:#2C2C2E;background:var(--dsw-hovercard-bg)}`。
77
+ 这是全站唯一的大写手写 hex:既不读色阶、也不读别名,与主题完全无关(连浅色模式也是深灰)。
78
+
79
+ - **修法**:`buildTokens` 新增专用 token `--dsh-tp-hovercard-bg`(明暗两档都取色阶 **850 的暗面**),
80
+ 并注入第三条样式 `dsh-theme-palette/host-patch.css`:
81
+ `*{--dsw-hovercard-bg:var(--dsh-tp-hovercard-bg,#2c2c2e)!important}`。
82
+ 宿主的声明在卡片元素**自身**,普通声明压不过它,必须 `!important`;只改这一个自定义属性,
83
+ 其它元素即使命中也没有副作用。卡片保持**暗底白字**(对比度不变),但从此带上配色色相。
84
+ 黑白档没有色阶可接,回落到宿主原色(行为不变)。
85
+ - **断言**:`check-audit` 第 28 组 —— 明暗同值 / = 850 暗面 / 补丁样式的形态 / 黑白档不产出。
86
+ - 复核:`npm run check` 8/8;两档报表 exit 0。
87
+
11
88
  ### 1.13.3
12
89
  用户实测反馈:**左侧会话行的悬停卡(会话标题 / 时间 / 状态)底色不跟随主题色** —— 卡片背景停在
13
90
  `#2c2c2e`、标题近白,而旁边的侧栏与画布都是染过色的。
package/README-es.md CHANGED
@@ -28,9 +28,9 @@ El plugin solo apila tokens de color mediante `ctx.theme.overrideTokens`; no reg
28
28
 
29
29
  - **4 preajustadas + personalizada** — Mono, Arena cálida, Índigo, Musgo (cada una adaptada de forma independiente a claro y oscuro), más una paleta personalizada derivada de tres colores;
30
30
  - la escala se **recalcula** completa: reconstrucción OKLCH → croma por rol → **ajuste al gamut sRGB** → contraste resuelto sobre superficies reales;
31
- - se cubren todos los tokens de color del host (158 tokens en paletas con tinte, 19 en mono; 1080 combinaciones de ajuste escaneadas): 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;
31
+ - tokens de color del host: 179 en las paletas con tinte (+13 propios del plugin), 19 en mono (1080 combinaciones de ajuste escaneadas); **los 66 tokens de color que el CSS compilado consume de verdad resuelven todos al plugin**: 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 y ahora **los estados de error/éxito/aviso y las líneas +/− del diff** (las escalas roja/verde/ámbar entran por primera vez en la reconstrucción: solo temperatura, giro de tono limitado a 8°);
32
32
  - ajustable: intensidad del texto (3), profundidad de la barra lateral (3), énfasis de Markdown, barras de desplazamiento, color de interruptores;
33
- - **paleta personalizada** (elige superficie / texto / acento — el motor mantiene sus garantías de contraste), **exportación/importación de configuración** con un código `dshs1.`, **modo de alto contraste** y **4 idiomas de interfaz**;
33
+ - **paleta personalizada** (elige superficie / texto / acento: el matiz del texto entra en la derivación y una superficie claramente oscura o clara pasa a ser la luminosidad del lienzo de ese modo — el motor mantiene sus garantías de contraste), **exportación/importación de configuración** con un código `dshs1.`, **modo de alto contraste** y **4 idiomas de interfaz**;
34
34
  - **autorreparable**: si el host descarta la capa de override, la paleta se vuelve a aplicar; tras fallos repetidos la autorreparación se pausa y la página de ajustes ofrece un botón para reactivarla;
35
35
  - **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 (HC 9 / 9.6), el terciario ≥ 4.85 en claro y 6.4 en oscuro (HC 7 / 7.6), los enlaces ≥ 4.95 en claro y 4.62 en oscuro (HC 7; los mínimos claros llevan un margen para déficits de visión cromática; los enlaces oscuros además pasan una puerta APCA Lc ≥ 60, 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);
36
36
  - 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);
package/README-hi.md CHANGED
@@ -28,9 +28,9 @@ DeepSeek Harness (वेब और डेस्कटॉप) के लिए
28
28
 
29
29
  - **4 प्रीसेट + कस्टम** — मोनो, वार्म सैंड, इंडिगो, मॉस (हर एक लाइट और डार्क दोनों के लिए अलग से अनुकूलित), साथ ही तीन रंगों से निकली कस्टम पैलेट;
30
30
  - पूरी रैंप **दोबारा गणना** होती है, बदली नहीं जाती: OKLCH पुनर्निर्माण → भूमिका-आधारित क्रोमा → **sRGB गैमट क्लैंप** → वास्तविक सतहों पर कंट्रास्ट हल;
31
- - होस्ट के सभी रंग टोकन शामिल (रंगीन पैलेट में 158 टोकन, मोनो में 19; 1080 सेटिंग संयोजन पूरी तरह स्कैन किए गए): मुख्य टेक्स्ट, द्वितीयक, तृतीयक, कैप्शन, लिंक, साइडबार, कार्ड, उपयोगकर्ता बबल, प्राइमरी बटन, स्क्रॉलबार, स्विच, Markdown शीर्षक/बोल्ड, कोड ब्लॉक;
31
+ - होस्ट के रंग टोकन: रंगीन पैलेट में 179 (+ प्लगइन के अपने 13), मोनो में 19 (1080 सेटिंग संयोजन पूरी तरह स्कैन किए गए); **बिल्ट CSS जो 66 रंग टोकन वास्तव में उपभोग करता है, वे सभी प्लगइन पर ही हल होते हैं**: मुख्य टेक्स्ट, द्वितीयक, तृतीयक, कैप्शन, लिंक, साइडबार, कार्ड, उपयोगकर्ता बबल, प्राइमरी बटन, स्क्रॉलबार, स्विच, Markdown शीर्षक/बोल्ड, कोड ब्लॉक, और अब **त्रुटि/सफलता/चेतावनी की स्थितियाँ तथा फ़ाइल diff की +/− पंक्तियाँ** (लाल/हरा/एम्बर तीनों स्केल पहली बार पुनर्निर्माण में शामिल; केवल तापमान, रंग-कोण घुमाव अधिकतम 8°);
32
32
  - समायोज्य: टेक्स्ट की तीव्रता (3), साइडबार की गहराई (3), Markdown ज़ोर, स्क्रॉलबार, स्विच का रंग;
33
- - **कस्टम पैलेट** (सतह / टेक्स्ट / एक्सेंट चुनें — इंजन अपने कंट्रास्ट वादे बनाए रखता है), `dshs1.` कोड से **कॉन्फ़िगरेशन निर्यात/आयात**, **हाई-कॉन्ट्रास्ट** मोड, और **4 UI भाषाएँ**;
33
+ - **कस्टम पैलेट** (सतह / टेक्स्ट / एक्सेंट चुनें: टेक्स्ट का रंग-कोण भी व्युत्पन्न में शामिल होता है, और स्पष्ट रूप से गहरी/हल्की सतह उस मोड के कैनवास की चमक बन जाती है — इंजन अपने कंट्रास्ट वादे बनाए रखता है), `dshs1.` कोड से **कॉन्फ़िगरेशन निर्यात/आयात**, **हाई-कॉन्ट्रास्ट** मोड, और **4 UI भाषाएँ**;
34
34
  - **स्व-उपचार**: यदि होस्ट ओवरराइड परत हटा दे, पैलेट फिर से लगा दी जाती है; बार-बार विफलता पर स्व-उपचार रुक जाता है और सेटिंग्स पेज पर इसे दोबारा चालू करने का बटन मिलता है;
35
35
  - **कंट्रास्ट के वादे**: मुख्य टेक्स्ट के लिए लाइट में 12:1 और डार्क में 10:1 (उच्च-कंट्रास्ट में 15:1), द्वितीयक ≥ 6.2 (लाइट) / 8.0 (डार्क) (HC 9 / 9.6), तृतीयक ≥ 4.85 (लाइट) / 6.4 (डार्क) (HC 7 / 7.6), लिंक ≥ 4.95 (लाइट) / 4.62 (डार्क) (HC 7) — लाइट की सीमाओं में रंग-दृष्टि दोष के लिए गुंजाइश रखी गई है; डार्क लिंक अतिरिक्त APCA Lc ≥ 60 (HC 75) — उन्हीं सतहों पर मापे गए जहाँ टेक्स्ट वास्तव में बैठता है (डार्क में ज़्यादा चाहिए: गहरे बैकग्राउंड पर WCAG अनुपात पढ़ने की सुविधा को बढ़ा-चढ़ा कर दिखाता है);
36
36
  - वादे की गई सतहें लाइट में 00/60 और डार्क में 950/875 हैं; डार्क 800 (मॉड्यूल सतह) और 850 (उप-सतह) मुख्य टेक्स्ट को नहीं सहनाते — रिपोर्ट उन्हें अलग «नीति से बाहर» के रूप में गिनती है (न तो पास, न ही फेल);
package/README-ja.md CHANGED
@@ -28,9 +28,9 @@ package.json の `engines` は `^22.19.0 || >=24.0.0` —— 上の表と同じ
28
28
 
29
29
  - **4 プリセット + カスタム** —— モノクロ / ウォームサンド / インディゴ / モスグリーンがライトとダークをそれぞれ独立に適応し、さらに 3 色から派生するカスタム配色;
30
30
  - 色階は置き換えではなく**全体を再計算**:OKLCH 再構築 → 役割ごとの彩度 → **sRGB ガマット・クランプ** → 実際の面でコントラストを解く;
31
- - ホストの全色トークンを網羅(着色パレットで 158、モノクロで 19。1080 通りの設定を全走査):本文、二次、三次、注記、リンク、サイドバー、カード、ユーザー吹き出し、プライマリボタン、スクロールバー、スイッチ、Markdown 見出し/太字、コードブロック;
31
+ - ホストの色トークン:着色パレットで 179(+ プラグイン自前 13)、モノクロで 19(1080 通りの設定を全走査)。**ビルド済み CSS が実際に消費する色トークン 66 個はすべてプラグインに解決されます**:本文、二次、三次、注記、リンク、サイドバー、カード、ユーザー吹き出し、プライマリボタン、スクロールバー、スイッチ、Markdown 見出し/太字、コードブロック、そして**エラー/成功/警告の状態色とファイル diff の +/− 行**(赤/緑/アンバーの三色階が初めて再構築に参加。色相の回転は 8° まで、温度だけを合わせるので意味は変わりません);
32
32
  - 調整可:テキストの着色 3 段、サイドバー 3 段、Markdown 強調、スクロールバー、スイッチの配色;
33
- - **カスタム配色**(背景 / テキスト / アクセントの 3 色。極端に明るい・暗いアクセントは正規化されます)、**設定のエクスポート/インポート**(`dshs1.` ショートコードに 3 色を含む)、**ハイコントラスト**、UI 4 言語;
33
+ - **カスタム配色**(背景 / テキスト / アクセントの 3 色。テキストの色相も派生に参加し、背景が明確に暗い/明るい場合はそのモードのキャンバス明度になります。極端なアクセントは正規化されます)、**設定のエクスポート/インポート**(`dshs1.` ショートコードに 3 色を含む)、**ハイコントラスト**、UI 4 言語;
34
34
  - **自己修復**:ホストがオーバーライド層を破棄すると自動で再適用。連続失敗すると自動再適用を停止し、設定ページに「再有効化」の入口を出します;
35
35
  - **コントラストの約束**:本文はライト 12:1 / ダーク 10:1(ハイコントラスト 15:1)、二次 ≥ 6.2(ライト)/ 8.0(ダーク)、三次 ≥ 4.85(ライト)/ 6.4(ダーク)(HC 7 / 7.6)、リンク ≥ 4.95(ライト)/ 4.62(ダーク)(HC 7。ライト側は色覚特性の余裕を含む)。ダークのリンクはさらに APCA Lc ≥ 60(HC 75)—— いずれもテキストが実際に載る面で実測(ダーク側の要求が厳しいのは、暗い背景では WCAG の比が読みやすさを過大評価するため);
36
36
  - 約束する面はライト 00/60 とダーク 950/875。ダークの 800(モジュール面)と 850(サブ面)は**本文を保証しません** —— レポートでは「ポリシー外」として別枠(達成にも未達にも数えない);
package/README-pt.md CHANGED
@@ -28,9 +28,9 @@ O plugin apenas empilha tokens de cor via `ctx.theme.overrideTokens`; não regis
28
28
 
29
29
  - **4 predefinidas + personalizada** — Mono, Areia quente, Índigo, Musgo (cada uma adaptada de forma independente ao claro e ao escuro), mais uma paleta personalizada derivada de três cores;
30
30
  - a escala é **recalculada** por completo: reconstrução OKLCH → croma por papel → **limite de gamut sRGB** → contraste resolvido sobre superfícies reais;
31
- - todos os tokens de cor do host são cobertos (158 tokens nas paletas com tinta, 19 na mono; 1080 combinações de ajuste escaneadas): 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;
31
+ - tokens de cor do host: 179 nas paletas com tinta (+13 do próprio plugin), 19 na mono (1080 combinações de ajuste escaneadas); **os 66 tokens de cor que o CSS compilado realmente consome resolvem todos no plugin**: 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 e agora **os estados de erro/sucesso/aviso e as linhas +/− do diff** (as escalas vermelho/verde/âmbar entram pela primeira vez na reconstrução: só temperatura, rotação de matiz limitada a 8°);
32
32
  - ajustável: intensidade do texto (3), profundidade da barra lateral (3), ênfase de Markdown, barras de rolagem, cor dos interruptores;
33
- - **paleta personalizada** (escolha superfície / texto / acento — o motor mantém as garantias de contraste), **exportação/importação de configuração** por um código `dshs1.`, **modo de alto contraste** e **4 idiomas de interface**;
33
+ - **paleta personalizada** (escolha superfície / texto / acento: a matiz do texto entra na derivação e uma superfície claramente escura ou clara vira a luminosidade do canvas daquele modo — o motor mantém as garantias de contraste), **exportação/importação de configuração** por um código `dshs1.`, **modo de alto contraste** e **4 idiomas de interface**;
34
34
  - **autorrecuperação**: se o host descartar a camada de override, a paleta é reaplicada; após falhas repetidas a autorrecuperação pausa e a página de ajustes oferece um botão para reativá-la;
35
35
  - **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 (HC 9 / 9.6), o terciário ≥ 4.85 no claro e 6.4 no escuro (HC 7 / 7.6), os links ≥ 4.95 no claro e 4.62 no escuro (HC 7; os pisos do lado claro têm margem para deficiências de visão cromática; os links escuros ainda passam por um portão APCA Lc ≥ 60, 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);
36
36
  - 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);
package/README-zh.md CHANGED
@@ -28,9 +28,9 @@ package.json 的 `engines` 字段写的是 `^22.19.0 || >=24.0.0`——与上表
28
28
 
29
29
  - **4 套预设 + 自定义** —— 黑白 / 暖砂 / 靛蓝 / 苔绿各自独立适配明暗两侧,另加由三色派生的自定义配色;
30
30
  - 色阶**整体重算**而不是替换:OKLCH 重建 → 角色化色度 → **sRGB 色域钳位** → 在真实承载面上求解对比度;
31
- - 覆盖宿主全部颜色 token(自定义/染色档 158 个、黑白档 19 个;1080 组设置全扫描):正文、次级、三级、注释、链接、侧栏、卡片、主人气泡、主按钮、滚动条、开关、Markdown 标题/加粗、代码块;
31
+ - 覆盖宿主颜色 token:染色档 179 个宿主 token + 13 个插件自有 token,黑白档 19 个(1080 组设置全扫描);**构建 CSS 实际消费的 66 个颜色 token 全部覆盖或跟随**—— 正文、次级、三级、注释、链接、侧栏、卡片、主人气泡、主按钮、滚动条、开关、Markdown 标题/加粗、代码块,以及**错误/成功/警告状态与文件 diff 的 +/− 行**(红/绿/琥珀三族第一次参与重建:只调温度、色相旋转封顶 8°,语义不漂);
32
32
  - 可调:文字着色三档、侧栏三档、Markdown 强调、滚动条、开关配色;
33
- - **自定义配色**(给 底色/文字/强调 三色,文字色相也参与派生;过亮/过暗的强调色会归一化)、**配置导入导出**(`dshs1.` 短码含三色)、**高对比**档、4 种界面语言;
33
+ - **自定义配色**(给 底色/文字/强调 三色:文字的色相参与派生,**底色偏暗/偏亮会成为对应模式的画布明度**;过亮/过暗的强调色会归一化)、**配置导入导出**(`dshs1.` 短码含三色)、**高对比**档、4 种界面语言;
34
34
  - **自愈**:宿主丢弃 override 层时自动重放;自动重放连续失败后会停用,并在设置页给出「重新启用」入口;
35
35
  - **对比度承诺**:正文浅色 12:1、深色 10:1(高对比 15:1),次级 ≥ 6.2 浅 / 8.0 深,三级 ≥ 4.85 浅 / 6.4 深(高对比 7 / 7.6),链接 ≥ 4.95 浅 / 4.62 深(高对比 7;浅色留了色觉缺陷余量),链接深色另过 APCA Lc ≥ 60(高对比 75)—— 都在文字真实落到的承载面上实测(深色要高一些:WCAG 比值在暗底上会高估可读性);
36
36
  - 承诺面是浅色 00/60 与深色 950/875;深色 800(模块面)与 850(次级深面)**不为正文兜底**,报表单列为「政策外」(既不算达标也不算失败);
@@ -70,6 +70,8 @@ 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
+ - **语义色(1.13.5)**:宿主自带的红/绿/琥珀三族不重排明度,只按配色色相做**温度调和**(`harmonizeSemantic`:色相旋转封顶 8°、色度压到 88–92%),所以「错误仍是红、成功仍是绿、警告仍是琥珀」,只是换了一套冷暖;无彩(纯黑/纯白/正灰)的自定义配色不做调和,一个像素都不动;
74
+ - **覆盖口径可验证**:`tools/check-host-contract.mjs` 从宿主构建里解出「应用 CSS 真正 `var()` 消费的颜色 token」,逐条要求它要么被插件声明、要么 `var()` 链落到插件声明的 token 上 —— 1.13.4 及以前这条会挂 30 处(红/绿/琥珀三族 + 危险悬停 + diff 行),1.13.5 起为 0;
73
75
  - 文字下限在构建期强制:正文浅色 ≥ 12:1、深色 ≥ 10:1(高对比 15:1),次级 ≥ 6.2 浅 / 8.0 深(HC 9 / 9.6),三级 ≥ 4.85 浅 / 6.4 深(HC 7 / 7.6),链接 ≥ 4.95 浅 / 4.62 深(HC 7),链接另过 APCA Lc ≥ 60 深 / 75 HC;侧栏与气泡文字随色阶派生,实测均 ≥ 7(不设独立对比度门);
74
76
  - 承诺面是浅色 00/60 与深色 950/875;深色 800(模块面)与 850(次级深面)**不为正文兜底**,报表单列为「政策外」(既不算达标也不算失败),且每一档承载面仍须过 AA 底线 4.5;
75
77
  - 报表把三件事分开:声明目标、物理上限(该承载面上黑或白前景能达到的对比度)、政策豁免;0.93 只是求解余量,仅用于判断目标是否高于上限;
package/README.md CHANGED
@@ -28,9 +28,9 @@ The plugin only stacks colour tokens through `ctx.theme.overrideTokens`; it does
28
28
 
29
29
  - **4 presets + custom** — Mono, Warm sand, Indigo, Moss (each adapting to light *and* dark independently), plus a custom palette derived from three colours;
30
30
  - the whole ramp is **recomputed**, not swapped: OKLCH rebuild → role-based chroma → **sRGB gamut clamp** → contrast solved on real surfaces;
31
- - every host colour token is covered (158 tokens for tinted palettes, 19 for mono; 1080 setting combinations fully scanned): body, secondary, tertiary, captions, links, sidebars, cards, user bubble, primary button, scrollbars, switches, Markdown headings/bold, code blocks;
31
+ - host colour tokens: 179 for tinted palettes plus 13 plugin-owned tokens, 19 for mono (1080 setting combinations fully scanned); **all 66 colour tokens the built CSS actually consumes resolve to the plugin** — body, secondary, tertiary, captions, links, sidebars, cards, user bubble, primary button, scrollbars, switches, Markdown headings/bold, code blocks, and now **error/success/warning states and the +/− rows of the file diff** (the red/green/amber scales join the rebuild for the first time: temperature only, hue rotation capped at 8°, so the semantics never drift);
32
32
  - adjustable: text tint (3), sidebar depth (3), Markdown emphasis, scrollbars, switch colour;
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;
33
+ - **custom palette** (pick surface / text / accent — the text hue joins the derivation, and a clearly dark or light surface becomes that mode's canvas lightness; 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; after repeated failures auto-restore pauses and the settings page offers a re-enable button;
35
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 (HC 9 / 9.6), tertiary ≥ 4.85 in light / 6.4 in dark (HC 7 / 7.6), links ≥ 4.95 in light / 4.62 in dark (HC 7; the light floors carry a colour-vision headroom; dark links also clear an APCA Lc ≥ 60 gate, 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);
@@ -70,6 +70,8 @@ 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
+ - **semantic colours (1.13.5)**: the host's red/green/amber scales keep their host lightness and only get a **temperature harmonisation** toward the palette hue (`harmonizeSemantic`: hue rotation capped at 8°, chroma scaled to 88–92%) — error stays red, success stays green, warning stays amber, just in the palette's warm/cool key; an achromatic custom palette (pure black/white/grey) is left untouched;
74
+ - **the coverage claim is testable**: `tools/check-host-contract.mjs` extracts every colour token the built CSS actually consumes and requires each one to be either declared by the plugin or to end its `var()` chain in a declared token — 1.13.4 and earlier fail this with 30 leaks (red/green/amber, danger hover, diff rows), 1.13.5 reports zero;
73
75
  - 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 ≥ 4.85 in light / 6.4 in dark (HC 7 / 7.6), link ≥ 4.95 in light / 4.62 in dark (HC 7), link APCA Lc ≥ 60 in dark (HC 75); sidebar and bubble text follow the ramp and measure ≥ 7 (no separate contrast gate);
74
76
  - 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
77
  - 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;
Binary file
Binary file
@@ -1,7 +1,7 @@
1
1
  {
2
- "generatedAt": "2026-10-29T11:29:25.163Z",
3
- "pluginVersion": "1.13.3",
4
- "chrome": "Chrome/156.0.8078.49",
2
+ "generatedAt": "2026-10-30T15:33:51.493Z",
3
+ "pluginVersion": "1.13.5",
4
+ "chrome": "Chrome/155.0.8059.85",
5
5
  "width": 620,
6
6
  "scale": 1,
7
7
  "images": [
@@ -14,13 +14,13 @@
14
14
  "--adv",
15
15
  "0"
16
16
  ],
17
- "htmlSha256": "8952278cd4d7eb94840604c8ffddba4c51670dd565bf80ead6f8820a84eff661",
17
+ "htmlSha256": "72e549c29634c57766d6c158c44abac17540a69846b40959e8703e367cb70342",
18
18
  "cssWidth": 620,
19
19
  "cssHeight": 888,
20
20
  "width": 620,
21
21
  "height": 888,
22
- "bytes": 47480,
23
- "sha256": "2504df2500e798c458dcb3014882b880c96ca09a057702a2845fd0252a2b736b"
22
+ "bytes": 47280,
23
+ "sha256": "9cfc50ae62a47151c4f91227aac492d0f75e71f49ead95bf7eb94cfb36582cb1"
24
24
  },
25
25
  {
26
26
  "file": "assets/screenshot-settings-dark.png",
@@ -31,13 +31,13 @@
31
31
  "--adv",
32
32
  "0"
33
33
  ],
34
- "htmlSha256": "c8c71e00b0e2518beea8f4b58e64c57b70e7903f69829a9652fb27f11e3ffd6d",
34
+ "htmlSha256": "fba6044a9742347944f900f75961d81f99df53ea44b9e40924b4286e0f46abb3",
35
35
  "cssWidth": 620,
36
36
  "cssHeight": 888,
37
37
  "width": 620,
38
38
  "height": 888,
39
- "bytes": 47789,
40
- "sha256": "4133eaf99848b36dd4df90b5604155878df9f5b47d09e6cdccdce1e8b9fb3ca5"
39
+ "bytes": 47814,
40
+ "sha256": "4aa499cf86eae625e7648aafc692263dfde7f6eddf2d36bf3bf3e5005a1345dc"
41
41
  },
42
42
  {
43
43
  "file": "assets/screenshot-settings-custom.png",
@@ -50,13 +50,13 @@
50
50
  "--out",
51
51
  "customadv"
52
52
  ],
53
- "htmlSha256": "199f061201c88c70c0228dc2d9066f83d333f7b7f3c20627a3fe947b81e36084",
53
+ "htmlSha256": "147f41c51dd2373d76eb16d07fd175630f0d8e75f7ca93f3209b66ec7d15d018",
54
54
  "cssWidth": 620,
55
- "cssHeight": 1753,
55
+ "cssHeight": 1771,
56
56
  "width": 620,
57
- "height": 1753,
58
- "bytes": 116545,
59
- "sha256": "c2b6d3d6e0a11b2da64c16b9729b0aa12b8cd9ea47b9627cd2d95881008306ab"
57
+ "height": 1771,
58
+ "bytes": 118730,
59
+ "sha256": "123909ebbbf6827a7d2f1b8d06976bffc6e64883cb1a8f624c398410d8af2805"
60
60
  },
61
61
  {
62
62
  "file": "assets/screenshot-settings-hc.png",
@@ -69,13 +69,13 @@
69
69
  "--out",
70
70
  "hcadv"
71
71
  ],
72
- "htmlSha256": "56ea7c65091e98ff5a25149cd39933b14035da47ebc986ec4afb423ef0f475f6",
72
+ "htmlSha256": "62c56df0d28276506415c4d53ec22509c14ad4cff1c2ad8be2aca1242dd63f81",
73
73
  "cssWidth": 620,
74
74
  "cssHeight": 1463,
75
75
  "width": 620,
76
76
  "height": 1463,
77
- "bytes": 94765,
78
- "sha256": "db1ff1416e5d6b0a0202f5a435a64137f4bf13a35fce6d43ad53497358b279cb"
77
+ "bytes": 94312,
78
+ "sha256": "37e5795563143fff26b4c72af62a9db82f113db7dac609eb7feab539a6d9cee2"
79
79
  }
80
80
  ]
81
81
  }
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.13.3'
30
+ const PLUGIN_VERSION = '1.13.5'
31
31
  const STORE_KEY = 'dsh-theme-palette.v1'
32
32
  const OVERRIDE_SOURCE = 'dsh-theme-palette'
33
33
 
@@ -58,6 +58,48 @@ window.__ModuleLoader__.load({
58
58
  },
59
59
  }
60
60
 
61
+ /* ================= 语义色阶(宿主原生红 / 绿 / 琥珀)=================
62
+ * 2026-10-30:宿主 `design-platform.css` 里除了四个中性/品牌色阶(bluish / neutral /
63
+ * deepseek / blue),还并列着三族**语义色阶**:red / green / amber。插件此前只重建前四族,
64
+ * 于是自定义配色下「步骤点、`data-tone` 标签、文件 diff 的 +/− 行、危险菜单项悬停、
65
+ * 无效输入边框」永远停在宿主的纯红 / 纯绿 / 纯琥珀(逐条核过:58 个颜色 token 里
66
+ * 39 个最终落在字面色值上,红绿琥珀占了绝大多数)。
67
+ *
68
+ * 重建方式与 file-diff 同一手法 —— **只换温度,不动语义**:
69
+ * 色相往配色色相拉一小段(封顶 ±8°,见 harmonizeSemantic)、色度压到 0.88–0.92,
70
+ * 明度一律沿用宿主。明度不动是关键:宿主的语义色明暗节奏(浅色 600/500 当字、100 当底;
71
+ * 深色反过来用 400/900)是它自己的对比度策略,改明度会把宿主的层次算坏。
72
+ *
73
+ * 黑白档(mono)不重建:它按设计只动 19 个 token,语义色保持宿主原样。
74
+ */
75
+ const SEMANTIC_BASE = {
76
+ red: { '50': '#fef2f2', '100': '#fee2e2', '400': '#f25a5a', '500': '#ef4444', '600': '#ec1313', '900': '#570c0c' },
77
+ green: { '100': '#e6faed', '400': '#4ed17e', '500': '#22c55e', '900': '#233c2c' },
78
+ amber: { '100': '#fef5e7', '400': '#f7ad31', '500': '#f59e0b', '600': '#dd8629', '900': '#27241f' },
79
+ }
80
+ /* 宿主在同一条色阶上另给了四个**带 alpha 的底色档**(diff 行底),单列在这里:
81
+ * 它们不是独立色相,而是 base + alpha,调和时只能先调 base 再补 alpha,
82
+ * 否则 8 位 hex 进 harmonize 会被当非法输入。 */
83
+ const SEMANTIC_ALPHA = {
84
+ green: { '500-a08': ['500', 0.078], '500-a12': ['500', 0.121] },
85
+ red: { '600-a08': ['600', 0.078], '400-a12': ['400', 0.121] },
86
+ }
87
+ /* 语义色色相旋转上限(度)与拉近系数。一个都不能多:苔绿配色(hue 162)的
88
+ * "短路径"会把红(OKLCH h≈28°)往黄绿推,推多了红就滑成琥珀、和警告撞色;
89
+ * 封到 8° 后观感只是"同一件语义色换了冷暖"(暖砂→朱红、靛蓝→绯红),语义不丢。 */
90
+ const SEMANTIC_TURN = 8
91
+ const SEMANTIC_PULL = 0.12
92
+
93
+ /**
94
+ * 语义色温度调和:与 harmonize 的差别只有一条 —— **色相旋转按 SEMANTIC_TURN 封顶**。
95
+ * 明度与色相的"主"部分保持宿主原样,所以红仍是红、绿仍是绿、琥珀仍是琥珀。
96
+ */
97
+ function harmonizeSemantic(hex, targetHue, chromaScale) {
98
+ const [L, C, h] = toOklch(hex)
99
+ const dh = ((targetHue - h + 540) % 360) - 180
100
+ return fromOklch(L, C * chromaScale, h + clamp(dh * SEMANTIC_PULL, -SEMANTIC_TURN, SEMANTIC_TURN))
101
+ }
102
+
61
103
  /**
62
104
  * 每一步在 UI 里的角色 → 色度权重来源。
63
105
  * light = 浅色模式的底/卡片/悬浮层;mid = 中间灰(边框、次级 UI);
@@ -238,6 +280,29 @@ window.__ModuleLoader__.load({
238
280
  }
239
281
  const lightC = achromatic ? 0 : clampN(bg[1] * 0.9, 0.008, 0.045)
240
282
  const darkC = achromatic ? 0 : clampN(bg[1] * 0.75, 0.006, 0.035)
283
+ /* ---------- 底色明度(2026-10-30 用户回执 #2:选 #070b11 当底色,界面还是"默认深灰")----------
284
+ * 1.13.5 以前只取底色的**色相与色度**,明度完全由引擎按模式给:
285
+ * 深色画布固定 L≈0.235(#191c21 一档的蓝灰)。用户把「底色」设成 #070b11(L≈0.15)时,
286
+ * 屏幕上看不到自己选的那个底色 —— 读起来就是"很多地方底色还是默认的",而且说得对。
287
+ *
288
+ * 现在把「底色」当真的底色用:
289
+ * 底色偏暗(L ≤ 0.45)→ 它就是**深色模式**的画布明度(钳到 0.10–0.24,别把卡层挤没);
290
+ * 底色偏亮(L ≥ 0.55)→ 它就是**浅色模式**的画布明度(钳到 0.90–0.985)。
291
+ * 中间灰(0.45–0.55)视为"没有偏好",两侧都沿用引擎值 —— 那种输入表达不出明暗意图。
292
+ * 只锚**画布这一档**:其余层按原间距整体平移(见 buildRamps),文字对比度照旧由求解器
293
+ * 在新承载面上重算,承诺下限不变。 */
294
+ /* 下限 0.14 是**层次余量**,不是审美:深色侧还有三个更亮的面(875/850/800,各 +0.03 L)
295
+ * 和左栏「更深」一档(再 -0.042)——画布压到 0.10 时左栏会掉到 #020407(近纯黑,
296
+ * 和画布只差 ΔE 2 但整体塌成一块)。0.14 让整条阶梯仍有 ~0.09 L 的可用空间,
297
+ * 用户选 #070b11 这种近黑底色时观感仍是"近黑",但层次还在。
298
+ *
299
+ * 阈值 0.35 / 0.75:只有"明显偏暗"或"明显偏亮"才算表达了明暗意图;
300
+ * 中间灰(L 0.35–0.75)两侧都不锚 —— 那既不表达深色也不表达浅色,硬锚会把浅色画布
301
+ * 压成 0.90 的中灰,等于凭空换了套配色。 */
302
+ const CANVAS_L_DARK = [0.14, 0.30]
303
+ const CANVAS_L_LIGHT = [0.90, 0.985]
304
+ const canvasDarkL = bg[0] <= 0.35 ? clampN(bg[0], CANVAS_L_DARK[0], CANVAS_L_DARK[1]) : undefined
305
+ const canvasLightL = bg[0] >= 0.75 ? clampN(bg[0], CANVAS_L_LIGHT[0], CANVAS_L_LIGHT[1]) : undefined
241
306
  return {
242
307
  id: 'custom', label: '自定义', desc: '三色派生', tinted: true, custom: true,
243
308
  hue, darkHue: hue, achromatic,
@@ -247,6 +312,7 @@ window.__ModuleLoader__.load({
247
312
  textC: achromatic ? 0 : clampN(tx[1], 0.002, 0.030), textHue: achromatic ? hue : tx[2],
248
313
  darkC, darkLift: 0.030,
249
314
  paperC: achromatic ? 0 : clampN(tx[1], 0.004, 0.030), paperHue: achromatic ? hue : tx[2],
315
+ canvasDarkL, canvasLightL,
250
316
  // accentL 必须钳位:500 档同时是 --dsw-alias-link、button-info-fill 与勾选支点的来源。
251
317
  // 纯白/荧光黄会让填充与白字同色;只在极端输入时归一化,预设色不受影响。
252
318
  accentHue: ac[2], accentC: clampN(ac[1], 0.04, 0.18), accentL: clampN(ac[0], 0.30, 0.64), accentScale: 1.0,
@@ -944,6 +1010,21 @@ window.__ModuleLoader__.load({
944
1010
  const hue = L <= 0.45 && scheme.darkHue !== undefined ? scheme.darkHue : scheme.hue
945
1011
  neutral[step] = fromOklch(compressL(L), c, hue)
946
1012
  }
1013
+ /* 语义色阶:只换温度(见 harmonizeSemantic),明度与宿主一致。
1014
+ * 面(50/100/900)压到 0.88 的色度、字(400–600)留 0.92 —— 大面积色块要读成
1015
+ * 「同一张纸」,而标签/步骤点的字色一旦发灰,「操作成功/失败」就不再一眼可辨。 */
1016
+ const semantic = {}
1017
+ for (const [family, table] of Object.entries(SEMANTIC_BASE)) {
1018
+ const out = {}
1019
+ for (const [step, hex] of Object.entries(table)) {
1020
+ const surface = step === '50' || step === '100' || step === '900'
1021
+ out[step] = scheme.achromatic === true ? hex : harmonizeSemantic(hex, scheme.hue, surface ? 0.88 : 0.92)
1022
+ }
1023
+ for (const [suffix, [base, alpha]] of Object.entries(SEMANTIC_ALPHA[family] || {})) {
1024
+ out[suffix] = withAlpha(out[base], alpha)
1025
+ }
1026
+ semantic[family] = out
1027
+ }
947
1028
  const accent = (ramp) => {
948
1029
  const out = {}
949
1030
  for (const [step, hex] of Object.entries(ramp)) {
@@ -1003,7 +1084,24 @@ window.__ModuleLoader__.load({
1003
1084
  bluish['100'] = lerp(0.55)
1004
1085
  bluish['200'] = lerp(1.25)
1005
1086
  }
1006
- return { bluish, neutral, deepseek: accent(BASE.deepseek), blue: accent(BASE.blue) }
1087
+ /* 自定义配色:把画布锚到用户「底色」的明度(见 customScheme 的 canvasDarkL / canvasLightL)。
1088
+ * 在整条色阶**建好之后**按同一 delta 平移那一组面,层与层的间距不变;
1089
+ * 只动面档(深色 800–950 / 浅色 00–200),文字档留给 tuneRamps 在新面上重解。
1090
+ * 预置不参与:它们的明度是设计稿钉死的(SURFACE_TARGETS)。 */
1091
+ if (scheme.custom === true) {
1092
+ const shiftSurfaces = (steps, target, canvasStep) => {
1093
+ if (target === undefined) return
1094
+ const delta = target - toOklch(bluish[canvasStep])[0]
1095
+ if (Math.abs(delta) < 0.002) return
1096
+ for (const step of steps) {
1097
+ const [L, C, h] = toOklch(bluish[step])
1098
+ bluish[step] = fromOklch(clamp(L + delta, 0.02, 0.995), C, h)
1099
+ }
1100
+ }
1101
+ shiftSurfaces(['800', '850', '875', '900', '950'], scheme.canvasDarkL, '950')
1102
+ shiftSurfaces(['00', '60', '75', '100', '150', '200'], scheme.canvasLightL, '00')
1103
+ }
1104
+ return { bluish, neutral, deepseek: accent(BASE.deepseek), blue: accent(BASE.blue), ...semantic }
1007
1105
  }
1008
1106
 
1009
1107
  /**
@@ -1219,7 +1317,7 @@ window.__ModuleLoader__.load({
1219
1317
  * 不跟着色阶走,必须单独覆盖,否则暖底上会出现冷灰发丝线。
1220
1318
  * 值按「浅色 / 深色」给一对(overrideTokens 的硬性要求)。
1221
1319
  */
1222
- function aliasOverrides(r, hc) {
1320
+ function aliasOverrides(r, hc, params) {
1223
1321
  const ink = r.bluish['1000'] // 浅色模式的墨色
1224
1322
  const paper = r.bluish['50'] // 深色模式的纸白
1225
1323
  const grey = r.bluish['700'] // 悬浮在媒体上的工具条用中性灰
@@ -1228,9 +1326,19 @@ window.__ModuleLoader__.load({
1228
1326
  * (参考实现也是这个量级,界面不会变成表格)。但高对比模式继续用 alpha 就说不过去了:
1229
1327
  * 1.9.8 的 HC 只改了文字,边框 alpha 没动,实测数值完全一样,HC 名不副实。
1230
1328
  * 现在 HC 下按 SC 1.4.11 对承载面反解到 3:1 / 3.4:1 / 4:1,三档保留层级。
1231
- */
1329
+ *
1330
+ * 2026-10-30(用户回执 #2「线条色还是默认的」):发丝线一直取**文字色**的 alpha。
1331
+ * 文字色一旦近中性(例如自定义的 #e1eced,C≈0.012),线条就等于中性白 α —— 屏幕上
1332
+ * 确实没有任何主题色。自定义配色下改成:明度与 alpha 仍取墨/纸(对比关系不变),
1333
+ * 但**色相取配色色相、色度给下限 0.02**,让线条带上主题色而不是一块灰。 */
1334
+ const borderInk = (mode) => {
1335
+ const base = mode === 'light' ? ink : paper
1336
+ if (!params || params.custom !== true) return base
1337
+ const [L, C, h] = toOklch(base)
1338
+ return fromOklch(L, Math.max(C, 0.02), params.hue)
1339
+ }
1232
1340
  const inkOf = (mode) => (mode === 'light' ? ink : paper)
1233
- const alphaBorder = (mode, alpha) => withAlpha(inkOf(mode), alpha)
1341
+ const alphaBorder = (mode, alpha) => withAlpha(borderInk(mode), alpha)
1234
1342
  /**
1235
1343
  * HC 的边框要的是**定量**:3.0 / 3.4 / 4.0。之前用 solveForeground 求解,
1236
1344
  * 从墨色起步时它一步都不用挪就「已达标」→ 直接返回墨色,实测 16.9:1 ——
@@ -1246,7 +1354,7 @@ window.__ModuleLoader__.load({
1246
1354
  */
1247
1355
  const solidBorder = (mode, target) => mixToContrast(
1248
1356
  mode === 'light' ? [r.bluish['00'], r.bluish['60']] : [r.bluish['875'], r.bluish['850']],
1249
- inkOf(mode),
1357
+ borderInk(mode),
1250
1358
  target,
1251
1359
  )
1252
1360
  const border = (mode, alpha, target) => (hc ? solidBorder(mode, target) : alphaBorder(mode, alpha))
@@ -1339,6 +1447,39 @@ window.__ModuleLoader__.load({
1339
1447
  light: mixHex(r.deepseek['500'], r.bluish['00'], 0.11),
1340
1448
  dark: mixHex(r.deepseek['400'], r.bluish['950'], 0.13),
1341
1449
  },
1450
+
1451
+ /* ---- 语义色(2026-10-30)----
1452
+ * 宿主把这一族写成 `var(--dsw-static-red/green/amber-NN)`,而这三族此前根本没被重建,
1453
+ * 于是一整套「错误 / 成功 / 警告 / diff / 危险悬停」在自定义配色下保持宿主原色。
1454
+ * 现在色阶跟着配色调温度(SEMANTIC_BASE),别名这里**显式落地**对应档位 ——
1455
+ * 明暗两侧档位不同(错误浅色 600 / 深色 400,成功与警告两侧同档),
1456
+ * 与宿主 design-platform.css 逐条对齐,`tools/check-host-contract.mjs` 会核这张表。
1457
+ */
1458
+ '--dsw-alias-state-error-primary': { light: r.red['600'], dark: r.red['400'] },
1459
+ '--dsw-alias-state-error-secondary': { light: r.red['400'], dark: r.red['400'] },
1460
+ '--dsw-alias-state-success-primary': { light: r.green['500'], dark: r.green['500'] },
1461
+ '--dsw-alias-state-success-secondary': { light: r.green['400'], dark: r.green['400'] },
1462
+ // 成功/警告的「浅底」在深色模式换成深底(宿主用的是 green-900 / amber-900)
1463
+ '--dsw-alias-state-success-tertiary': { light: r.green['100'], dark: r.green['900'] },
1464
+ '--dsw-alias-state-warn-primary': { light: r.amber['500'], dark: r.amber['500'] },
1465
+ '--dsw-alias-state-warn-secondary': { light: r.amber['400'], dark: r.amber['400'] },
1466
+ '--dsw-alias-state-warn-tertiary': { light: r.amber['100'], dark: r.amber['900'] },
1467
+ '--dsw-alias-state-warn-label': { light: r.amber['600'], dark: r.amber['600'] },
1468
+ // 宿主**没有定义**、但构建 CSS 在用的错误文字色(设置页「保存失败」那一行)。
1469
+ // 缺失时那条 color 声明整条失效 → 文字回落到继承色,看着像没报错。
1470
+ '--dsw-alias-label-error': { light: r.red['600'], dark: r.red['400'] },
1471
+ // 文件 diff 的 +/− 行底:宿主现在用的是 code-diff-*(不是 file-diff-*,后者仍保留给旧构建)
1472
+ '--dsw-alias-code-diff-added': { light: r.green['500-a08'], dark: r.green['500-a12'] },
1473
+ '--dsw-alias-code-diff-deleted': { light: r.red['600-a08'], dark: r.red['400-a12'] },
1474
+ // 危险菜单项 hover/focus 底:宿主给的是 `#ec13130d`(浅,5%)/`#f25a5a26`(深,15%)两个字面量,
1475
+ // 这里用同一档密度(13/255 与 38/255)从语义红重新合成,色相才跟得上配色
1476
+ '--dsw-alias-interactive-bg-hover-danger': {
1477
+ light: withAlpha(r.red['600'], 13 / 255),
1478
+ dark: withAlpha(r.red['400'], 38 / 255),
1479
+ },
1480
+ // 宿主同样没有定义、但构建 CSS 在用的第四层面(帮助按钮 hover):给一档与
1481
+ // --dsw-alias-button-floating-hover 同档的面,正好是「比 layer-3 再抬一层」的语义
1482
+ '--dsw-alias-bg-layer-4': { light: r.bluish['75'], dark: r.bluish['800'] },
1342
1483
  }
1343
1484
  }
1344
1485
 
@@ -1652,7 +1793,7 @@ window.__ModuleLoader__.load({
1652
1793
  }
1653
1794
  }
1654
1795
  }
1655
- const aliases = aliasOverrides(ramps, hc)
1796
+ const aliases = aliasOverrides(ramps, hc, params)
1656
1797
  for (const [name, pair] of Object.entries(aliases)) pairs[name] = pair
1657
1798
  }
1658
1799
  const sidebar = sidebarTokens(ramps, sidebarMode, params.tinted, params)
@@ -1936,6 +2077,12 @@ window.__ModuleLoader__.load({
1936
2077
  const dark = pairs['--dsw-static-neutral-bluish-' + steps.dark]
1937
2078
  if (light && dark) pairs[name] = { light: light.light, dark: dark.dark }
1938
2079
  }
2080
+ // 宿主悬停卡的专用底色(配合 HOST_PATCH_CSS):宿主把卡片底色写死成 #2C2C2E,
2081
+ // 这里给一个**两档都取暗面**的 token —— 卡片始终是暗底白字(对比度不变),但带上配色色相。
2082
+ if (pairs['--dsw-static-neutral-bluish-850']) {
2083
+ const hoverBg = pairs['--dsw-static-neutral-bluish-850'].dark
2084
+ pairs['--dsh-tp-hovercard-bg'] = { light: hoverBg, dark: hoverBg }
2085
+ }
1939
2086
  // 覆盖层标记(自愈判据用):宿主撤销这一层时它会一起消失。
1940
2087
  pairs[LAYER_MARK] = { light: '1', dark: '1' }
1941
2088
  return pairs
@@ -1996,7 +2143,25 @@ return pairs
1996
2143
  '[class*="_markdown_"] :not(pre) > code{background:var(--dshtp-code-inline,inherit)}',
1997
2144
  ].join('')
1998
2145
 
1999
- /* ================= 设置页样式(head 里第二条 CSS) ================= */
2146
+ /* ================= 宿主补丁样式(head 里第三条 CSS) =================
2147
+ *
2148
+ * 为什么需要它(2026-10-29 用户实测,第二轮回执):
2149
+ * 宿主的「悬停卡」(hover card,会话行悬停时弹出的小卡)把底色**写死在卡片元素上**:
2150
+ * dsh-web-frontend 的 `._card_38jqx_9{--dsw-hovercard-bg:#2C2C2E;background:var(--dsw-hovercard-bg)}`。
2151
+ * 这是一个**手写字面量**(全站唯一的大写 hex),既不读色阶也不读别名 ——
2152
+ * 所以浅色模式下它也是深灰、明暗两套主题下颜色一模一样。改多少条 token 都碰不到它。
2153
+ *
2154
+ * 修法:给一个**专用 token**(`--dsh-tp-hovercard-bg`,见 buildTokens:两档都取色阶 850 的暗色值),
2155
+ * 再用 `*` + `!important` 把它接到宿主的 `--dsw-hovercard-bg` 上 —— 宿主的声明在卡片元素自身,
2156
+ * 普通声明压不过它,必须 important。白字保持不变(暗底白字,对比度不受影响),
2157
+ * 卡片从此带上配色的色相,而不是一块中性的深灰。
2158
+ */
2159
+ const HOST_PATCH_STYLE_ID = 'dsh-theme-palette/host-patch.css'
2160
+ const HOST_PATCH_CSS = `
2161
+ /* 宿主悬停卡:--dsw-hovercard-bg 是宿主写死的 #2C2C2E;这里接回配色的暗面(见 buildTokens 的 --dsh-tp-hovercard-bg) */
2162
+ *{--dsw-hovercard-bg:var(--dsh-tp-hovercard-bg,#2c2c2e)!important}
2163
+ `
2164
+ /* ================= 设置页样式(head 里第四条 CSS) ================= */
2000
2165
  const SETTINGS_STYLE_ID = 'dsh-theme-palette/settings.css'
2001
2166
  const SETTINGS_CSS = `
2002
2167
  .dsh-tp{display:flex;flex-direction:column;gap:14px;width:100%;color:var(--dsw-alias-label-primary);box-sizing:border-box}
@@ -2127,8 +2292,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2127
2292
  }
2128
2293
  }
2129
2294
 
2130
- function installSettingsStyle() {
2131
- try {
2295
+ function installSettingsStyle() { try {
2132
2296
  if (typeof document === 'undefined' || !document.head) return () => {}
2133
2297
  let tag = document.querySelector('style[data-plugin-css="' + SETTINGS_STYLE_ID + '"]')
2134
2298
  if (!tag) {
@@ -2146,6 +2310,25 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2146
2310
  }
2147
2311
  }
2148
2312
 
2313
+ /** 宿主补丁(悬停卡写死的底色)——与另外两条样式同构:覆盖写 + 卸载移除。 */
2314
+ function installHostPatchStyle() {
2315
+ try {
2316
+ if (typeof document === 'undefined' || !document.head) return () => {}
2317
+ let tag = document.querySelector('style[data-plugin-css="' + HOST_PATCH_STYLE_ID + '"]')
2318
+ if (!tag) {
2319
+ tag = document.createElement('style')
2320
+ tag.dataset.plugin = 'dsh-theme-palette'
2321
+ tag.dataset.pluginCss = HOST_PATCH_STYLE_ID
2322
+ document.head.appendChild(tag)
2323
+ }
2324
+ tag.textContent = HOST_PATCH_CSS
2325
+ return () => { if (tag.parentNode) tag.remove() }
2326
+ } catch (err) {
2327
+ console.warn('[dsh-theme-palette] 宿主补丁样式注入失败:', err)
2328
+ return () => {}
2329
+ }
2330
+ }
2331
+
2149
2332
  /* ================= 运行时自检 ================= */
2150
2333
  /**
2151
2334
  * 宿主若改了 token 名,插件会**静默失效**(overrideTokens 照收,样式不生效)。
@@ -2451,11 +2634,11 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2451
2634
  lang: '语言',
2452
2635
  hc: '高对比',
2453
2636
  custom: '自定义配色',
2454
- customHint: '取三个色的色相与色度;过亮/过暗的强调色会归一化以保证可读,明度由引擎按模式计算。',
2637
+ customHint: '取三个色的色相与色度;底色偏暗会当作深色模式的画布、偏亮会当作浅色模式的画布(过暗/过亮的会被夹回可用区间);过亮/过暗的强调色会归一化以保证可读。',
2455
2638
  derive: '由主色派生',
2456
2639
  deriveHint: '用当前强调色的色相与色度推导底色与文字(明暗跟随当前模式)',
2457
2640
  deriveNote: '「由主色派生」以强调色为唯一输入,自动算出底色与文字 —— 会覆盖你手动设置的底色与文字;派生之后仍可逐项手改。',
2458
- notice: '1.13.3:修复左侧会话行悬停卡的底色不跟随主题色——宿主把表面别名写成色阶引用,浮层里这条链不生效;现在 47 个表面别名直接写入覆盖层。',
2641
+ notice: '1.13.5:错误/成功/警告与文件 diff 的 +/− 行也跟着配色了 —— 红/绿/琥珀三族第一次参与重建,只调温度、色相旋转封顶 8°。',
2459
2642
  dismiss: '知道了',
2460
2643
  explainClamp: '强调色已归一化:{from} → {to}(保证按钮与勾选可读)',
2461
2644
  explainLink: '链接已为可读性压暗:{ratio}:1(目标 {target}:1)',
@@ -2524,11 +2707,11 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2524
2707
  lang: '語言',
2525
2708
  hc: '高對比',
2526
2709
  custom: '自訂配色',
2527
- customHint: '取三個色的色相與色度;過亮/過暗的強調色會正規化以確保可讀性,明度由引擎依模式計算。',
2710
+ customHint: '取三個色的色相與色度;底色偏暗會當作深色模式的畫布、偏亮會當作淺色模式的畫布(過暗/過亮的會被夾回可用區間);過亮/過暗的強調色會正規化以確保可讀性。',
2528
2711
  derive: '由主色派生',
2529
2712
  deriveHint: '用目前強調色的色相與彩度推導底色與文字(明暗跟隨目前模式)',
2530
2713
  deriveNote: '「由主色派生」以強調色為唯一輸入,自動算出底色與文字 —— 會覆蓋你手動設置的底色與文字;派生之後仍可逐項手改。',
2531
- notice: '1.13.3:修正左側會話列懸停卡的底色不跟隨主題色——宿主把表面別名寫成色階引用,浮層裡這條鏈不生效;現在 47 個表面別名直接寫入覆蓋層。',
2714
+ notice: '1.13.5:錯誤/成功/警告與檔案 diff 的 +/− 行也跟著配色了 —— 紅/綠/琥珀三族第一次參與重建,只調溫度、色相旋轉上限 8°。',
2532
2715
  dismiss: '知道了',
2533
2716
  explainClamp: '強調色已正規化:{from} → {to}(確保按鈕與勾選可讀)',
2534
2717
  explainLink: '連結已為可讀性壓暗:{ratio}:1(目標 {target}:1)',
@@ -2597,11 +2780,11 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2597
2780
  lang: 'Language',
2598
2781
  hc: 'High contrast',
2599
2782
  custom: 'Custom palette',
2600
- customHint: 'Takes hue and chroma from three colours; extreme accents are normalised for readability; lightness comes from the engine per mode.',
2783
+ customHint: 'Takes hue and chroma from three colours plus the surface lightness: a dark surface becomes the canvas in dark mode, a light one in light mode (out-of-range values are clamped); extreme accents are normalised for readability.',
2601
2784
  derive: 'Derive from accent',
2602
2785
  deriveHint: 'Derive background and text from the current accent hue and chroma (follows the current light/dark mode)',
2603
2786
  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.',
2604
- notice: '1.13.3: fixes the session-row hover card not following the palette — the host declares surface aliases as ramp references and that chain does not resolve inside popovers; all 47 surface aliases now go straight into the override layer.',
2787
+ notice: '1.13.5: error/success/warning states and the +/− diff rows now follow the palette too — the semantic red/green/amber scales join the rebuild, temperature only, hue rotation capped at 8°.',
2605
2788
  dismiss: 'Got it',
2606
2789
  explainClamp: 'Accent normalised: {from} → {to} (keeps buttons and checks readable)',
2607
2790
  explainLink: 'Link darkened for readability: {ratio}:1 (target {target}:1)',
@@ -2670,11 +2853,11 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
2670
2853
  lang: '言語',
2671
2854
  hc: 'ハイコントラスト',
2672
2855
  custom: 'カスタム配色',
2673
- customHint: '3 色の色相と彩度を使用。極端なアクセント色は可読性のため正規化され、明度はモードごとにエンジンが決めます。',
2856
+ customHint: '3 色の色相と彩度に加えて、背景色の明度も使います(暗い背景ならダークモードのキャンバス、明るい背景ならライトモードのキャンバス。範囲外はクランプ)。極端なアクセント色は可読性のため正規化されます。',
2674
2857
  derive: 'メイン色から生成',
2675
2858
  deriveHint: '現在のアクセント色の色相と彩度から背景色と文字色を生成します(明暗は現在のモードに従います)',
2676
2859
  deriveNote: '「メイン色から生成」はアクセント色だけを入力として背景色と文字色を自動計算します。手動で設定した背景色と文字色は上書きされます(生成後に個別に変更できます)。',
2677
- notice: '1.13.3:セッション行のホバーカードが配色に追随しない問題を修正——ホストは表面別名を色階の参照で宣言しており、ポップオーバー内ではその連鎖が効きません。47 個の表面別名をオーバーライド層へ直接書き込むようにしました。',
2860
+ notice: '1.13.5:エラー/成功/警告とファイル diff の +/− 行も配色に追従するようになりました(赤/緑/アンバーの三色階が再構築に参加、色相の回転は 8° まで)。',
2678
2861
  dismiss: '了解',
2679
2862
  explainClamp: 'アクセント色を正規化しました:{from} → {to}(ボタンとチェックの可読性のため)',
2680
2863
  explainLink: 'リンクを可読性のため暗くしました:{ratio}:1(目標 {target}:1)',
@@ -3325,7 +3508,7 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3325
3508
  // 必须能在测试里读到"实现里的那个数"。此前测试拿 floors() 的返回值当期望值 ——
3326
3509
  // 常量一改、期望值跟着改,那道门就是空转的(变异实测:CONTRAST_FLOOR 4.5→1.0 全绿)。
3327
3510
  if (DEV_OPT_IN && typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
3328
- 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, floorsDark, apcaContrast, raiseToLc, solveForeground, pickLocale, hostLocale, encodeConfig, decodeConfig, customScheme, deriveCustomFromAccent, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, buildContrastReport, devContrastReport, CONTRAST_TARGETS, CONTRAST_EXEMPT, CONTRAST_FLOOR, CONTRAST_SOLVER_MARGIN, MIN_MAIN, MIN_SECONDARY, MIN_TERTIARY, MIN_LINK, MIN_LINK_DARK, MIN_SECONDARY_DARK, MIN_TERTIARY_DARK, FLOORS_HC, FLOORS_HC_DARK, RAMP_MIN_GAP, RAMP_ORDER, SURFACE_ALIAS_STEPS, hexToRgb, LAYER_MARK, settingsCss: SETTINGS_CSS, markdownCss: MARKDOWN_CSS, cardColors })
3511
+ 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, floorsDark, apcaContrast, raiseToLc, solveForeground, pickLocale, hostLocale, encodeConfig, decodeConfig, customScheme, deriveCustomFromAccent, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, buildContrastReport, devContrastReport, CONTRAST_TARGETS, CONTRAST_EXEMPT, CONTRAST_FLOOR, CONTRAST_SOLVER_MARGIN, MIN_MAIN, MIN_SECONDARY, MIN_TERTIARY, MIN_LINK, MIN_LINK_DARK, MIN_SECONDARY_DARK, MIN_TERTIARY_DARK, FLOORS_HC, FLOORS_HC_DARK, RAMP_MIN_GAP, RAMP_ORDER, SURFACE_ALIAS_STEPS, hexToRgb, LAYER_MARK, hostPatchCss: HOST_PATCH_CSS, settingsCss: SETTINGS_CSS, markdownCss: MARKDOWN_CSS, cardColors })
3329
3512
  }
3330
3513
 
3331
3514
  return {
@@ -3941,10 +4124,12 @@ body[data-ds-dark-theme] .dsh-tp{color-scheme:dark}
3941
4124
  applyAll('boot')
3942
4125
  const removeStyle = installMarkdownStyle()
3943
4126
  const removeSettings = installSettingsStyle()
4127
+ const removeHostPatch = installHostPatchStyle()
3944
4128
  return () => {
3945
4129
  disposeLayer()
3946
4130
  removeStyle()
3947
4131
  removeSettings()
4132
+ removeHostPatch()
3948
4133
  try {
3949
4134
  document.removeEventListener('visibilitychange', healOnVisible)
3950
4135
  window.removeEventListener('focus', healOnFocus)
package/docs/DESIGN.md CHANGED
@@ -258,8 +258,10 @@ WCAG 2.x 的相对亮度比在暗底上**系统性高估**可读性。同一个
258
258
 
259
259
  - **暖砂与苔绿在 deutan 下几乎同色**(深色画布 ΔE **0.84**)。也就是说红绿色盲用户在深色侧几乎分不出这两套 ——
260
260
  好在它只是审美选择,不承载信息。
261
- - **红/绿语义色在 protan 下只剩 ΔE 5.7**。语义色由宿主提供(`--dsw-alias-state-*`),插件**有意不染色**:
262
- 把「成功 / 失败」也染成用户主色会让语义丢信息。所以这一层需要靠**图标或文字**区分,不能只靠颜色。
261
+ - **红/绿语义色在 protan 下只剩 ΔE 5.7**。语义色由宿主提供(`--dsw-alias-state-*`),
262
+ 1.13.5 起插件会给它们**做温度调和**(`harmonizeSemantic`:色相旋转封顶 8°、色度压到 88–92%,
263
+ 明度一律沿用宿主)—— 但"只是换冷暖",不是把「成功 / 失败」染成用户主色:
264
+ 色觉缺陷下这一层仍然需要靠**图标或文字**区分,不能只靠颜色。
263
265
 
264
266
  三套预置在 achromat(全色盲)下的层间明度差仍保留(ΔE 3.7–5.9),层级不依赖色度。
265
267
 
@@ -293,12 +295,40 @@ WCAG 2.x 的相对亮度比在暗底上**系统性高估**可读性。同一个
293
295
  三级 5.3–5.4、链接 5.4–5.5)。主色无彩(C < 0.004)时派生中性纸面,
294
296
  避免「黑底 + 白字 + 黄强调派生出一套淡粉」那类老问题。
295
297
 
298
+ ### 底色明度也参与(1.13.5 补丁)
299
+
300
+ 1.13.5 之前,三色里的「底色」只贡献**色相与色度**,画布明度由引擎按模式写死(深色 ≈ 0.226)。
301
+ 用户把底色设成 `#070b11`(L≈0.148)时屏幕上是 `#191c21` 一档的蓝灰 —— 用户读成"底色没变",
302
+ 而且这个读法是对的:**控件叫「底色」,那它就该是底色**。
303
+
304
+ 现在的规则只有三条:
305
+
306
+ | 底色明度 | 行为 |
307
+ |---|---|
308
+ | L ≤ 0.35(明显偏暗) | 它成为**深色模式的画布明度**,夹到 0.14–0.30 |
309
+ | L ≥ 0.75(明显偏亮) | 它成为**浅色模式的画布明度**,夹到 0.90–0.985 |
310
+ | 0.35 < L < 0.75(中间灰) | 不表达明暗意图,两侧都沿用引擎值 |
311
+
312
+ 只锚**画布这一档**:800–950 四层面按同一 delta 平移(层间距 0.03 L 不变),浅色侧同理平移
313
+ 00–200。文字色不在这次平移里,它们随后由 `tuneRamps` 在新承载面上重解对比度 ——
314
+ 所以承诺下限一条没松(该用户配色实测正文对画布 11.33:1、对 875 面 10.16:1)。
315
+
316
+ 下限 0.14 是**层次余量**,不是审美:深色侧还有三层更亮的面,左栏「更深」档再往下 -0.042 L;
317
+ 画布压到 0.10 时左栏会掉进 `#020407` 一档的近纯黑,整体塌成一块。
318
+
319
+ 同一次回执里还有一条:**线条色**过去取的是文字色的 alpha。文字一旦近中性
320
+ (`#e1eced`,C≈0.012),发丝线就是一片中性灰,用户读作"线条还是默认的"。
321
+ 自定义配色下线条改成**配色色相 + 色度下限 0.02**(明度与 alpha 不变,对比关系不变);
322
+ 预置仍走墨/纸原色,不受影响。
323
+
324
+
296
325
  ### 运行时覆盖报告(`getCoverageReport`)
297
326
 
298
327
  诊断导出(设置页「复制诊断信息」)多一份 `coverage`:遍历宿主样式表里所有 `--dsw-*` 自定义属性,
299
328
  按四类分账 —— `ours`(本插件覆盖层接管)、`derived`(值是 `var(--dsw-…)` 引用,跟着基础 token 走)、
300
- `literalChromatic`(字面带彩色值,宿主语义色等,有意不染)、`literalGray`(字面灰值,是「跟配色」
301
- 的候选)。它回答的是「宿主还有多少 token 没跟上配色」,只读、尽力而为:
329
+ `literalChromatic`(字面带彩色值)、`literalGray`(字面灰值,是「跟配色」的候选)。
330
+ 1.13.5 起语义色(red / green / amber)进入 `ours`,剩下的 `literalChromatic` 只有首次引导页强调色这类
331
+ 真正够不到的字面量。它回答的是「宿主还有多少 token 没跟上配色」,只读、尽力而为:
302
332
  拿不到 `styleSheets`(沙箱、跨域)就返回 `{ ok: false }`,不影响其它诊断。
303
333
 
304
334
  ## 宽色域(display-P3):本期不做(1.10.0)
@@ -435,7 +465,10 @@ MIN_MAIN 从 15.5 放宽到 12,1.9.7 起深色正文再降到 10:给“带
435
465
 
436
466
  **本来就跟随配色的中性组件**:边框(用色阶两端的墨/纸做 alpha)、分隔线、骨架屏、菜单表面、
437
467
  `interactive-bg-*` 悬停层、`bg-document-selection`(选择高亮)、`markdown-code-segment-*`。
438
- **故意不跟**:遮罩 `bg-mask-*`(保持中性黑)、代码 diff 的红绿、`state-error/warn` 语义色。
468
+ **故意不跟**:遮罩 `bg-mask-*`(保持中性黑)、首次引导页那几条字面包彩/渐变(只在 onboarding 出现)。
469
+ **1.13.5 起改为「跟温度、不跟语义」**:代码 diff 的红绿(`code-diff-*`)、`state-{error,success,warn}-*`
470
+ —— 它们此前完全不跟,是"好多颜色还是默认色"的主因;现在色相往配色色相拉但**封顶 8°**,
471
+ 红仍是红、绿仍是绿、琥珀仍是琥珀,`check-audit` 第 29 组守着这个上限与三者之间的可辨距离。
439
472
 
440
473
  ## 开关 / 勾选 / 单选
441
474
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.13.3",
3
+ "version": "1.13.5",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",