dsh-theme-palette 1.8.0 → 1.8.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/client.js +102 -18
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,43 @@
4
4
 
5
5
  ## 版本一览
6
6
 
7
+ ### 1.8.2
8
+ **暖砂字体体系修正**(用户反馈“和其他两套不一样,尤其字体颜色”):
9
+
10
+ - **根因 1:深色正文字色度没有按色域归一**。`paperC` 是绝对值,×2.4 后暖砂请求 130%、靛蓝 166% 色域上限,
11
+ 实际色度被裁成 0.0244 / 0.0115 / 0.0303——暖砂深正文几乎是靛蓝的两倍黄。现在三套统一 `paperC=0.0053`
12
+ (标准档实际 ≈0.0127),浅/深正文色度比降到 1.21 / 1.10
13
+ - **根因 2:暖砂是唯一「深色面 60°、正文 84°」的双色相预置**。深色面橙棕、正文黄绿,色相分裂 24°;
14
+ 现在 `paper` 角色跟随 `darkHue`,并把暖砂 `hue` 84→72、`darkHue` 60→66,全套色相收敛到 12° 内
15
+ - 新增断言:深正文色相偏离 `darkHue` ≤12°、三套浅色正文色度比 ≤1.25、深色 ≤1.5
16
+ - 新增 `tools/preview-typography.mjs`:三套 × 明暗并排渲染同一段文字,发版前目检
17
+
18
+ 语法高亮跟随配色(1.8.1 里“未做”的那条,前提已实测):
19
+
20
+ - **实测依据**:宿主渲染器用 Shiki 的 `css-variables` 模式(`class="shiki css-variables"`,
21
+ `style="background-color:var(--shiki-background)"`),token 颜色走 CSS 变量,
22
+ 所以覆盖 `--shiki-token-*` 生效,不需要 `!important`
23
+ - **色相策略**:从强调色相取 3 个色族(强调 / +150° 冷族 / -60° 暖族),
24
+ 同族靠明度分常/表达;每个 token 对代码块底求解到 **≥4.6:1**,四族最小 ΔE **≥10**
25
+ - 覆盖 9 类 token(keyword / function / constant / parameter / string / string-expression /
26
+ comment / punctuation / link)+ `--shiki-background` / `--shiki-foreground`
27
+ - **md=off 时不输出**任何 shiki token,交回宿主原色
28
+ - 新增 `tools/preview-code.mjs`:用生产 token 渲染一段真实代码,发版前肉眼确认
29
+ - 断言:9 类 token 对比度 ≥4.6、四族 ΔE ≥10、shiki 背景与代码块一致、md=off 无输出
30
+
31
+ ### 1.8.1
32
+ Markdown 配色丰富化(非加色相,而是加层级 + 修代码块底):
33
+
34
+ - **标题三档**:h1/h2 → h3/h4 → h5/h6 由强到弱(`--dshtp-md-h1/h2/h3`),不再六级同色;
35
+ 层级按**着色强度**区分,字号仍由宿主决定
36
+ - **引用 / 表格 / 分割线 / 列表**:引用淡底 + 左侧强调边、表头淡底、表格边框与分割线跟随语义 token、列表标记用三级文字色
37
+ - **修代码块双层底**:宿主 `<pre>` 背景来自 `--shiki-background → --dsw-alias-markdown-code-block`(bluish-50/900),
38
+ 此前只染 `.md-code-block` 外层,内层仍是宿主中性色;现在直接覆盖该 token(含 banner),两层同色
39
+ - **新增断言**:标题三档互异、正文/代码块底 ≥4.5、正文/引用底 ≥4.5、次级/表头底 ≥5.5、
40
+ `<pre>` 底色与代码块一致、`md=off` 时全部继承;契约测试补 `--dsw-alias-markdown-code-block`
41
+ - **未做**:语法高亮(`--shiki-token-*`)跟随配色。宿主构建里这 9 个变量只定义、无消费者,
42
+ 需先在 GUI 实测渲染器是吃变量还是内联色,再决定方案——不在未验证的情况下强改
43
+
7
44
  ### 1.8.0
8
45
  依据 [竞品调研](../../_audit-theme-palette-20261023/竞品调研-2026-10-23.md) 的差距反哺:把工程严谨变成可见、可分享、可验证的东西(不改求解器)。
9
46
 
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.8.0'
30
+ const PLUGIN_VERSION = '1.8.2'
31
31
  const STORE_KEY = 'dsh-theme-palette.v1'
32
32
  const OVERRIDE_SOURCE = 'dsh-theme-palette'
33
33
 
@@ -107,22 +107,22 @@ window.__ModuleLoader__.load({
107
107
  { id: 'mono', label: '黑白', desc: '无色调 · 默认', tinted: false },
108
108
  {
109
109
  id: 'warm', label: '暖砂', desc: '米白 + 棕调', tinted: true,
110
- hue: 84, darkHue: 60, lightC: 0.030, midC: 0.010, textC: 0.004,
111
- darkC: 0.014, darkLift: 0.026, paperC: 0.014, // 深底 #1e1b14 L 0.223 / 30% 色域
110
+ hue: 72, darkHue: 66, lightC: 0.030, midC: 0.010, textC: 0.004,
111
+ darkC: 0.014, darkLift: 0.026, paperC: 0.0053, // 深正文按色域归一:×2.4 后 ≈0.0127
112
112
  accentHue: 60, accentC: 0.101, accentL: 0.58, // 标准色 #A9683E
113
113
  accentScale: 1.0,
114
114
  },
115
115
  {
116
116
  id: 'indigo', label: '靛蓝', desc: '冷白 + 蓝调', tinted: true,
117
117
  hue: 266, lightC: 0.020, midC: 0.010, textC: 0.004,
118
- darkC: 0.028, darkLift: 0.032, paperC: 0.009, // 深底 #161c2a L 0.227 / 19% 色域
118
+ darkC: 0.028, darkLift: 0.032, paperC: 0.0053, // 同上:三套统一,避免被色域裁成两倍差
119
119
  accentHue: 259, accentC: 0.107, accentL: 0.5108, // 标准色 #3F66A3
120
120
  accentScale: 1.0,
121
121
  },
122
122
  {
123
123
  id: 'moss', label: '苔绿', desc: '浅灰绿 + 绿调', tinted: true,
124
124
  hue: 162, lightC: 0.016, midC: 0.010, textC: 0.004,
125
- darkC: 0.015, darkLift: 0.042, paperC: 0.0125, // 深底 #1a211b L 0.239 / 24% 色域
125
+ darkC: 0.015, darkLift: 0.042, paperC: 0.0053, // 同上
126
126
  accentHue: 139, accentC: 0.096, accentL: 0.5335, // 标准色 #4F7A42
127
127
  accentScale: 1.0,
128
128
  },
@@ -495,9 +495,10 @@ window.__ModuleLoader__.load({
495
495
  // 换成 60°(橙棕)才是咖啡色。浅色侧仍是 88°(米白)。
496
496
  // 文字档(含深色正文 paper)跟随「文字」这一项自己的色相;预设没有 textHue,行为不变。
497
497
  const tintRole = role === 'text' || role === 'paper'
498
+ // 深色角色(含深色正文 paper)统一走 darkHue,避免「深色面橙棕、正文黄绿」的双色相
498
499
  const hue = tintRole && scheme.textHue !== undefined
499
500
  ? scheme.textHue
500
- : (role === 'dark' && scheme.darkHue !== undefined ? scheme.darkHue : scheme.hue)
501
+ : ((role === 'dark' || role === 'paper') && scheme.darkHue !== undefined ? scheme.darkHue : scheme.hue)
501
502
  bluish[step] = fromOklch(clamp(compressL(lifted), 0, 0.995), roleChroma(role, weight, scheme, scales), hue)
502
503
  }
503
504
  const neutral = {}
@@ -870,41 +871,115 @@ window.__ModuleLoader__.load({
870
871
  ? 'var(--dsw-alias-bg-base)'
871
872
  : 'var(--dsw-specific-sidebar-fill)'
872
873
  pairs['--dshtp-rightbar-fill'] = { light: rightbarFill, dark: rightbarFill }
873
- // Markdown 强调色:标题 = 正文色混 40% 强调色;加粗 = 混 20%(比标题弱一档)
874
+ // Markdown 强调色:标题分三档(h1/h2 → h3/h4 → h5/h6),加粗比标题再弱一档。
875
+ // 分档而不是给六级标题六种色:长文靠层级扫读,不靠色相数量。
874
876
  if (params.tinted) {
877
+ const tintText = (hex, accentHex, extraChroma, lift) => {
878
+ const [L, C] = toOklch(hex)
879
+ const acc = toOklch(accentHex)
880
+ return fromOklch(clamp(L + lift, 0.05, 0.97), C + acc[1] * extraChroma, acc[2])
881
+ }
875
882
  if (mdMode === 'off') {
876
883
  const inherit = { light: 'inherit', dark: 'inherit' }
877
- pairs['--dshtp-md-heading'] = inherit
878
- pairs['--dshtp-md-strong'] = inherit
884
+ for (const k of ['--dshtp-md-h1', '--dshtp-md-h2', '--dshtp-md-h3', '--dshtp-md-strong',
885
+ '--dshtp-md-quote-fill', '--dshtp-md-quote-border', '--dshtp-md-table-head']) pairs[k] = inherit
879
886
  } else {
880
887
  // 加粗:只加色度、不动明度 —— 混色会把正文调亮(浅色下反而比正文更弱)。
881
888
  // 标题:再往中间推一点明度(浅色提亮 / 深色压暗),配合更大的字号拉开层级。
882
- const tintText = (hex, accentHex, extraChroma, lift) => {
883
- const [L, C] = toOklch(hex)
884
- const acc = toOklch(accentHex)
885
- return fromOklch(clamp(L + lift, 0.05, 0.97), C + acc[1] * extraChroma, acc[2])
886
- }
887
- pairs['--dshtp-md-heading'] = {
889
+ pairs['--dshtp-md-h1'] = {
888
890
  light: tintText(ramps.bluish['1000'], ramps.deepseek['500'], 0.45, 0.065),
889
891
  dark: tintText(ramps.bluish['50'], ramps.deepseek['400'], 0.40, -0.030),
890
892
  }
893
+ pairs['--dshtp-md-h2'] = {
894
+ light: tintText(ramps.bluish['1000'], ramps.deepseek['500'], 0.38, 0.035),
895
+ dark: tintText(ramps.bluish['50'], ramps.deepseek['400'], 0.36, -0.018),
896
+ }
897
+ pairs['--dshtp-md-h3'] = {
898
+ light: tintText(ramps.bluish['1000'], ramps.deepseek['500'], 0.30, 0.015),
899
+ dark: tintText(ramps.bluish['50'], ramps.deepseek['400'], 0.28, -0.008),
900
+ }
891
901
  pairs['--dshtp-md-strong'] = {
892
902
  light: tintText(ramps.bluish['1000'], ramps.deepseek['500'], 0.32, 0.012),
893
903
  dark: tintText(ramps.bluish['50'], ramps.deepseek['400'], 0.32, -0.010),
894
904
  }
905
+ // 引用 / 表头:只掺 4–7% 强调色,主文字在这些面上仍要走"不降级"的对比度断言
906
+ pairs['--dshtp-md-quote-fill'] = {
907
+ light: mixHex(ramps.deepseek['500'], ramps.bluish['00'], 0.04),
908
+ dark: mixHex(ramps.deepseek['400'], ramps.bluish['950'], 0.06),
909
+ }
910
+ pairs['--dshtp-md-quote-border'] = {
911
+ light: mixHex(ramps.deepseek['500'], ramps.bluish['150'], 0.55),
912
+ dark: mixHex(ramps.deepseek['400'], ramps.bluish['800'], 0.55),
913
+ }
914
+ pairs['--dshtp-md-table-head'] = {
915
+ light: mixHex(ramps.deepseek['500'], ramps.bluish['60'], 0.05),
916
+ dark: mixHex(ramps.deepseek['400'], ramps.bluish['850'], 0.07),
917
+ }
895
918
  }
896
919
  }
897
920
  // 代码块底色:宿主用 bg-layer-1,浅色下与画布同色(都指 00)→ 看不出边界。
898
921
  // 这里给它一档独立的"内嵌面板"色:卡层色 + 少量强调色相,保证与画布有可辨差。
899
922
  // mixHex(a, b, w) = a*w + b*(1-w):a 是要掺进去的颜色,b 是底色。
900
- pairs['--dshtp-code-fill'] = {
923
+ const codeFill = {
901
924
  light: mixHex(ramps.deepseek['500'], ramps.bluish['60'], 0.08),
902
925
  dark: mixHex(ramps.deepseek['400'], ramps.bluish['875'], 0.10),
903
926
  }
927
+ pairs['--dshtp-code-fill'] = codeFill
928
+ // 宿主的 <pre> 背景来自 --shiki-background → --dsw-alias-markdown-code-block(bluish-50/900)。
929
+ // 不覆盖它就会出现"外层 div 染了、内层 pre 没染"的双层底;直接改这个 token 才是根治。
930
+ pairs['--dsw-alias-markdown-code-block'] = codeFill
931
+ pairs['--dsw-alias-markdown-code-block-banner'] = {
932
+ light: mixHex(ramps.deepseek['500'], ramps.bluish['50'], 0.06),
933
+ dark: mixHex(ramps.deepseek['400'], ramps.bluish['850'], 0.08),
934
+ }
904
935
  pairs['--dshtp-code-inline'] = {
905
936
  light: mixHex(ramps.deepseek['500'], ramps.bluish['50'], 0.05),
906
937
  dark: mixHex(ramps.deepseek['400'], ramps.bluish['900'], 0.07),
907
938
  }
939
+ /* ---- 语法高亮(1.8.2)----
940
+ * 宿主渲染器用 Shiki 的 `css-variables` 模式(className="shiki css-variables",
941
+ * 样式走 var(--shiki-*)),所以覆盖这些变量有效,不需要 !important。
942
+ *
943
+ * 色相策略:不引入与配色无关的固定色,而是从强调色相出发取 3 个色族
944
+ * (强调 / +150° 的冷族 / -55° 的暖族),再用明度与色度拉开可辨度;
945
+ * 每个 token 都必须对代码块底达到 4.6:1,否则沿远离背景的方向调整明度。
946
+ */
947
+ if (params.tinted && mdMode === 'on') {
948
+ const baseHue = params.accentHue === undefined ? scheme.hue : params.accentHue
949
+ const pickL = (bg) => (contrast('#ffffff', bg) >= contrast('#000000', bg) ? 0.80 : 0.45)
950
+ const dirOf = (bg) => (contrast('#ffffff', bg) >= contrast('#000000', bg) ? 1 : -1)
951
+ const ink = (bg, hue, chroma, startL, target) => {
952
+ const dir = dirOf(bg)
953
+ let L = startL
954
+ for (let i = 0; i < 160; i++) {
955
+ const c = fromOklch(clamp(L, 0.02, 0.99), chroma, hue)
956
+ if (contrast(c, bg) >= target) return c
957
+ L += dir * 0.004
958
+ }
959
+ return fromOklch(clamp(L, 0.02, 0.99), chroma, hue)
960
+ }
961
+ // 每个色族自带目标对比度:同族靠明度分"常/表达",异族靠色相差 55–150°
962
+ const families = {
963
+ keyword: [baseHue, 0.15, 4.6],
964
+ function: [baseHue + 70, 0.12, 5.0],
965
+ constant: [baseHue - 60, 0.13, 4.8],
966
+ parameter: [baseHue + 30, 0.10, 5.6],
967
+ string: [baseHue + 150, 0.10, 4.8],
968
+ 'string-expression': [baseHue + 150, 0.085, 5.8],
969
+ comment: [baseHue, 0.015, 4.6],
970
+ punctuation: [baseHue, 0.02, 6.0],
971
+ }
972
+ for (const [name, [hue, chroma, target]] of Object.entries(families)) {
973
+ const key = '--shiki-token-' + name
974
+ pairs[key] = {
975
+ light: ink(codeFill.light, hue, chroma, pickL(codeFill.light), target),
976
+ dark: ink(codeFill.dark, hue, chroma, pickL(codeFill.dark), target),
977
+ }
978
+ }
979
+ pairs['--shiki-token-link'] = { light: lightLink(ramps, false), dark: ramps.deepseek['400'] }
980
+ pairs['--shiki-background'] = codeFill
981
+ pairs['--shiki-foreground'] = { light: ramps.bluish['1000'], dark: ramps.bluish['50'] }
982
+ }
908
983
  const scroll = scrollbarTokens(ramps, params, scrollMode)
909
984
  for (const [name, pair] of Object.entries(scroll)) pairs[name] = pair
910
985
  // 开关/勾选/单选的共用支点:brand-primary 本来是近黑/近白,改成强调色。
@@ -958,8 +1033,17 @@ window.__ModuleLoader__.load({
958
1033
  '[data-rightbar-fullscreen] [data-rightbar-col]{background:var(--dsw-alias-bg-base)}',
959
1034
  // Markdown 标题/加粗:容器类是 CSS Modules 的 `_markdown_<hash>_<n>`,
960
1035
  // 用「类名里含 _markdown_」做钩子(局部名稳定,比整串哈希耐版本)。
961
- '[class*="_markdown_"] :where(h1,h2,h3,h4,h5,h6){color:var(--dshtp-md-heading,inherit)}',
1036
+ // 三级层次:h1/h2 最重、h3/h4 次之、h5/h6 最轻(不是六级六色)。
1037
+ '[class*="_markdown_"] :where(h1,h2){color:var(--dshtp-md-h1,inherit)}',
1038
+ '[class*="_markdown_"] :where(h3,h4){color:var(--dshtp-md-h2,inherit)}',
1039
+ '[class*="_markdown_"] :where(h5,h6){color:var(--dshtp-md-h3,inherit)}',
962
1040
  '[class*="_markdown_"] strong{color:var(--dshtp-md-strong,inherit)}',
1041
+ // 引用 / 表格 / 分割线 / 列表:只动"结构性"颜色,不碰正文
1042
+ '[class*="_markdown_"] blockquote{background:var(--dshtp-md-quote-fill,transparent);border-left:3px solid var(--dshtp-md-quote-border,currentColor)}',
1043
+ '[class*="_markdown_"] table th{background:var(--dshtp-md-table-head,transparent)}',
1044
+ '[class*="_markdown_"] :where(table th,table td){border-color:var(--dsw-alias-border-l2)}',
1045
+ '[class*="_markdown_"] hr{border:0;border-top:.5px solid var(--dsw-alias-border-l3)}',
1046
+ '[class*="_markdown_"] li::marker{color:var(--dsw-alias-label-tertiary)}',
963
1047
  // 代码块:宿主把底色写在 CSS Modules 的 .block 上(浅色与画布同为 00,看不出边界)。
964
1048
  // 这里以 `.md-code-block` 提高一级特异性覆盖,不需要 !important。
965
1049
  '[class*="_markdown_"] .md-code-block{background:var(--dshtp-code-fill,inherit)}',
@@ -2039,7 +2123,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
2039
2123
 
2040
2124
  // 开发期接口:palette-dev.mjs 通过它拿到同一套引擎(避免「报表好看、上线不一样」)
2041
2125
  if (DEV_OPT_IN && typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
2042
- window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, RIGHTBAR_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, rampsFor, floors, encodeConfig, decodeConfig, customScheme, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, settingsCss: SETTINGS_CSS, cardColors })
2126
+ window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, RIGHTBAR_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, fromOklch, rampsFor, floors, encodeConfig, decodeConfig, customScheme, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, settingsCss: SETTINGS_CSS, cardColors })
2043
2127
  }
2044
2128
 
2045
2129
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",