dsh-theme-palette 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,48 @@
13
13
  复核宿主源码发现它带 `includePrerelease`,结论相反。现在这条判断固化进脚本,不再靠手算
14
14
  - 该改动只影响本地发版工具,**不进 npm 包**(`files` 不含 `tools/`),故不升版本号
15
15
 
16
+ ### 1.6.0
17
+ 视觉与可访问性整改(依据 2026-10-22 第三方 UI 视觉验收审计,60/100):
18
+
19
+ - **A2 配置块重做**:改用 `.dsh-tp-field` 容器(与普通行同节奏),导出/导入各占一行,
20
+ 反馈文案就地显示(复制→复制按钮旁,导入→导入按钮旁),导出框加 `text-overflow:ellipsis`
21
+ - **A3 复制不再谎报**:`await navigator.clipboard.writeText()`,失败或无 API 时显示
22
+ 「复制失败,请手选后 Ctrl+C」,不再无条件报成功
23
+ - **A4 布局从视口断点改容器驱动**:卡片 `repeat(auto-fit,minmax(196px,1fr))`、色块 `minmax(86px,1fr)`、
24
+ 行与分段控件 `flex-wrap`;日语长标签改短(右栏「サイドバー / キャンバス」、滚动条「無彩 / 追従 / 強調」)。
25
+ 实测 4 语 × 内容宽 564/336/216:**越界元素 0**(改前日语 216px 溢出 61px)
26
+ - **A5 语言属性**:`<section lang>` 跟随手选语言,`auto` 时回落宿主 `documentElement.lang`
27
+ - **A6 分段控件有名字**:`role="radiogroup"` + `aria-labelledby` 指向行标题,选项改
28
+ `role="radio" aria-checked`(8 组 22 个按钮)
29
+ - **A7 反馈可播报**:复制/导入/自愈状态挂 `role="status" aria-live="polite"`,错误用 `alert`;
30
+ 导入框补 `aria-label`
31
+ - **A8 自定义面板层级**:面板底改 `bg-layer-1`(按钮不再与面板同底 1.00:1)、说明文字提到
32
+ `label-secondary` 12px;色值从只读 `<code>` 改为**可输入 hex**(回车/失焦生效,非法值忽略)
33
+ - **A9 缩略图跟随侧栏档位**:`cardColors()` 接收 `sidebarMode`,不再写死默认档
34
+ - **A11** 黑白配色下「滚动条」「开关」置灰禁用(点了也不会有变化)
35
+ - **A12** 卡片与分段按钮 `:focus-visible{outline-offset:2px}`,焦点环不再与选中边框贴死
36
+ - **A13** 加 `prefers-reduced-motion` 降级
37
+ - **A14** `docs/DESIGN.md` 补「当前标定值」表(旧推演内容保留,注明以 `client.js` 为准)
38
+ - **A15** 删掉 4 语共 36 条从未渲染的文案键与未被调用的 `swatches()`
39
+ - **A10 证据链**:README 首图换成当前版本(5 张卡)的**静态渲染**,按真机内容宽 564 px 出图
40
+ (浅色 / 深色 / 自定义面板三张,`--scale 2` 即 1240 px 宽),并在五语 README 里注明「静态渲染」;
41
+ `screenshots.json` 同步为这三张
42
+ - 工具:`tools/preview-settings.mjs` 新增 `--width`(默认按真机 564px 内容宽)与 `--adv`(默认折叠),
43
+ token 跟随 `--palette`;新增 `tools/measure-preview.mjs`(量越界/行高/关键计算色,非 0 越界退出 1)
44
+
45
+ ### 1.5.3
46
+ 修:选中「自定义」后滚动条不跟随取色。
47
+
48
+ - **根因**:`scrollbarTokens()` 收到的是 `SCHEMES` 里的**占位** scheme 对象
49
+ (`accentHue` 恒为 60),而真正按三色派生出的参数只在 `rampsFor()` 内部解引用。
50
+ 于是画布/强调色都对,唯独滚动条永远停在暖橙。
51
+ - **修法**:`buildTokens()` 开头统一解引用一次(`params = scheme.custom ? customScheme(readCustom()) : scheme`),
52
+ 之后所有读 `scheme.*` 的地方改用 `params`;`scrollbarTokens(ramps, params, mode)` 也改传解析后的对象。
53
+ - **防复发**:新增 `tools/check-custom.mjs` —— 断言三种强调色必须产出三种滚动条颜色,
54
+ 且三组自选色的正文/次文/三级对比度仍在限内(现在输出
55
+ `#2f6fd0→#bfd6f9 #a9683e→#f2cbb3 #4f7a42→#c2ddbb`)。
56
+ - 顺带:`tools/preview-settings.mjs` 支持 `PREVIEW_ACCENT`,可预览任意强调色的自定义面板。
57
+
16
58
  ### 1.5.2
17
59
  设置页交互重排(用户反馈:自定义不该埋在「更多选项」里、取色看不到即时效果):
18
60
 
package/README-es.md CHANGED
@@ -6,6 +6,8 @@ Paleta de tema para DeepSeek Harness (web y escritorio): **4 paletas × claro/os
6
6
 
7
7
  ![Página de ajustes](assets/screenshot-settings-light.png)
8
8
 
9
+ *Render estático con el ancho real del panel (564 px de contenido).*
10
+
9
11
  ## Compatibility
10
12
 
11
13
  | Superficie | Estado |
package/README-hi.md CHANGED
@@ -6,6 +6,8 @@ DeepSeek Harness (वेब और डेस्कटॉप) के लिए
6
6
 
7
7
  ![सेटिंग्स पेज](assets/screenshot-settings-light.png)
8
8
 
9
+ *पैनल की वास्तविक चौड़ाई (564px सामग्री) पर स्थिर रेंडर।*
10
+
9
11
  ## Compatibility
10
12
 
11
13
  | सतह | स्थिति |
package/README-pt.md CHANGED
@@ -6,6 +6,8 @@ Paleta de tema para o DeepSeek Harness (web e desktop): **4 paletas × claro/esc
6
6
 
7
7
  ![Página de configuração](assets/screenshot-settings-light.png)
8
8
 
9
+ *Render estático na largura real do painel (564 px de conteúdo).*
10
+
9
11
  ## Compatibility
10
12
 
11
13
  | Superfície | Estado |
package/README-zh.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  DeepSeek Harness(Web / 桌面)的主题配色插件:**4 套配色 × 明暗两模式**,两个轴互相独立。
6
6
 
7
- ![设置页:四套配色、强度与面板表面](assets/screenshot-settings-light.png)
7
+ ![设置页:五套配色、强度与面板表面](assets/screenshot-settings-light.png)
8
+
9
+ *上图按真机面板宽度(内容区 564 px)静态渲染。*
8
10
 
9
11
  ## Compatibility
10
12
 
package/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  Theme palette for DeepSeek Harness (web & desktop): **4 palettes × light/dark**, two independent axes.
6
6
 
7
- ![Settings page: four palettes, strength and panel surfaces](assets/screenshot-settings-light.png)
7
+ ![Settings page: five palettes, strength and panel surfaces](assets/screenshot-settings-light.png)
8
+
9
+ *Static render of the settings page at the real panel width (564 px content).*
8
10
 
9
11
  ## Compatibility
10
12
 
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.5.2'
30
+ const PLUGIN_VERSION = '1.6.0'
31
31
  const STORE_KEY = 'dsh-theme-palette.v1'
32
32
  const OVERRIDE_SOURCE = 'dsh-theme-palette'
33
33
 
@@ -703,9 +703,12 @@ window.__ModuleLoader__.load({
703
703
 
704
704
  function buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode, hcMode) {
705
705
  const hc = hcMode === true
706
- const ramps = rampsFor(scheme, textMode || DEFAULT_TEXT, hc)
706
+ // 自定义配色必须先解引用:ramps 内部会派生参数,但 accent / scroll / control
707
+ // 这些直接读 params.accentHue、params.hue 的地方若拿到占位值,就会"不跟取色"
708
+ const params = scheme && scheme.custom === true ? customScheme(readCustom()) : scheme
709
+ const ramps = rampsFor(params, textMode || DEFAULT_TEXT, hc)
707
710
  const pairs = {}
708
- if (!scheme.tinted) {
711
+ if (!params.tinted) {
709
712
  // 黑白:只输出「被求解改动过」的那几步 + 派生链接,不整条覆盖(保持宿主原貌)
710
713
  for (const step of ['400', '600', '700', '1000']) {
711
714
  pairs['--dsw-static-neutral-bluish-' + step] = { light: ramps.bluish[step], dark: ramps.bluish[step] }
@@ -719,7 +722,7 @@ window.__ModuleLoader__.load({
719
722
  pairs['--dsw-alias-button-primary-fill'] = { light: monoBtn, dark: ramps.bluish['1000'] }
720
723
  }
721
724
  // 其它配色:整条色阶 + 字面量别名覆盖。
722
- if (scheme.tinted) {
725
+ if (params.tinted) {
723
726
  for (const [family, table] of Object.entries(ramps)) {
724
727
  for (const [step, value] of Object.entries(table)) {
725
728
  pairs['--dsw-static-' + (family === 'bluish' ? 'neutral-bluish-' : family + '-') + step] = {
@@ -731,13 +734,13 @@ window.__ModuleLoader__.load({
731
734
  const aliases = aliasOverrides(ramps, hc)
732
735
  for (const [name, pair] of Object.entries(aliases)) pairs[name] = pair
733
736
  }
734
- const sidebar = sidebarTokens(ramps, sidebarMode, scheme.tinted)
737
+ const sidebar = sidebarTokens(ramps, sidebarMode, params.tinted)
735
738
  for (const [name, pair] of Object.entries(sidebar)) pairs[name] = pair
736
739
  // 右栏底色只给一个间接层:同左栏时直接引用左栏 token,跟随画布时引用画布 token。
737
740
  // 主按钮:浅色下宿主把 button-primary-fill 指到 state-error-primary(红),
738
741
  // 于是页面上有「红主按钮 + 强调色」两个主色。改成强调色,并压暗到按钮文字 ≥4.5。
739
742
  // 黑白配色在上面已经单独设过,这里跳过(它要按「更浅」档的画布兜底)。
740
- if (scheme.tinted) {
743
+ if (params.tinted) {
741
744
  pairs['--dsw-alias-button-primary-fill'] = {
742
745
  light: darkenUntil(ramps.deepseek['500'], ramps.bluish['00'], 4.5),
743
746
  dark: ramps.deepseek['400'],
@@ -756,7 +759,7 @@ window.__ModuleLoader__.load({
756
759
  : 'var(--dsw-specific-sidebar-fill)'
757
760
  pairs['--dshtp-rightbar-fill'] = { light: rightbarFill, dark: rightbarFill }
758
761
  // Markdown 强调色:标题 = 正文色混 40% 强调色;加粗 = 混 20%(比标题弱一档)
759
- if (scheme.tinted) {
762
+ if (params.tinted) {
760
763
  if (mdMode === 'off') {
761
764
  const inherit = { light: 'inherit', dark: 'inherit' }
762
765
  pairs['--dshtp-md-heading'] = inherit
@@ -790,7 +793,7 @@ window.__ModuleLoader__.load({
790
793
  light: mixHex(ramps.deepseek['500'], ramps.bluish['50'], 0.05),
791
794
  dark: mixHex(ramps.deepseek['400'], ramps.bluish['900'], 0.07),
792
795
  }
793
- const scroll = scrollbarTokens(ramps, scheme, scrollMode)
796
+ const scroll = scrollbarTokens(ramps, params, scrollMode)
794
797
  for (const [name, pair] of Object.entries(scroll)) pairs[name] = pair
795
798
  // 开关/勾选/单选的共用支点:brand-primary 本来是近黑/近白,改成强调色。
796
799
  if (controlMode === 'accent') {
@@ -798,22 +801,16 @@ window.__ModuleLoader__.load({
798
801
  }
799
802
  // 黑白配色的浅色画布本身就是纯白,「更浅」无处可提 —— 改成把会话区压一档浅灰,
800
803
  // 侧栏保持纯白,同样得到区块层次(这是唯一需要动画布的情形)。
801
- if (!scheme.tinted && sidebarMode === 'light') {
804
+ if (!params.tinted && sidebarMode === 'light') {
802
805
  pairs['--dsw-static-neutral-bluish-00'] = { light: '#fafafa', dark: BASE.bluish['00'] }
803
806
  }
804
807
  return pairs
805
808
  }
806
809
 
807
- /* ================= 卡片预览色 ================= */
808
- function swatches(scheme, textMode) {
809
- if (!scheme.tinted) return ['#ffffff', '#e5e5e5', '#0f1115']
810
- const r = rampsFor(scheme, textMode || DEFAULT_TEXT)
811
- return [r.bluish['00'], r.bluish['100'], r.deepseek['500']]
812
- }
813
-
810
+
814
811
 
815
812
  /** 卡片缩略图用的一小组颜色(画布/侧栏/文字/强调),按当前强度档取。 */
816
- function cardColors(scheme, textMode) {
813
+ function cardColors(scheme, textMode, sidebarMode) {
817
814
  const r = rampsFor(scheme, textMode || DEFAULT_TEXT)
818
815
  if (!r) {
819
816
  return {
@@ -821,7 +818,7 @@ window.__ModuleLoader__.load({
821
818
  dark: { canvas: '#151517', side: '#1b1b1c', text: '#f9fafb', accent: '#7aaaff' },
822
819
  }
823
820
  }
824
- const side = sidebarTokens(r, DEFAULT_SIDEBAR, scheme.tinted)
821
+ const side = sidebarTokens(r, sidebarMode || DEFAULT_SIDEBAR, scheme.tinted)
825
822
  return {
826
823
  light: {
827
824
  canvas: r.bluish['00'],
@@ -865,10 +862,10 @@ window.__ModuleLoader__.load({
865
862
  .dsh-tp-head h2{margin:0;font-size:18px;font-weight:600;letter-spacing:.2px}
866
863
  .dsh-tp-badge{margin-left:auto;font:11px/1.7 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-tertiary);border:.5px solid var(--dsw-alias-border-l4);border-radius:999px;padding:0 8px}
867
864
  .dsh-tp-sub{margin:-6px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
868
- .dsh-tp-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
869
- @media (max-width:620px){.dsh-tp-grid{grid-template-columns:1fr}}
870
- .dsh-tp-card{position:relative;display:flex;flex-direction:column;gap:8px;text-align:left;font:inherit;cursor:pointer;padding:9px;border-radius:14px;border:.5px solid var(--dsw-alias-border-l4);background:transparent;transition:background .12s ease,border-color .12s ease}
865
+ .dsh-tp-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(196px,1fr));gap:10px}
866
+ .dsh-tp-card{position:relative;display:flex;flex-direction:column;gap:8px;text-align:left;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;padding:9px;border-radius:14px;border:.5px solid var(--dsw-alias-border-l4);background:transparent;transition:background .12s ease,border-color .12s ease}
871
867
  .dsh-tp-card:hover{background:var(--dsw-alias-interactive-bg-hover)}
868
+ .dsh-tp-card:focus-visible{outline-offset:2px}
872
869
  .dsh-tp-card.is-on{background:var(--dshtp-card-picked,var(--dsw-alias-bg-module-platform));border-color:var(--t-accent-l)}
873
870
  body[data-ds-dark-theme] .dsh-tp-card.is-on{border-color:var(--t-accent-d)}
874
871
  .dsh-tp-thumb{display:flex;height:56px;border-radius:9px;overflow:hidden;border:.5px solid rgba(128,128,128,.28)}
@@ -882,20 +879,21 @@ body[data-ds-dark-theme] .dsh-tp-thumb-main b{background:var(--t-text-d)}
882
879
  .dsh-tp-thumb-main b:nth-of-type(3){width:44%;background:var(--t-accent-l);opacity:.9}
883
880
  body[data-ds-dark-theme] .dsh-tp-thumb-main b:nth-of-type(3){background:var(--t-accent-d)}
884
881
  .dsh-tp-card-meta{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
885
- .dsh-tp-card-name{font-size:13.5px;font-weight:500}
886
- .dsh-tp-card-desc{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}
882
+ .dsh-tp-card-name{font-size:14px;font-weight:500}
883
+ .dsh-tp-card-desc{font-size:12px;color:var(--dsw-alias-label-tertiary)}
887
884
  .dsh-tp-check{position:absolute;top:7px;right:7px;width:16px;height:16px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;line-height:1;background:var(--t-accent-l);color:var(--t-canvas-l);opacity:0;transition:opacity .12s ease}
888
885
  body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--t-canvas-d)}
889
886
  .dsh-tp-card.is-on .dsh-tp-check{opacity:1}
890
- .dsh-tp-row{display:flex;align-items:center;gap:12px;padding:11px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
887
+ .dsh-tp-row{display:flex;align-items:center;gap:12px;padding:11px 0;border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap}
891
888
  .dsh-tp-row-label{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
892
889
  .dsh-tp-row-label b{font-size:13.5px;font-weight:500}
893
890
  .dsh-tp-row-label span{font-size:11.5px;color:var(--dsw-alias-label-tertiary);line-height:16px}
894
- .dsh-tp-seg{display:inline-flex;gap:2px;padding:2px;border-radius:10px;background:var(--dsw-alias-interactive-bg-hover);flex:none}
891
+ .dsh-tp-seg{display:inline-flex;flex-wrap:wrap;gap:2px;padding:2px;border-radius:10px;background:var(--dsw-alias-interactive-bg-hover);flex:none;max-width:100%}
895
892
  .dsh-tp-seg button{font:inherit;font-size:12.5px;line-height:20px;cursor:pointer;border:0;border-radius:8px;padding:5px 12px;background:transparent;color:var(--dsw-alias-label-secondary);white-space:nowrap;transition:background .12s,color .12s}
896
893
  .dsh-tp-seg button:hover{color:var(--dsw-alias-label-primary)}
897
894
  .dsh-tp-seg button.is-on{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.10)}
898
- @media (max-width:520px){.dsh-tp-row{flex-direction:column;align-items:stretch;gap:8px}.dsh-tp-seg{justify-content:space-between}.dsh-tp-seg button{flex:1;text-align:center}}
895
+ .dsh-tp-seg button:focus-visible{outline-offset:2px}
896
+ .dsh-tp-seg button[disabled]{opacity:.45;cursor:default}
899
897
  .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}
900
898
  .dsh-tp-more:hover{color:var(--dsw-alias-label-primary)}
901
899
  .dsh-tp-adv{display:flex;flex-direction:column}
@@ -903,21 +901,27 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
903
901
  .dsh-tp-input::placeholder{color:var(--dsw-alias-label-tertiary)}
904
902
  .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
903
  .dsh-tp-mini:hover{background:var(--dsw-alias-bg-layer-1)}
906
- .dsh-tp-custom{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:14px;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-interactive-bg-hover)}
904
+ .dsh-tp-custom{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:14px;border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1)}
907
905
  .dsh-tp-custom-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
908
906
  .dsh-tp-custom-head b{font-size:13.5px;font-weight:500}
909
- .dsh-tp-swatches{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
910
- @media (max-width:520px){.dsh-tp-swatches{grid-template-columns:1fr}}
907
+ .dsh-tp-swatches{display:grid;grid-template-columns:repeat(auto-fit,minmax(86px,1fr));gap:10px}
911
908
  .dsh-tp-swatch{display:flex;flex-direction:column;gap:6px;cursor:pointer}
912
909
  .dsh-tp-color{width:100%;height:44px;padding:0;border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;background:transparent;cursor:pointer}
913
910
  .dsh-tp-color::-webkit-color-swatch-wrapper{padding:3px}
914
911
  .dsh-tp-color::-webkit-color-swatch{border:none;border-radius:8px}
915
912
  .dsh-tp-swatch-name{font-size:12px;color:var(--dsw-alias-label-secondary)}
916
913
  .dsh-tp-hex{font:11.5px/1 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-tertiary)}
917
- .dsh-tp-custom-hint{margin:0;font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
914
+ .dsh-tp-hexinput{width:100%;box-sizing:border-box;font:11.5px/16px ui-monospace,Consolas,monospace;padding:3px 6px;border-radius:6px;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary)}
915
+ .dsh-tp-hexinput:focus-visible{outline-offset:1px}
916
+ .dsh-tp-custom-hint{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
918
917
  .dsh-tp-note{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}
919
918
  .dsh-tp-status{margin:0;font-size:11.5px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
920
919
  .dsh-tp-status.is-bad{color:var(--dsw-alias-state-error-primary)}
920
+ .dsh-tp-field{display:flex;flex-direction:column;gap:8px;padding:11px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
921
+ .dsh-tp-field-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
922
+ .dsh-tp-field-title{font-size:13.5px;font-weight:500}
923
+ .dsh-tp-input{text-overflow:ellipsis}
924
+ @media (prefers-reduced-motion:reduce){.dsh-tp-card,.dsh-tp-seg button,.dsh-tp-more,.dsh-tp-check,.dsh-tp-mini{transition:none}}
921
925
  `
922
926
 
923
927
  function installRightbarStyle() {
@@ -1169,14 +1173,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1169
1173
  nav: '主题色',
1170
1174
  hint: '配色与明暗模式独立;切换后立即生效并保存在 localStorage。',
1171
1175
  md: 'Markdown 强调',
1172
- mdHint: '标题与加粗用「正文色混强调色」,比正文更有色;关掉则与正文同色。',
1173
1176
  control: '开关 / 勾选',
1174
- controlHint: '宿主的开关、复选框、单选都压在近黑/近白上;开启后改用强调色。',
1175
1177
  scroll: '滚动条',
1176
- scrollHint: '默认中性灰;「跟随」上配色色相,「强调」用强调色。明暗关系不变。',
1177
1178
  scrollHintMono: '当前是黑白配色,没有色相可跟随,滚动条保持中性灰。',
1178
1179
  rightbar: '右侧栏底色',
1179
- rightbarHint: '右栏本来没有自己的背景,露的是会话区画布色。',
1180
1180
  advanced: '更多选项',
1181
1181
  textSub: '正文与强调的着色程度',
1182
1182
  sidebarSub: '相对会话区的层次',
@@ -1185,16 +1185,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1185
1185
  scrollSub: '滚动条与滑轨的色相',
1186
1186
  controlSub: '开关与复选框是否跟强调色',
1187
1187
  langSub: '自动跟随宿主界面语言',
1188
- surface: '面板表面',
1189
- surfaceHint: '决定侧栏与右栏相对会话区是「浮起」还是「凹陷」。',
1190
- ok: '✓ 配色已生效',
1191
1188
  bad: '⚠ 部分 token 未生效(宿主可能改名),请查看控制台',
1192
1189
  healed: '配色层被宿主丢过一次,已自动恢复 ',
1193
1190
  healedSuffix: ' 次',
1194
1191
  text: '强度',
1195
- textHint: '让正文/次级文字带配色色味;三档都满足 AA。',
1196
1192
  sidebar: '左侧栏底色',
1197
- sidebarHint: '让侧栏比会话区更浅(或更深),做出区块层次。',
1198
1193
  lang: '语言',
1199
1194
  hc: '高对比',
1200
1195
  custom: '自定义配色',
@@ -1205,6 +1200,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1205
1200
  copy: '复制',
1206
1201
  applyImport: '导入',
1207
1202
  copied: '已复制',
1203
+ copyFail: '复制失败,请手选后 Ctrl+C',
1208
1204
  importOk: '已导入',
1209
1205
  importBad: '短码无效,已忽略',
1210
1206
  hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三级 ≥7)',
@@ -1213,14 +1209,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1213
1209
  nav: '主題色',
1214
1210
  hint: '配色與明暗模式獨立;切換後立即生效並存在 localStorage。',
1215
1211
  md: 'Markdown 強調',
1216
- mdHint: '標題與粗體用「本文色混強調色」,比本文更有色;關掉則與本文同色。',
1217
1212
  control: '開關 / 勾選',
1218
- controlHint: '宿主開關、核取方塊、單選都用近黑/近白;開啟後改用強調色。',
1219
1213
  scroll: '捲軸',
1220
- scrollHint: '預設中性灰;「跟隨」上配色色相,「強調」用強調色。明暗關係不變。',
1221
1214
  scrollHintMono: '目前是黑白配色,沒有色相可跟隨,捲軸保持中性灰。',
1222
1215
  rightbar: '右側欄底色',
1223
- rightbarHint: '右欄本來沒有自己的背景,露的是會話區畫布色。',
1224
1216
  advanced: '更多選項',
1225
1217
  textSub: '本文與強調的著色程度',
1226
1218
  sidebarSub: '相對會話區的層次',
@@ -1229,16 +1221,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1229
1221
  scrollSub: '捲軸與軌道的色相',
1230
1222
  controlSub: '開關與核取方塊是否跟強調色',
1231
1223
  langSub: '自動跟隨宿主介面語言',
1232
- surface: '面板表面',
1233
- surfaceHint: '決定側欄與右欄相對會話區是「浮起」還是「凹陷」。',
1234
- ok: '✓ 配色已生效',
1235
1224
  bad: '⚠ 部分 token 未生效(宿主可能改名),請看主控台',
1236
1225
  healed: '配色層被宿主丟棄過一次,已自動恢復 ',
1237
1226
  healedSuffix: ' 次',
1238
1227
  text: '強度',
1239
- textHint: '讓本文/次級文字帶配色色味;三檔都符合 AA。',
1240
1228
  sidebar: '左側欄底色',
1241
- sidebarHint: '讓側欄比會話區更淺(或更深),做出區塊層次。',
1242
1229
  lang: '語言',
1243
1230
  hc: '高對比',
1244
1231
  custom: '自訂配色',
@@ -1249,6 +1236,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1249
1236
  copy: '複製',
1250
1237
  applyImport: '匯入',
1251
1238
  copied: '已複製',
1239
+ copyFail: '複製失敗,請手選後 Ctrl+C',
1252
1240
  importOk: '已匯入',
1253
1241
  importBad: '短碼無效,已忽略',
1254
1242
  hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三級 ≥7)',
@@ -1257,14 +1245,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1257
1245
  nav: 'Color theme',
1258
1246
  hint: 'Independent of light/dark mode. Applies immediately and is saved to localStorage.',
1259
1247
  md: 'Markdown emphasis',
1260
- mdHint: 'Headings and bold mix the accent into body colour; off keeps them identical to body text.',
1261
1248
  control: 'Switches / checks',
1262
- controlHint: 'Switches, checkboxes and radios use near-black/white in the host. Turn this on to use the accent colour.',
1263
1249
  scroll: 'Scrollbars',
1264
- scrollHint: 'Neutral grey by default. Tint follows the palette hue; Accent uses the accent colour. Lightness is unchanged.',
1265
1250
  scrollHintMono: 'Monochrome has no hue to follow; scrollbars stay neutral.',
1266
1251
  rightbar: 'Right panel surface',
1267
- rightbarHint: 'The right panel has no fill of its own; it shows the canvas.',
1268
1252
  advanced: 'More options',
1269
1253
  textSub: 'How strongly text follows the palette',
1270
1254
  sidebarSub: 'Layer relative to the conversation',
@@ -1273,16 +1257,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1273
1257
  scrollSub: 'Hue of the scrollbar',
1274
1258
  controlSub: 'Switches and checkboxes',
1275
1259
  langSub: 'Follow the host interface language',
1276
- surface: 'Panel surfaces',
1277
- surfaceHint: 'Whether the sidebars sit above or below the conversation area.',
1278
- ok: '✓ Palette applied',
1279
1260
  bad: '⚠ Some tokens did not apply (host may have renamed them); check the console',
1280
1261
  healed: 'Palette layer was dropped by the host; restored ',
1281
1262
  healedSuffix: 'x',
1282
1263
  text: 'Strength',
1283
- textHint: 'Tints body and secondary text toward the palette; all three levels meet AA.',
1284
1264
  sidebar: 'Sidebar surface',
1285
- sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area.',
1286
1265
  lang: 'Language',
1287
1266
  hc: 'High contrast',
1288
1267
  custom: 'Custom palette',
@@ -1293,6 +1272,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1293
1272
  copy: 'Copy',
1294
1273
  applyImport: 'Import',
1295
1274
  copied: 'Copied',
1275
+ copyFail: 'Copy failed — select the code and press Ctrl+C',
1296
1276
  importOk: 'Imported',
1297
1277
  importBad: 'Invalid code, ignored',
1298
1278
  hcSub: 'Raises text floors to AAA (body ≥15, secondary ≥9, tertiary ≥7)',
@@ -1301,14 +1281,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1301
1281
  nav: 'カラーテーマ',
1302
1282
  hint: 'ライト/ダークとは独立です。変更は即時反映され、localStorage に保存されます。',
1303
1283
  md: 'Markdown の強調',
1304
- mdHint: '見出しと太字にアクセント色を混ぜます。オフにすると本文と同じ色になります。',
1305
1284
  control: 'スイッチ / チェック',
1306
- controlHint: 'ホストのスイッチ・チェックボックスはほぼ白黒です。オンにするとアクセント色になります。',
1307
1285
  scroll: 'スクロールバー',
1308
- scrollHint: '既定はニュートラルグレー。「追従」でテーマの色相、「アクセント」でアクセント色になります。',
1309
1286
  scrollHintMono: 'モノクロには色相がないため、スクロールバーはグレーのままです。',
1310
1287
  rightbar: '右パネルの背景',
1311
- rightbarHint: '右パネルは独自の背景を持たず、キャンバス色が見えています。',
1312
1288
  advanced: 'その他のオプション',
1313
1289
  textSub: '本文と強調の色づき具合',
1314
1290
  sidebarSub: '会話エリアとの段差',
@@ -1317,16 +1293,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1317
1293
  scrollSub: 'スクロールバーの色相',
1318
1294
  controlSub: 'スイッチとチェックの色',
1319
1295
  langSub: 'ホストの表示言語に合わせます',
1320
- surface: 'パネル表面',
1321
- surfaceHint: 'サイドバーが会話エリアより浮くか沈むかを決めます。',
1322
- ok: '✓ 配色を適用しました',
1323
1296
  bad: '⚠ 一部の token が適用されていません(ホスト側で改名された可能性)。コンソールを確認してください',
1324
1297
  healed: '配色レイヤーがホストに破棄されたため、自動で復元しました(',
1325
1298
  healedSuffix: ' 回)',
1326
1299
  text: '強さ',
1327
- textHint: '本文と副次テキストをテーマ色に寄せます。3 段階とも AA を満たします。',
1328
1300
  sidebar: '左サイドバーの背景',
1329
- sidebarHint: 'サイドバーを会話エリアより明るく(または暗く)して段差を作ります。',
1330
1301
  lang: '言語',
1331
1302
  hc: 'ハイコントラスト',
1332
1303
  custom: 'カスタム配色',
@@ -1337,6 +1308,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1337
1308
  copy: 'コピー',
1338
1309
  applyImport: '読み込み',
1339
1310
  copied: 'コピーしました',
1311
+ copyFail: 'コピーできません。選択して Ctrl+C してください',
1340
1312
  importOk: '読み込みました',
1341
1313
  importBad: '無効なコードです',
1342
1314
  hcSub: '本文の下限を AAA に引き上げます(本文 ≥15、副次 ≥9、三次 ≥7)',
@@ -1389,8 +1361,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1389
1361
  desc: { mono: '色なし · 既定', warm: 'クリーム + ブラウン', indigo: 'クールホワイト + ブルー', moss: 'グレーグリーン', custom: '3 色から派生' },
1390
1362
  text: { neutral: '控えめ', tinted: '標準', deep: '強め' },
1391
1363
  sidebar: { light: '明るく', match: '同じ', deep: '暗く' },
1392
- rightbar: { sidebar: 'サイドバーに合わせる', canvas: 'キャンバスに従う' },
1393
- scroll: { neutral: 'ニュートラル', tinted: '追従', accent: 'アクセント' },
1364
+ rightbar: { sidebar: 'サイドバー', canvas: 'キャンバス' },
1365
+ scroll: { neutral: '無彩', tinted: '追従', accent: '強調' },
1394
1366
  control: { accent: 'アクセント', mono: 'モノクロ' },
1395
1367
  md: { on: '色づけ', off: '本文と同じ' },
1396
1368
  lang: { auto: '自動' },
@@ -1412,32 +1384,42 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1412
1384
  const health = React.useSyncExternalStore(props.subscribeHealth, props.getHealth)
1413
1385
  const [importText, setImportText] = React.useState('')
1414
1386
  const [importState, setImportState] = React.useState(null)
1387
+ const [copyState, setCopyState] = React.useState(null)
1415
1388
  const [advanced, setAdvanced] = React.useState(false)
1416
1389
  const locale = React.useSyncExternalStore(props.subscribeLocale, props.getLocale)
1417
1390
  const langPref = React.useSyncExternalStore(props.subscribeLocale, props.getLangPref)
1391
+ const langAttr = langPref !== 'auto'
1392
+ ? langPref
1393
+ : ((typeof document !== 'undefined' && document.documentElement && document.documentElement.lang) || 'zh-CN')
1418
1394
  const txt = TEXT[locale] || TEXT['zh-CN']
1419
1395
  const lb = LABELS[locale] || LABELS['zh-CN']
1420
1396
 
1421
- const seg = (modes, value, onPick, labels) => h(
1397
+ // A6:读屏需要知道"这是哪个设置的选项组";单选用 radio 语义更准
1398
+ const seg = (modes, value, onPick, labels, labelId, disabled) => h(
1422
1399
  'div',
1423
- { className: 'dsh-tp-seg', role: 'group' },
1400
+ { className: 'dsh-tp-seg', role: 'radiogroup', 'aria-labelledby': labelId, 'aria-disabled': disabled ? 'true' : undefined },
1424
1401
  modes.map((mode) => h(
1425
1402
  'button',
1426
1403
  {
1427
1404
  key: mode.id,
1428
1405
  type: 'button',
1406
+ role: 'radio',
1429
1407
  className: mode.id === value ? 'is-on' : undefined,
1430
- 'aria-pressed': mode.id === value,
1431
- onClick: () => onPick(mode.id),
1408
+ 'aria-checked': mode.id === value,
1409
+ 'aria-disabled': disabled ? 'true' : undefined,
1410
+ disabled: disabled === true,
1411
+ onClick: () => { if (!disabled) onPick(mode.id) },
1432
1412
  },
1433
1413
  (labels && labels[mode.id]) || mode.label,
1434
1414
  )),
1435
1415
  )
1436
1416
 
1437
- const row = (titleText, subText, control) => h(
1417
+ const rowId = (key) => 'dsh-tp-row-' + key
1418
+ const row = (key, titleText, subText, control) => h(
1438
1419
  'div',
1439
1420
  { className: 'dsh-tp-row' },
1440
- h('div', { className: 'dsh-tp-row-label' }, h('b', null, titleText), h('span', null, subText)),
1421
+ h('div', { className: 'dsh-tp-row-label', id: rowId(key) + '-desc' },
1422
+ h('b', { id: rowId(key) }, titleText), h('span', null, subText)),
1441
1423
  control,
1442
1424
  )
1443
1425
 
@@ -1465,13 +1447,21 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1465
1447
  'aria-label': (lb.custom && lb.custom[field]) || field,
1466
1448
  }),
1467
1449
  h('span', { className: 'dsh-tp-swatch-name' }, (lb.custom && lb.custom[field]) || field),
1468
- h('code', { className: 'dsh-tp-hex' }, customColors[field])))),
1450
+ h('input', {
1451
+ key: customColors[field],
1452
+ className: 'dsh-tp-hexinput',
1453
+ defaultValue: customColors[field],
1454
+ spellCheck: false,
1455
+ 'aria-label': ((lb.custom && lb.custom[field]) || field) + ' HEX',
1456
+ onBlur: (e) => props.setCustomColor(field, e.target.value.trim()),
1457
+ onKeyDown: (e) => { if (e.key === 'Enter') props.setCustomColor(field, e.target.value.trim()) },
1458
+ })))),
1469
1459
  h('p', { className: 'dsh-tp-custom-hint' }, txt.customHint),
1470
1460
  )
1471
1461
 
1472
1462
  const card = (scheme) => {
1473
1463
  const picked = scheme.id === current
1474
- const c = cardColors(scheme, textMode)
1464
+ const c = cardColors(scheme, textMode, sidebarMode)
1475
1465
  return h(
1476
1466
  'button',
1477
1467
  {
@@ -1499,16 +1489,16 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1499
1489
 
1500
1490
  return h(
1501
1491
  'section',
1502
- { className: 'dsh-tp' },
1492
+ { className: 'dsh-tp', lang: langAttr },
1503
1493
  h('div', { className: 'dsh-tp-head' },
1504
1494
  h('h2', null, txt.nav),
1505
1495
  h('span', { className: 'dsh-tp-badge' }, 'v' + PLUGIN_VERSION)),
1506
1496
  h('p', { className: 'dsh-tp-sub' }, txt.hint),
1507
1497
  h('div', { className: 'dsh-tp-grid' }, SCHEMES.map(card)),
1508
1498
  customPanel,
1509
- row(txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode, lb.text)),
1510
- row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode, lb.sidebar)),
1511
- row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode, lb.rightbar)),
1499
+ row('text', txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode, lb.text, rowId('text'))),
1500
+ row('sidebar', txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode, lb.sidebar, rowId('sidebar'))),
1501
+ row('rightbar', txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode, lb.rightbar, rowId('rightbar'))),
1512
1502
  health && !health.ok && h('p', { className: 'dsh-tp-status is-bad' }, txt.bad),
1513
1503
  health && health.ok && health.reapplies > 0 && h(
1514
1504
  'p',
@@ -1524,35 +1514,43 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1524
1514
  advanced && h(
1525
1515
  'div',
1526
1516
  { className: 'dsh-tp-adv' },
1527
- row(txt.lang, txt.langSub, seg(LANG_OPTIONS, langPref, props.setLangPref, lb.lang)),
1517
+ row('lang', txt.lang, txt.langSub, seg(LANG_OPTIONS, langPref, props.setLangPref, lb.lang, rowId('lang'))),
1528
1518
  h(
1529
1519
  'div',
1530
- { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
1531
- h('div', { style: { fontSize: 14, lineHeight: '22px' } }, txt.config),
1532
- h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' } },
1520
+ { className: 'dsh-tp-field' },
1521
+ h('div', { className: 'dsh-tp-field-title', id: rowId('config') }, txt.config),
1522
+ h('div', { className: 'dsh-tp-field-row' },
1533
1523
  h('input', {
1534
1524
  className: 'dsh-tp-input',
1535
1525
  readOnly: true,
1536
1526
  value: props.getConfigCode(),
1527
+ title: props.getConfigCode(),
1537
1528
  onFocus: (e) => { try { e.target.select() } catch (err) { /* 忽略 */ } },
1538
- 'aria-label': txt.config,
1529
+ 'aria-label': txt.config + ' — ' + txt.copy,
1539
1530
  }),
1540
1531
  h('button', {
1541
1532
  type: 'button',
1542
1533
  className: 'dsh-tp-mini',
1543
- onClick: () => {
1534
+ onClick: async () => {
1544
1535
  const code = props.getConfigCode()
1545
1536
  try {
1546
- if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(code)
1547
- } catch (err) { /* 剪贴板不可用时靠手选 */ }
1548
- setImportState('copied')
1537
+ if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') throw new Error('no clipboard api')
1538
+ await navigator.clipboard.writeText(code)
1539
+ setCopyState('copied')
1540
+ } catch (err) {
1541
+ // 剪贴板可能被拒(无安全上下文/权限);不能谎报成功
1542
+ setCopyState('fail')
1543
+ }
1549
1544
  },
1550
- }, txt.copy)),
1551
- h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' } },
1545
+ }, txt.copy),
1546
+ copyState && h('span', { className: 'dsh-tp-note', role: 'status', 'aria-live': 'polite' },
1547
+ copyState === 'copied' ? txt.copied : txt.copyFail)),
1548
+ h('div', { className: 'dsh-tp-field-row' },
1552
1549
  h('input', {
1553
1550
  className: 'dsh-tp-input',
1554
1551
  placeholder: 'dshs1.…',
1555
1552
  value: importText,
1553
+ 'aria-label': txt.applyImport,
1556
1554
  onChange: (e) => { setImportText(e.target.value); setImportState(null) },
1557
1555
  }),
1558
1556
  h('button', {
@@ -1560,14 +1558,14 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1560
1558
  className: 'dsh-tp-mini',
1561
1559
  onClick: () => setImportState(props.applyConfigCode(importText) ? 'ok' : 'bad'),
1562
1560
  }, txt.applyImport),
1563
- importState && h('span', { className: 'dsh-tp-note' },
1564
- importState === 'ok' ? txt.importOk : importState === 'copied' ? txt.copied : txt.importBad)),
1565
- h('p', { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' } }, txt.configHint),
1561
+ importState && h('span', { className: 'dsh-tp-note', role: importState === 'bad' ? 'alert' : 'status', 'aria-live': 'polite' },
1562
+ importState === 'ok' ? txt.importOk : txt.importBad)),
1563
+ h('p', { className: 'dsh-tp-custom-hint' }, txt.configHint),
1566
1564
  ),
1567
- row(txt.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode, lb.md)),
1568
- row(txt.hc, txt.hcSub, seg(HC_MODES, hcMode, props.setHcMode, lb.hc)),
1569
- row(txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode, lb.scroll)),
1570
- row(txt.control, txt.controlSub, seg(CONTROL_MODES, controlMode, props.setControlMode, lb.control)),
1565
+ row('md', txt.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode, lb.md, rowId('md'))),
1566
+ row('hc', txt.hc, txt.hcSub, seg(HC_MODES, hcMode, props.setHcMode, lb.hc, rowId('hc'))),
1567
+ row('scroll', txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode, lb.scroll, rowId('scroll'), current === 'mono')),
1568
+ row('control', txt.control, txt.controlSub, seg(CONTROL_MODES, controlMode, props.setControlMode, lb.control, rowId('control'), current === 'mono')),
1571
1569
  ),
1572
1570
  )
1573
1571
  }
package/docs/DESIGN.md CHANGED
@@ -42,6 +42,20 @@
42
42
  旧写法把 `-0.020×0.6` 夹成 `-0.004`,深色导轨与画布只剩 ΔE 0.49 —— 边界直接消失,
43
43
  而同一档在浅色侧是 2.10。现在两侧都是 1.9–2.1,同一个档位在明暗两模式下强度一致。
44
44
 
45
+ ## 当前标定值(真源:`client.js`)
46
+
47
+ > 下面各节的推演过程保留原样(它们是当时的推理轨迹),但**具体数值以本表为准**。
48
+ > 2026-10-22 视觉审计发现旧表格仍写着 1.8 / 2.6 与「中性 / 跟随 / 浓一些」,与实现已不一致。
49
+
50
+ | 档位(UI 文案) | id | textScale | paperScale | textLift |
51
+ |---|---|---|---|---|
52
+ | 静 | `neutral` | 1 | 1 | 0 |
53
+ | **标准(默认)** | `tinted` | 3.0 | 2.4 | +0.055 |
54
+ | 浓 | `deep` | 4.6 | 3.6 | +0.085 |
55
+
56
+ 高对比档(`HC_MODES`,默认关)把文字下限整体抬到正文 ≥15 / 次文 ≥9 / 三级 ≥7 / 链接 ≥7,
57
+ 并把求解器位移上限从 0.13 放宽到 0.30(低档位下 4.6 → 7 需要 ΔL≈0.2,原上限够不着)。
58
+
45
59
  ## 文字着色
46
60
 
47
61
  正文原来只给 0.004 的色度(近中性),视觉上就是「黑字 / 白字」。现在有第三行「文字着色」,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",