dsh-theme-palette 1.4.4 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,27 @@
13
13
  复核宿主源码发现它带 `includePrerelease`,结论相反。现在这条判断固化进脚本,不再靠手算
14
14
  - 该改动只影响本地发版工具,**不进 npm 包**(`files` 不含 `tools/`),故不升版本号
15
15
 
16
+ ### 1.5.1
17
+ - `dsh.compatibility.profiles` 补 `web`:在 web profile 上实测 5 张卡片(含「自定义」)正常显示,
18
+ 与 desktop 一致;`dshOperations` 的 install/start 保持 `passed`,uninstall/rollback 仍如实 `unknown`
19
+ - 只改声明,无运行时改动
20
+
21
+ ### 1.5.0
22
+ 反哺 A-2 / A-3 / A-4 / A-5 —— 把"能调"扩成"可迁移、可自定义、可无障碍、可回归":
23
+
24
+ - **A-3 自定义配色**:新增第 5 张卡片「自定义」,给 底色 / 文字 / 强调 三个颜色,
25
+ 引擎只取它们的**色相与色度**,明度仍按明暗模式由引擎决定 —— 因此对比度下限照旧成立
26
+ (实测三组自选色:正文 14.6–15.0、三级 4.62–4.71)
27
+ - **A-2 配置导入/导出**:`dshs1.` 短码(base64url),导入逐项校验、任一不符整体拒绝;
28
+ 「更多选项 → 配置」可复制 / 粘贴导入,覆盖全部 8 项
29
+ - **A-4 高对比档**:新开关把文字下限抬到 AAA(正文 ≥15 / 次文 ≥9 / 三级 ≥7 / 链接 ≥7),
30
+ 只调求解目标,不换配色;实测 三级 4.65→7.06、次文 6.2→9.05、链接 4.62→7.03
31
+ - **A-5 真机回归可用**:dev 接口新增 `window.__DSH_THEME_PALETTE_DEV_REQUEST__` 入口,
32
+ `tools/shoot-desktop.mjs` 改用 CDP `Page.addScriptToEvaluateOnNewDocument` 注入 + 重载,
33
+ B 层截图回归不再依赖"无人创建的全局"
34
+ - 顺带修:求解器位移上限从写死的 0.13 改为可传参(高对比需要更大位移);迭代次数跟随上限
35
+ - `tools/preview-settings.mjs` 支持 `--palette <id>`,可预览任意配色(含自定义)的设置页
36
+
16
37
  ### 1.4.4
17
38
  - 补 `dsh.compatibility` 兼容性履历(反哺 A-1):
18
39
 
package/README-zh.md CHANGED
@@ -22,6 +22,7 @@ DeepSeek Harness(Web / 桌面)的主题配色插件:**4 套配色 × 明
22
22
  - 色阶**整体重算**而不是替换:OKLCH 重建 → 角色化色度 → **sRGB 色域钳位** → 在真实承载面上求解对比度;
23
23
  - 覆盖 27,000+ 处 token 引用:正文、次级、三级、注释、链接、侧栏、卡片、主人气泡、主按钮、滚动条、开关、Markdown 标题/加粗、代码块;
24
24
  - 可调:文字着色三档、侧栏三档、右栏两档、Markdown 强调、滚动条、开关配色;
25
+ - **自定义配色**(给 底色/文字/强调 三色,对比度保证不变)、**配置导入导出**(`dshs1.` 短码)、**高对比**档、4 种界面语言;
25
26
  - **自愈**:宿主丢弃 override 层时自动重放;
26
27
  - 界面支持 简体中文 / 繁體中文 / English / 日本語(跟随宿主,也可手选)。
27
28
 
package/README.md CHANGED
@@ -22,6 +22,7 @@ The plugin only stacks colour tokens through `ctx.theme.overrideTokens`; it does
22
22
  - the whole ramp is **recomputed**, not swapped: OKLCH rebuild → role-based chroma → **sRGB gamut clamp** → contrast solved on real surfaces;
23
23
  - 27,000+ token references covered: body, secondary, tertiary, captions, links, sidebars, cards, user bubble, primary button, scrollbars, switches, Markdown headings/bold, code blocks;
24
24
  - adjustable: text tint (3), sidebar depth (3), right panel (2), Markdown emphasis, scrollbars, switch colour;
25
+ - **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;
25
26
  - **self-healing**: if the host drops the override layer, the palette is re-applied;
26
27
  - UI in 简体中文 / 繁體中文 / English / 日本語 (follows the host language, can be pinned).
27
28
 
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.4.4'
30
+ const PLUGIN_VERSION = '1.5.1'
31
31
  const STORE_KEY = 'dsh-theme-palette.v1'
32
32
  const OVERRIDE_SOURCE = 'dsh-theme-palette'
33
33
 
@@ -126,17 +126,71 @@ window.__ModuleLoader__.load({
126
126
  accentHue: 139, accentC: 0.096, accentL: 0.5335, // 标准色 #4F7A42
127
127
  accentScale: 1.0,
128
128
  },
129
+ { id: 'custom', label: '自定义', desc: '三色派生', tinted: true, custom: true, hue: 60, darkHue: 60, lightC: 0.02, midC: 0.01, textC: 0.004, darkC: 0.014, darkLift: 0.03, paperC: 0.012, accentHue: 60, accentC: 0.1, accentL: 0.58, accentScale: 1 },
129
130
  ]
130
131
 
131
132
  /* 对比度下限:主文本 16 / 次文本 6 / 三级文本 4.6(AA)/ 链接 4.6 */
132
133
  /* 对比度下限:按**真实承载面**定(卡层/模块层比画布更难达标),不是按画布定。
133
134
  正文 15.5 / 次级 6.2 / 三级 4.62(AA)/ 链接 4.62(AA) */
135
+ /* ---------- 自定义配色(反哺 A-3)----------
136
+ * 只取三色的**色相与色度**,明度由引擎按明暗模式决定;
137
+ * 色度夹在安全区间,避免用户选纯白/纯黑时引擎退化。
138
+ */
139
+ const CUSTOM_KEY = 'dsh-theme-palette.custom.v1'
140
+ const CUSTOM_FIELDS = ['bg', 'text', 'accent']
141
+ const CUSTOM_DEFAULT = { bg: '#f5efe6', text: '#1d1a17', accent: '#a9683e' }
142
+ const HEX_RE = /^#[0-9a-f]{6}$/i
143
+
144
+ function readCustom() {
145
+ const out = Object.assign({}, CUSTOM_DEFAULT)
146
+ try {
147
+ const raw = localStorage.getItem(CUSTOM_KEY)
148
+ if (raw) {
149
+ const parsed = JSON.parse(raw)
150
+ for (const k of CUSTOM_FIELDS) if (parsed && typeof parsed[k] === 'string' && HEX_RE.test(parsed[k])) out[k] = parsed[k]
151
+ }
152
+ } catch (err) { /* 退回默认 */ }
153
+ return out
154
+ }
155
+
156
+ function writeCustom(next) {
157
+ try { localStorage.setItem(CUSTOM_KEY, JSON.stringify(next)) } catch (err) { /* 忽略 */ }
158
+ }
159
+
160
+ function customScheme(colors) {
161
+ const bg = toOklch(colors.bg)
162
+ const tx = toOklch(colors.text)
163
+ const ac = toOklch(colors.accent)
164
+ const clampN = (v, lo, hi) => Math.min(hi, Math.max(lo, v))
165
+ return {
166
+ id: 'custom', label: '自定义', desc: '三色派生', tinted: true, custom: true,
167
+ hue: bg[2], darkHue: bg[2],
168
+ lightC: clampN(bg[1] * 0.9, 0.008, 0.045), midC: 0.010,
169
+ textC: clampN(tx[1], 0.002, 0.030),
170
+ darkC: clampN(bg[1] * 0.75, 0.006, 0.035), darkLift: 0.030,
171
+ paperC: clampN(tx[1], 0.004, 0.030),
172
+ accentHue: ac[2], accentC: clampN(ac[1], 0.04, 0.18), accentL: ac[0], accentScale: 1.0,
173
+ }
174
+ }
175
+
134
176
  const MIN_MAIN = 12
135
177
  const MIN_SECONDARY = 6.2
136
178
  const MIN_TERTIARY = 4.62
137
179
  const MIN_LINK = 4.62
138
180
  const MIN_LINK_DARK = 4.62
139
181
 
182
+ /**
183
+ * 高对比模式(反哺 A-4):把四档文字下限整体抬到 WCAG AAA 级别。
184
+ * 不改配色,只让求解器把文字压得更狠;默认关闭。
185
+ */
186
+ const FLOORS_HC = { main: 15, secondary: 9, tertiary: 7, link: 7, linkDark: 7 }
187
+ function floors(hc) {
188
+ return hc === true
189
+ ? FLOORS_HC
190
+ : { main: MIN_MAIN, secondary: MIN_SECONDARY, tertiary: MIN_TERTIARY, link: MIN_LINK, linkDark: MIN_LINK_DARK }
191
+ }
192
+ const HC_KEY = 'dsh-theme-palette.hc.v1'
193
+
140
194
  /**
141
195
  * 区块底色:左侧栏相对主画布的深浅档位。
142
196
  * lift = OKLab 亮度增量(正 = 更浅);chroma = 保留多少色度(越浅越该少)。
@@ -191,6 +245,12 @@ window.__ModuleLoader__.load({
191
245
  { id: 'off', label: '同正文' },
192
246
  ]
193
247
  const DEFAULT_MD = 'on'
248
+ /** 高对比开关(反哺 A-4):不改配色,把文字下限抬到 AAA。 */
249
+ const HC_MODES = [
250
+ { id: 'off', label: '关' },
251
+ { id: 'on', label: '开' },
252
+ ]
253
+ const DEFAULT_HC = 'off'
194
254
  const MD_KEY = 'dsh-theme-palette.md.v1'
195
255
 
196
256
  /**
@@ -415,21 +475,23 @@ window.__ModuleLoader__.load({
415
475
  * 对比度求解:前景达不到 target 就沿亮度往「离背景更远」的方向微调,
416
476
  * 最多挪 0.09(保持原有明暗层次,不把正文推成纯黑)。
417
477
  */
418
- function solveForeground(hexes, step, bgHex, target) {
478
+ function solveForeground(hexes, step, bgHex, target, cap) {
479
+ const maxShift = typeof cap === "number" ? cap : 0.13
419
480
  const [L0, C, hue] = toOklch(hexes[step])
420
481
  const darker = luminance(hexes[step]) < luminance(bgHex)
421
482
  let L = L0
422
- for (let i = 0; i < 40; i++) {
483
+ for (let i = 0; i < Math.ceil(maxShift / 0.004) + 4; i++) {
423
484
  const candidate = fromOklch(L, C, hue)
424
485
  if (contrast(candidate, bgHex) >= target) return candidate
425
486
  L += darker ? -0.004 : 0.004
426
487
  // 上限 0.13:三级文字要从 4.41 抬到 4.62 需要 ΔL≈0.096,旧的 0.09 会静默截断
427
- if (Math.abs(L - L0) > 0.13) break
488
+ if (Math.abs(L - L0) > maxShift) break
428
489
  }
429
490
  return fromOklch(L, C, hue)
430
491
  }
431
492
 
432
- function tuneRamps(ramps, scheme) {
493
+ function tuneRamps(ramps, scheme, hc) {
494
+ const F = floors(hc)
433
495
  // 靶点必须用**真实承载面**,不能用画布:三级文字与链接大量落在卡层(60)、
434
496
  // 悬浮层(150)、深色模块层(800)上,只对 00/950 求解会得到「画布达标、卡层不达标」。
435
497
  const lightCanvas = ramps.bluish['00']
@@ -437,20 +499,20 @@ window.__ModuleLoader__.load({
437
499
  const darkCanvas = ramps.bluish['950']
438
500
  const darkModule = ramps.bluish['800']
439
501
  const jobs = [
440
- ['1000', lightCanvas, MIN_MAIN],
441
- ['700', lightCard, MIN_SECONDARY],
442
- ['600', lightCard, MIN_TERTIARY],
443
- ['400', darkModule, MIN_TERTIARY],
502
+ ['1000', lightCanvas, F.main],
503
+ ['700', lightCard, F.secondary],
504
+ ['600', lightCard, F.tertiary],
505
+ ['400', darkModule, F.tertiary],
444
506
  ]
445
507
  for (const [step, bg, target] of jobs) {
446
508
  if (contrast(ramps.bluish[step], bg) >= target) continue
447
- ramps.bluish[step] = solveForeground(ramps.bluish, step, bg, target)
509
+ ramps.bluish[step] = solveForeground(ramps.bluish, step, bg, target, hc ? 0.30 : 0.13)
448
510
  }
449
511
  // 链接按「最亮的深色承载面(800)」兜底。
450
512
  // 注意这里**只有 400**:浅色链接不走这条色阶的中段(500 是设计稿的标准色,
451
513
  // 不能被求解器按承载面改写),浅色链接在 aliasOverrides 里从 500 派生。
452
- if (contrast(ramps.deepseek['400'], darkModule) < MIN_LINK_DARK) {
453
- ramps.deepseek['400'] = solveForeground(ramps.deepseek, '400', darkModule, MIN_LINK_DARK)
514
+ if (contrast(ramps.deepseek['400'], darkModule) < F.linkDark) {
515
+ ramps.deepseek['400'] = solveForeground(ramps.deepseek, '400', darkModule, F.linkDark, hc ? 0.30 : 0.13)
454
516
  }
455
517
  return ramps
456
518
  }
@@ -464,11 +526,12 @@ window.__ModuleLoader__.load({
464
526
  * 这样宿主里 500 的其它用途(info 按钮填充、焦点环、deep-diving 文字、卡片预览色)
465
527
  * 全部保持标准值,只有「当文字用」的链接换一条。
466
528
  */
467
- function lightLink(r) {
529
+ function lightLink(r, hc) {
530
+ const target = floors(hc).link
468
531
  if (r === BASE) return BASE.deepseek['500'] // 黑白配色没被覆盖,走宿主原值
469
532
  const bg = r.bluish['150']
470
- if (contrast(r.deepseek['500'], bg) >= MIN_LINK) return r.deepseek['500']
471
- return solveForeground(r.deepseek, '500', bg, MIN_LINK)
533
+ if (contrast(r.deepseek['500'], bg) >= target) return r.deepseek['500']
534
+ return solveForeground(r.deepseek, '500', bg, target, hc ? 0.30 : 0.13)
472
535
  }
473
536
 
474
537
  /* ================= token 组装 ================= */
@@ -478,26 +541,36 @@ window.__ModuleLoader__.load({
478
541
  const m = TEXT_MODES.find((x) => x.id === textMode) || TEXT_MODES[1]
479
542
  return { textScale: m.textScale, paperScale: m.paperScale, textLift: m.textLift || 0 }
480
543
  }
481
- function rampsFor(scheme, textMode) {
544
+ function rampsFor(scheme, textMode, hc) {
545
+ const hcKey = hc === true ? ':hc' : ''
546
+ // 自定义配色:按当前三色派生参数,缓存键必须带上三色
547
+ let params = scheme
548
+ let customKey = ''
549
+ if (scheme && scheme.custom === true) {
550
+ const colors = readCustom()
551
+ params = customScheme(colors)
552
+ customKey = ':' + colors.bg + colors.text + colors.accent
553
+ }
482
554
  // 黑白配色:色相/色度都是 0,但**仍然过一次引擎** —— 否则它会保留宿主的三级 3.71、
483
555
  // 链接 4.23(都没到 AA),而它正是默认配色。引擎在这里只做两件事:原样重建(C=0)+ 求解对比度。
484
- if (!scheme.tinted) {
485
- const monoKey = 'mono:aa'
556
+ if (!params.tinted) {
557
+ const monoKey = 'mono:aa' + hcKey
486
558
  let mono = rampCache.get(monoKey)
487
559
  if (mono === undefined) {
488
560
  const clone = (o) => Object.fromEntries(Object.entries(o))
489
561
  mono = tuneRamps(
490
562
  { bluish: clone(BASE.bluish), neutral: clone(BASE.neutral), deepseek: clone(BASE.deepseek), blue: clone(BASE.blue) },
491
563
  scheme,
564
+ hc,
492
565
  )
493
566
  rampCache.set(monoKey, mono)
494
567
  }
495
568
  return mono
496
569
  }
497
- const key = scheme.id + ':' + textMode
570
+ const key = params.id + ':' + textMode + hcKey + customKey
498
571
  let cached = rampCache.get(key)
499
572
  if (cached === undefined) {
500
- cached = tuneRamps(buildRamps(scheme, scalesFor(textMode)), scheme)
573
+ cached = tuneRamps(buildRamps(params, scalesFor(textMode)), params, hc)
501
574
  rampCache.set(key, cached)
502
575
  }
503
576
  return cached
@@ -508,7 +581,7 @@ window.__ModuleLoader__.load({
508
581
  * 不跟着色阶走,必须单独覆盖,否则暖底上会出现冷灰发丝线。
509
582
  * 值按「浅色 / 深色」给一对(overrideTokens 的硬性要求)。
510
583
  */
511
- function aliasOverrides(r) {
584
+ function aliasOverrides(r, hc) {
512
585
  const ink = r.bluish['1000'] // 浅色模式的墨色
513
586
  const paper = r.bluish['50'] // 深色模式的纸白
514
587
  const grey = r.bluish['700'] // 悬浮在媒体上的工具条用中性灰
@@ -517,7 +590,7 @@ window.__ModuleLoader__.load({
517
590
  宿主 `--dsw-alias-link` 指 deepseek-500(浅)/ 400(深),而 500 是品牌基准色。
518
591
  标准值当正文链接时过不了 AA(暖砂 #A9683E 在 150 面上 3.46),所以浅色链接
519
592
  用同色相的派生色;深色链接直接用 400(已由求解器兜到 4.62 起)。 */
520
- '--dsw-alias-link': { light: lightLink(r), dark: r.deepseek['400'] },
593
+ '--dsw-alias-link': { light: lightLink(r, hc), dark: r.deepseek['400'] },
521
594
 
522
595
  '--dsw-alias-border-l1': { light: withAlpha(ink, 0.04), dark: withAlpha(paper, 0.06) },
523
596
  '--dsw-alias-border-l2': { light: withAlpha(ink, 0.10), dark: withAlpha(paper, 0.12) },
@@ -628,8 +701,9 @@ window.__ModuleLoader__.load({
628
701
  }
629
702
  }
630
703
 
631
- function buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode) {
632
- const ramps = rampsFor(scheme, textMode || DEFAULT_TEXT)
704
+ function buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode, hcMode) {
705
+ const hc = hcMode === true
706
+ const ramps = rampsFor(scheme, textMode || DEFAULT_TEXT, hc)
633
707
  const pairs = {}
634
708
  if (!scheme.tinted) {
635
709
  // 黑白:只输出「被求解改动过」的那几步 + 派生链接,不整条覆盖(保持宿主原貌)
@@ -637,7 +711,7 @@ window.__ModuleLoader__.load({
637
711
  pairs['--dsw-static-neutral-bluish-' + step] = { light: ramps.bluish[step], dark: ramps.bluish[step] }
638
712
  }
639
713
  pairs['--dsw-static-deepseek-500'] = { light: ramps.deepseek['500'], dark: ramps.deepseek['500'] }
640
- pairs['--dsw-alias-link'] = { light: lightLink(ramps), dark: BASE.deepseek['400'] }
714
+ pairs['--dsw-alias-link'] = { light: lightLink(ramps, hc), dark: BASE.deepseek['400'] }
641
715
  // 主按钮同理:宿主浅色是红(对白字 4.34,差 0.16 才到 AA),压暗一档到 4.5
642
716
  // 注意:「更浅」档会把画布压到 #fafafa,按钮还要在最暗的那版画布上达标,
643
717
  // 所以按 #f5f5f5 兜底而不是纯白。
@@ -654,7 +728,7 @@ window.__ModuleLoader__.load({
654
728
  }
655
729
  }
656
730
  }
657
- const aliases = aliasOverrides(ramps)
731
+ const aliases = aliasOverrides(ramps, hc)
658
732
  for (const [name, pair] of Object.entries(aliases)) pairs[name] = pair
659
733
  }
660
734
  const sidebar = sidebarTokens(ramps, sidebarMode, scheme.tinted)
@@ -825,6 +899,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
825
899
  .dsh-tp-more{display:inline-flex;align-items:center;gap:6px;font:inherit;font-size:12.5px;color:var(--dsw-alias-label-tertiary);background:0 0;border:0;padding:6px 0 2px;cursor:pointer;align-self:flex-start;transition:color .12s}
826
900
  .dsh-tp-more:hover{color:var(--dsw-alias-label-primary)}
827
901
  .dsh-tp-adv{display:flex;flex-direction:column}
902
+ .dsh-tp-input{flex:1 1 220px;min-width:0;font:12px/20px ui-monospace,Consolas,monospace;padding:5px 9px;border-radius:8px;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}
903
+ .dsh-tp-input::placeholder{color:var(--dsw-alias-label-tertiary)}
904
+ .dsh-tp-mini{font:inherit;font-size:12.5px;line-height:20px;padding:5px 12px;border-radius:8px;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);cursor:pointer}
905
+ .dsh-tp-mini:hover{background:var(--dsw-alias-bg-layer-1)}
906
+ .dsh-tp-color{width:28px;height:22px;padding:0;border:.5px solid var(--dsw-alias-border-l4);border-radius:6px;background:transparent;cursor:pointer}
907
+ .dsh-tp-note{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}
828
908
  .dsh-tp-status{margin:0;font-size:11.5px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
829
909
  .dsh-tp-status.is-bad{color:var(--dsw-alias-state-error-primary)}
830
910
  `
@@ -976,6 +1056,58 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
976
1056
  try { localStorage.setItem(MD_KEY, id) } catch (err) { /* 同理 */ }
977
1057
  }
978
1058
 
1059
+ function readHcStored() {
1060
+ try {
1061
+ const raw = localStorage.getItem(HC_KEY)
1062
+ if (raw === 'on' || raw === 'off') return raw
1063
+ } catch (err) { /* 退回默认 */ }
1064
+ return DEFAULT_HC
1065
+ }
1066
+
1067
+ function writeHcStored(id) {
1068
+ try { localStorage.setItem(HC_KEY, id) } catch (err) { /* 同理 */ }
1069
+ }
1070
+
1071
+ /* ---------- 配置导入/导出(反哺 A-2)----------
1072
+ * 短码格式:`dshs1.` + base64url(JSON),字段全部用单字母,
1073
+ * 导入时逐项对照模式表校验,任何一项不认识就整体拒绝(不猜、不部分应用)。
1074
+ */
1075
+ const CONFIG_PREFIX = 'dshs1.'
1076
+
1077
+ function encodeConfig(state) {
1078
+ const raw = { v: 1, p: state.palette, s: state.sidebar, t: state.text, r: state.rightbar, c: state.scroll, k: state.control, m: state.md, h: state.hc, l: state.lang }
1079
+ try {
1080
+ const b64 = btoa(unescape(encodeURIComponent(JSON.stringify(raw))))
1081
+ return CONFIG_PREFIX + b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
1082
+ } catch (err) { return '' }
1083
+ }
1084
+
1085
+ const hasId = (list, id) => list.some((x) => x.id === id)
1086
+
1087
+ function decodeConfig(code) {
1088
+ if (typeof code !== 'string') return null
1089
+ const text = code.trim()
1090
+ if (!text.startsWith(CONFIG_PREFIX)) return null
1091
+ try {
1092
+ let b64 = text.slice(CONFIG_PREFIX.length).replace(/-/g, '+').replace(/_/g, '/')
1093
+ while (b64.length % 4 !== 0) b64 += '='
1094
+ const raw = JSON.parse(decodeURIComponent(escape(atob(b64))))
1095
+ if (!raw || raw.v !== 1) return null
1096
+ const next = {
1097
+ palette: SCHEMES.some((sc) => sc.id === raw.p) ? raw.p : null,
1098
+ sidebar: hasId(SIDEBAR_MODES, raw.s) ? raw.s : null,
1099
+ text: hasId(TEXT_MODES, raw.t) ? raw.t : null,
1100
+ rightbar: hasId(RIGHTBAR_MODES, raw.r) ? raw.r : null,
1101
+ scroll: hasId(SCROLL_MODES, raw.c) ? raw.c : null,
1102
+ control: hasId(CONTROL_MODES, raw.k) ? raw.k : null,
1103
+ md: hasId(MD_MODES, raw.m) ? raw.m : null,
1104
+ hc: hasId(HC_MODES, raw.h) ? raw.h : null,
1105
+ lang: raw.l === 'auto' || LOCALES.indexOf(raw.l) !== -1 ? raw.l : null,
1106
+ }
1107
+ return Object.values(next).every((v) => v !== null) ? next : null
1108
+ } catch (err) { return null }
1109
+ }
1110
+
979
1111
  /* ================= 文案 ================= */
980
1112
  /* ================= 多语言 ================= */
981
1113
  /**
@@ -1053,6 +1185,17 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1053
1185
  sidebar: '左侧栏底色',
1054
1186
  sidebarHint: '让侧栏比会话区更浅(或更深),做出区块层次。',
1055
1187
  lang: '语言',
1188
+ hc: '高对比',
1189
+ custom: '自定义配色',
1190
+ customHint: '取三个色的色相与色度;明度由引擎按明暗模式算,对比度仍由求解器兜底。',
1191
+ config: '配置',
1192
+ configHint: '导出这台机器的配色选择,或粘贴短码导入;导入会覆盖全部 8 项。',
1193
+ copy: '复制',
1194
+ applyImport: '导入',
1195
+ copied: '已复制',
1196
+ importOk: '已导入',
1197
+ importBad: '短码无效,已忽略',
1198
+ hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三级 ≥7)',
1056
1199
  },
1057
1200
  'zh-TW': {
1058
1201
  nav: '主題色',
@@ -1085,6 +1228,17 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1085
1228
  sidebar: '左側欄底色',
1086
1229
  sidebarHint: '讓側欄比會話區更淺(或更深),做出區塊層次。',
1087
1230
  lang: '語言',
1231
+ hc: '高對比',
1232
+ custom: '自訂配色',
1233
+ customHint: '取三個色的色相與色度;明度由引擎依明暗模式計算,對比度仍由求解器兜底。',
1234
+ config: '設定',
1235
+ configHint: '匯出這台機器的配色選擇,或貼上短碼匯入;匯入會覆蓋全部 8 項。',
1236
+ copy: '複製',
1237
+ applyImport: '匯入',
1238
+ copied: '已複製',
1239
+ importOk: '已匯入',
1240
+ importBad: '短碼無效,已忽略',
1241
+ hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三級 ≥7)',
1088
1242
  },
1089
1243
  en: {
1090
1244
  nav: 'Color theme',
@@ -1117,6 +1271,17 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1117
1271
  sidebar: 'Sidebar surface',
1118
1272
  sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area.',
1119
1273
  lang: 'Language',
1274
+ hc: 'High contrast',
1275
+ custom: 'Custom palette',
1276
+ customHint: 'Takes hue and chroma from three colours; lightness comes from the engine per mode and contrast is still solved.',
1277
+ config: 'Config',
1278
+ configHint: 'Export this machine’s choices, or paste a short code to import. Import overwrites all 8 settings.',
1279
+ copy: 'Copy',
1280
+ applyImport: 'Import',
1281
+ copied: 'Copied',
1282
+ importOk: 'Imported',
1283
+ importBad: 'Invalid code, ignored',
1284
+ hcSub: 'Raises text floors to AAA (body ≥15, secondary ≥9, tertiary ≥7)',
1120
1285
  },
1121
1286
  ja: {
1122
1287
  nav: 'カラーテーマ',
@@ -1149,14 +1314,25 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1149
1314
  sidebar: '左サイドバーの背景',
1150
1315
  sidebarHint: 'サイドバーを会話エリアより明るく(または暗く)して段差を作ります。',
1151
1316
  lang: '言語',
1317
+ hc: 'ハイコントラスト',
1318
+ custom: 'カスタム配色',
1319
+ customHint: '3 色の色相と彩度だけを使います。明度はモードごとにエンジンが決め、コントラストは従来どおり求解します。',
1320
+ config: '設定の共有',
1321
+ configHint: 'この端末の配色設定を書き出す/短いコードを貼り付けて読み込みます(8 項目すべて上書き)。',
1322
+ copy: 'コピー',
1323
+ applyImport: '読み込み',
1324
+ copied: 'コピーしました',
1325
+ importOk: '読み込みました',
1326
+ importBad: '無効なコードです',
1327
+ hcSub: '本文の下限を AAA に引き上げます(本文 ≥15、副次 ≥9、三次 ≥7)',
1152
1328
  },
1153
1329
  }
1154
1330
 
1155
1331
  /** 配色名与各档位的显示名:id 不变,只换文案。 */
1156
1332
  const LABELS = {
1157
1333
  'zh-CN': {
1158
- scheme: { mono: '黑白', warm: '暖砂', indigo: '靛蓝', moss: '苔绿' },
1159
- desc: { mono: '无色调 · 默认', warm: '米白 + 棕调', indigo: '冷白 + 蓝调', moss: '浅灰绿 + 绿调' },
1334
+ scheme: { mono: '黑白', warm: '暖砂', indigo: '靛蓝', moss: '苔绿', custom: '自定义' },
1335
+ desc: { mono: '无色调 · 默认', warm: '米白 + 棕调', indigo: '冷白 + 蓝调', moss: '浅灰绿 + 绿调', custom: '三色派生 · 你的配色' },
1160
1336
  text: { neutral: '静', tinted: '标准', deep: '浓' },
1161
1337
  sidebar: { light: '更浅', match: '同色', deep: '更深' },
1162
1338
  rightbar: { sidebar: '同左栏', canvas: '跟随画布' },
@@ -1164,10 +1340,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1164
1340
  control: { accent: '主题色', mono: '黑白' },
1165
1341
  md: { on: '着色', off: '同正文' },
1166
1342
  lang: { auto: '自动' },
1343
+ hc: { off: '关', on: '开' },
1344
+ custom: { bg: '底色', text: '文字', accent: '强调' },
1167
1345
  },
1168
1346
  'zh-TW': {
1169
- scheme: { mono: '黑白', warm: '暖砂', indigo: '靛藍', moss: '苔綠' },
1170
- desc: { mono: '無色調 · 預設', warm: '米白 + 棕調', indigo: '冷白 + 藍調', moss: '淺灰綠 + 綠調' },
1347
+ scheme: { mono: '黑白', warm: '暖砂', indigo: '靛藍', moss: '苔綠', custom: '自訂' },
1348
+ desc: { mono: '無色調 · 預設', warm: '米白 + 棕調', indigo: '冷白 + 藍調', moss: '淺灰綠 + 綠調', custom: '三色派生 · 你的配色' },
1171
1349
  text: { neutral: '靜', tinted: '標準', deep: '濃' },
1172
1350
  sidebar: { light: '更淺', match: '同色', deep: '更深' },
1173
1351
  rightbar: { sidebar: '同左欄', canvas: '跟隨畫布' },
@@ -1175,10 +1353,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1175
1353
  control: { accent: '主題色', mono: '黑白' },
1176
1354
  md: { on: '著色', off: '同本文' },
1177
1355
  lang: { auto: '自動' },
1356
+ hc: { off: '關', on: '開' },
1357
+ custom: { bg: '底色', text: '文字', accent: '強調' },
1178
1358
  },
1179
1359
  en: {
1180
- scheme: { mono: 'Mono', warm: 'Warm sand', indigo: 'Indigo', moss: 'Moss' },
1181
- desc: { mono: 'Neutral · default', warm: 'Cream + brown', indigo: 'Cool white + blue', moss: 'Grey-green' },
1360
+ scheme: { mono: 'Mono', warm: 'Warm sand', indigo: 'Indigo', moss: 'Moss', custom: 'Custom' },
1361
+ desc: { mono: 'Neutral · default', warm: 'Cream + brown', indigo: 'Cool white + blue', moss: 'Grey-green', custom: 'Derived from your three colours' },
1182
1362
  text: { neutral: 'Quiet', tinted: 'Standard', deep: 'Strong' },
1183
1363
  sidebar: { light: 'Lighter', match: 'Same', deep: 'Deeper' },
1184
1364
  rightbar: { sidebar: 'Match sidebar', canvas: 'Follow canvas' },
@@ -1186,10 +1366,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1186
1366
  control: { accent: 'Accent', mono: 'Mono' },
1187
1367
  md: { on: 'Tinted', off: 'Body colour' },
1188
1368
  lang: { auto: 'Auto' },
1369
+ hc: { off: 'Off', on: 'On' },
1370
+ custom: { bg: 'Surface', text: 'Text', accent: 'Accent' },
1189
1371
  },
1190
1372
  ja: {
1191
- scheme: { mono: 'モノクロ', warm: 'ウォームサンド', indigo: 'インディゴ', moss: 'モスグリーン' },
1192
- desc: { mono: '色なし · 既定', warm: 'クリーム + ブラウン', indigo: 'クールホワイト + ブルー', moss: 'グレーグリーン' },
1373
+ scheme: { mono: 'モノクロ', warm: 'ウォームサンド', indigo: 'インディゴ', moss: 'モスグリーン', custom: 'カスタム' },
1374
+ desc: { mono: '色なし · 既定', warm: 'クリーム + ブラウン', indigo: 'クールホワイト + ブルー', moss: 'グレーグリーン', custom: '3 色から派生' },
1193
1375
  text: { neutral: '控えめ', tinted: '標準', deep: '強め' },
1194
1376
  sidebar: { light: '明るく', match: '同じ', deep: '暗く' },
1195
1377
  rightbar: { sidebar: 'サイドバーに合わせる', canvas: 'キャンバスに従う' },
@@ -1197,6 +1379,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1197
1379
  control: { accent: 'アクセント', mono: 'モノクロ' },
1198
1380
  md: { on: '色づけ', off: '本文と同じ' },
1199
1381
  lang: { auto: '自動' },
1382
+ hc: { off: 'オフ', on: 'オン' },
1383
+ custom: { bg: '背景', text: '本文', accent: 'アクセント' },
1200
1384
  },
1201
1385
  }
1202
1386
 
@@ -1209,7 +1393,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1209
1393
  const scrollMode = React.useSyncExternalStore(props.subscribeScroll, props.getScrollMode)
1210
1394
  const controlMode = React.useSyncExternalStore(props.subscribeControl, props.getControlMode)
1211
1395
  const mdMode = React.useSyncExternalStore(props.subscribeMd, props.getMdMode)
1396
+ const hcMode = React.useSyncExternalStore(props.subscribeHc, props.getHcMode)
1212
1397
  const health = React.useSyncExternalStore(props.subscribeHealth, props.getHealth)
1398
+ const [importText, setImportText] = React.useState('')
1399
+ const [importState, setImportState] = React.useState(null)
1213
1400
  const [advanced, setAdvanced] = React.useState(false)
1214
1401
  const locale = React.useSyncExternalStore(props.subscribeLocale, props.getLocale)
1215
1402
  const langPref = React.useSyncExternalStore(props.subscribeLocale, props.getLangPref)
@@ -1293,8 +1480,66 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1293
1480
  advanced && h(
1294
1481
  'div',
1295
1482
  { className: 'dsh-tp-adv' },
1483
+ current === 'custom' && h(
1484
+ 'div',
1485
+ { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
1486
+ h('div', { style: { fontSize: 14, lineHeight: '22px' } }, txt.custom),
1487
+ h('div', { style: { display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' } },
1488
+ ['bg', 'text', 'accent'].map((field) => h('label', {
1489
+ key: field,
1490
+ style: { display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12.5, color: 'var(--dsw-alias-label-secondary)' },
1491
+ },
1492
+ h('input', {
1493
+ type: 'color',
1494
+ className: 'dsh-tp-color',
1495
+ value: props.getCustomColors()[field],
1496
+ onChange: (e) => props.setCustomColor(field, e.target.value),
1497
+ }),
1498
+ (lb.custom && lb.custom[field]) || field))),
1499
+ h('p', { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' } }, txt.customHint),
1500
+ ),
1296
1501
  row(txt.lang, txt.langSub, seg(LANG_OPTIONS, langPref, props.setLangPref, lb.lang)),
1502
+ h(
1503
+ 'div',
1504
+ { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
1505
+ h('div', { style: { fontSize: 14, lineHeight: '22px' } }, txt.config),
1506
+ h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' } },
1507
+ h('input', {
1508
+ className: 'dsh-tp-input',
1509
+ readOnly: true,
1510
+ value: props.getConfigCode(),
1511
+ onFocus: (e) => { try { e.target.select() } catch (err) { /* 忽略 */ } },
1512
+ 'aria-label': txt.config,
1513
+ }),
1514
+ h('button', {
1515
+ type: 'button',
1516
+ className: 'dsh-tp-mini',
1517
+ onClick: () => {
1518
+ const code = props.getConfigCode()
1519
+ try {
1520
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(code)
1521
+ } catch (err) { /* 剪贴板不可用时靠手选 */ }
1522
+ setImportState('copied')
1523
+ },
1524
+ }, txt.copy)),
1525
+ h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' } },
1526
+ h('input', {
1527
+ className: 'dsh-tp-input',
1528
+ placeholder: 'dshs1.…',
1529
+ value: importText,
1530
+ onChange: (e) => { setImportText(e.target.value); setImportState(null) },
1531
+ }),
1532
+ h('button', {
1533
+ type: 'button',
1534
+ className: 'dsh-tp-mini',
1535
+ onClick: () => setImportState(props.applyConfigCode(importText) ? 'ok' : 'bad'),
1536
+ }, txt.applyImport),
1537
+ importState && h('span', { className: 'dsh-tp-note' },
1538
+ importState === 'ok' ? txt.importOk : importState === 'copied' ? txt.copied : txt.importBad)),
1539
+ h('p', { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' } }, txt.configHint),
1540
+ ),
1297
1541
  row(txt.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode, lb.md)),
1542
+ row(txt.hc, txt.hcSub, seg(HC_MODES, hcMode, props.setHcMode, lb.hc)),
1298
1543
  row(txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode, lb.scroll)),
1299
1544
  row(txt.control, txt.controlSub, seg(CONTROL_MODES, controlMode, props.setControlMode, lb.control)),
1300
1545
  ),
@@ -1309,6 +1554,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1309
1554
  */
1310
1555
  const DEV_OPT_IN = (() => {
1311
1556
  try {
1557
+ // 两种开启方式:URL 带 ?dshThemeDev=1,或页面脚本在插件加载前设好
1558
+ // window.__DSH_THEME_PALETTE_DEV_REQUEST__ = true(CDP 用 Page.addScriptToEvaluateOnNewDocument 注入,
1559
+ // 这是 tools/shoot-desktop.mjs 的唯一入口 —— 之前缺这个入口,B 层回归实际上跑不起来)
1560
+ if (typeof window !== 'undefined' && window.__DSH_THEME_PALETTE_DEV_REQUEST__ === true) return true
1312
1561
  const search = typeof location !== 'undefined' && location ? String(location.search || '') : ''
1313
1562
  return /(?:^|[?&])dshThemeDev=1(?:&|$)/.test(search)
1314
1563
  } catch (err) { return false }
@@ -1335,6 +1584,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1335
1584
  let scrollMode = readScrollStored()
1336
1585
  let controlMode = readControlStored()
1337
1586
  let mdMode = readMdStored()
1587
+ let hcMode = readHcStored()
1588
+ const getConfigCode = () => encodeConfig({ palette: current, sidebar: sidebarMode, text: textMode, rightbar: rightbarMode, scroll: scrollMode, control: controlMode, md: mdMode, hc: hcMode, lang: langPref })
1338
1589
  let langPref = readLangPref()
1339
1590
  const localeListeners = new Set()
1340
1591
  const currentLocale = () => pickLocale(langPref)
@@ -1346,6 +1597,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1346
1597
  const scrollListeners = new Set()
1347
1598
  const controlListeners = new Set()
1348
1599
  const mdListeners = new Set()
1600
+ const hcListeners = new Set()
1349
1601
 
1350
1602
  const getCurrent = () => current
1351
1603
  const getSidebarMode = () => sidebarMode
@@ -1354,6 +1606,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1354
1606
  const getScrollMode = () => scrollMode
1355
1607
  const getControlMode = () => controlMode
1356
1608
  const getMdMode = () => mdMode
1609
+ const getHcMode = () => hcMode
1357
1610
  const subscribe = (fn) => {
1358
1611
  listeners.add(fn)
1359
1612
  return () => { listeners.delete(fn) }
@@ -1389,6 +1642,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1389
1642
  const emitScroll = () => { for (const fn of scrollListeners) { try { fn() } catch (err) { /* 同理 */ } } }
1390
1643
  const emitControl = () => { for (const fn of controlListeners) { try { fn() } catch (err) { /* 同理 */ } } }
1391
1644
  const emitMd = () => { for (const fn of mdListeners) { try { fn() } catch (err) { /* 同理 */ } } }
1645
+ const subscribeHc = (fn) => {
1646
+ hcListeners.add(fn)
1647
+ return () => { hcListeners.delete(fn) }
1648
+ }
1649
+ const emitHc = () => { for (const fn of hcListeners) { try { fn() } catch (err) { /* 同理 */ } } }
1392
1650
 
1393
1651
  let disposeLayer = () => {}
1394
1652
  /** 按当前配色 + 侧栏档位重建覆盖层(换配色与换侧栏档位走同一条路)。 */
@@ -1400,7 +1658,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1400
1658
 
1401
1659
  const applyAll = (reason) => {
1402
1660
  const scheme = SCHEMES.find((s) => s.id === current) || SCHEMES[0]
1403
- const tokens = buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode)
1661
+ const tokens = buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode, hcMode === 'on')
1404
1662
  let next = () => {}
1405
1663
  if (tokens !== undefined) {
1406
1664
  try {
@@ -1535,6 +1793,48 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1535
1793
  applyAll()
1536
1794
  emitMd()
1537
1795
  }
1796
+ /** 导入:整份校验通过才落地;返回 true/false 供 UI 显示结果 */
1797
+ const applyConfigCode = (code) => {
1798
+ const cfg = decodeConfig(code)
1799
+ if (!cfg) return false
1800
+ current = cfg.palette
1801
+ sidebarMode = cfg.sidebar
1802
+ textMode = cfg.text
1803
+ rightbarMode = cfg.rightbar
1804
+ scrollMode = cfg.scroll
1805
+ controlMode = cfg.control
1806
+ mdMode = cfg.md
1807
+ hcMode = cfg.hc
1808
+ langPref = cfg.lang
1809
+ writeStored(current)
1810
+ writeSidebarStored(sidebarMode)
1811
+ writeTextStored(textMode)
1812
+ writeRightbarStored(rightbarMode)
1813
+ writeScrollStored(scrollMode)
1814
+ writeControlStored(controlMode)
1815
+ writeMdStored(mdMode)
1816
+ writeHcStored(hcMode)
1817
+ writeLangPref(langPref)
1818
+ applyAll('import')
1819
+ emit(); emitSidebar(); emitText(); emitRightbar(); emitScroll(); emitControl(); emitMd(); emitHc(); emitLocale()
1820
+ return true
1821
+ }
1822
+ const getCustomColors = () => readCustom()
1823
+ const setCustomColor = (field, hex) => {
1824
+ if (CUSTOM_FIELDS.indexOf(field) === -1) return
1825
+ if (typeof hex !== 'string' || !HEX_RE.test(hex)) return
1826
+ const next = Object.assign(readCustom(), { [field]: hex })
1827
+ writeCustom(next)
1828
+ applyAll('custom:' + field)
1829
+ emit()
1830
+ }
1831
+ const setHcMode = (id) => {
1832
+ if (!HC_MODES.some((m) => m.id === id)) return
1833
+ hcMode = id
1834
+ writeHcStored(id)
1835
+ applyAll()
1836
+ emitHc()
1837
+ }
1538
1838
  const getLangPref = () => langPref
1539
1839
  const getLocale = () => currentLocale()
1540
1840
  const subscribeLocale = (fn) => {
@@ -1570,6 +1870,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1570
1870
  setControlMode,
1571
1871
  getPreference: () => theme.getTheme().preference,
1572
1872
  setLangPref,
1873
+ setHcMode,
1874
+ getHcMode,
1573
1875
  locales: LOCALES,
1574
1876
  // 和其它 setter 一样过白名单:任意字符串会原样进宿主主题状态并被持久化
1575
1877
  setTheme: (id) => { if (THEME_IDS.indexOf(id) !== -1) theme.setTheme(id) },
@@ -1653,6 +1955,13 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1653
1955
  getMdMode,
1654
1956
  subscribeMd,
1655
1957
  setMdMode,
1958
+ getHcMode,
1959
+ subscribeHc,
1960
+ setHcMode,
1961
+ getConfigCode,
1962
+ applyConfigCode,
1963
+ getCustomColors,
1964
+ setCustomColor,
1656
1965
  getLangPref,
1657
1966
  getLocale,
1658
1967
  subscribeLocale,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.4.4",
3
+ "version": "1.5.1",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -64,7 +64,8 @@
64
64
  }
65
65
  },
66
66
  "profiles": [
67
- "desktop"
67
+ "desktop",
68
+ "web"
68
69
  ]
69
70
  }
70
71
  },