dsh-theme-palette 1.5.3 → 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,35 @@
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
+
16
45
  ### 1.5.3
17
46
  修:选中「自定义」后滚动条不跟随取色。
18
47
 
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.3'
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
 
@@ -807,16 +807,10 @@ window.__ModuleLoader__.load({
807
807
  return pairs
808
808
  }
809
809
 
810
- /* ================= 卡片预览色 ================= */
811
- function swatches(scheme, textMode) {
812
- if (!scheme.tinted) return ['#ffffff', '#e5e5e5', '#0f1115']
813
- const r = rampsFor(scheme, textMode || DEFAULT_TEXT)
814
- return [r.bluish['00'], r.bluish['100'], r.deepseek['500']]
815
- }
816
-
810
+
817
811
 
818
812
  /** 卡片缩略图用的一小组颜色(画布/侧栏/文字/强调),按当前强度档取。 */
819
- function cardColors(scheme, textMode) {
813
+ function cardColors(scheme, textMode, sidebarMode) {
820
814
  const r = rampsFor(scheme, textMode || DEFAULT_TEXT)
821
815
  if (!r) {
822
816
  return {
@@ -824,7 +818,7 @@ window.__ModuleLoader__.load({
824
818
  dark: { canvas: '#151517', side: '#1b1b1c', text: '#f9fafb', accent: '#7aaaff' },
825
819
  }
826
820
  }
827
- const side = sidebarTokens(r, DEFAULT_SIDEBAR, scheme.tinted)
821
+ const side = sidebarTokens(r, sidebarMode || DEFAULT_SIDEBAR, scheme.tinted)
828
822
  return {
829
823
  light: {
830
824
  canvas: r.bluish['00'],
@@ -868,10 +862,10 @@ window.__ModuleLoader__.load({
868
862
  .dsh-tp-head h2{margin:0;font-size:18px;font-weight:600;letter-spacing:.2px}
869
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}
870
864
  .dsh-tp-sub{margin:-6px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
871
- .dsh-tp-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
872
- @media (max-width:620px){.dsh-tp-grid{grid-template-columns:1fr}}
873
- .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}
874
867
  .dsh-tp-card:hover{background:var(--dsw-alias-interactive-bg-hover)}
868
+ .dsh-tp-card:focus-visible{outline-offset:2px}
875
869
  .dsh-tp-card.is-on{background:var(--dshtp-card-picked,var(--dsw-alias-bg-module-platform));border-color:var(--t-accent-l)}
876
870
  body[data-ds-dark-theme] .dsh-tp-card.is-on{border-color:var(--t-accent-d)}
877
871
  .dsh-tp-thumb{display:flex;height:56px;border-radius:9px;overflow:hidden;border:.5px solid rgba(128,128,128,.28)}
@@ -885,20 +879,21 @@ body[data-ds-dark-theme] .dsh-tp-thumb-main b{background:var(--t-text-d)}
885
879
  .dsh-tp-thumb-main b:nth-of-type(3){width:44%;background:var(--t-accent-l);opacity:.9}
886
880
  body[data-ds-dark-theme] .dsh-tp-thumb-main b:nth-of-type(3){background:var(--t-accent-d)}
887
881
  .dsh-tp-card-meta{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
888
- .dsh-tp-card-name{font-size:13.5px;font-weight:500}
889
- .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)}
890
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}
891
885
  body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--t-canvas-d)}
892
886
  .dsh-tp-card.is-on .dsh-tp-check{opacity:1}
893
- .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}
894
888
  .dsh-tp-row-label{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
895
889
  .dsh-tp-row-label b{font-size:13.5px;font-weight:500}
896
890
  .dsh-tp-row-label span{font-size:11.5px;color:var(--dsw-alias-label-tertiary);line-height:16px}
897
- .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%}
898
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}
899
893
  .dsh-tp-seg button:hover{color:var(--dsw-alias-label-primary)}
900
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)}
901
- @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}
902
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}
903
898
  .dsh-tp-more:hover{color:var(--dsw-alias-label-primary)}
904
899
  .dsh-tp-adv{display:flex;flex-direction:column}
@@ -906,21 +901,27 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
906
901
  .dsh-tp-input::placeholder{color:var(--dsw-alias-label-tertiary)}
907
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}
908
903
  .dsh-tp-mini:hover{background:var(--dsw-alias-bg-layer-1)}
909
- .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)}
910
905
  .dsh-tp-custom-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
911
906
  .dsh-tp-custom-head b{font-size:13.5px;font-weight:500}
912
- .dsh-tp-swatches{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
913
- @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}
914
908
  .dsh-tp-swatch{display:flex;flex-direction:column;gap:6px;cursor:pointer}
915
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}
916
910
  .dsh-tp-color::-webkit-color-swatch-wrapper{padding:3px}
917
911
  .dsh-tp-color::-webkit-color-swatch{border:none;border-radius:8px}
918
912
  .dsh-tp-swatch-name{font-size:12px;color:var(--dsw-alias-label-secondary)}
919
913
  .dsh-tp-hex{font:11.5px/1 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-tertiary)}
920
- .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)}
921
917
  .dsh-tp-note{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}
922
918
  .dsh-tp-status{margin:0;font-size:11.5px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
923
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}}
924
925
  `
925
926
 
926
927
  function installRightbarStyle() {
@@ -1172,14 +1173,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1172
1173
  nav: '主题色',
1173
1174
  hint: '配色与明暗模式独立;切换后立即生效并保存在 localStorage。',
1174
1175
  md: 'Markdown 强调',
1175
- mdHint: '标题与加粗用「正文色混强调色」,比正文更有色;关掉则与正文同色。',
1176
1176
  control: '开关 / 勾选',
1177
- controlHint: '宿主的开关、复选框、单选都压在近黑/近白上;开启后改用强调色。',
1178
1177
  scroll: '滚动条',
1179
- scrollHint: '默认中性灰;「跟随」上配色色相,「强调」用强调色。明暗关系不变。',
1180
1178
  scrollHintMono: '当前是黑白配色,没有色相可跟随,滚动条保持中性灰。',
1181
1179
  rightbar: '右侧栏底色',
1182
- rightbarHint: '右栏本来没有自己的背景,露的是会话区画布色。',
1183
1180
  advanced: '更多选项',
1184
1181
  textSub: '正文与强调的着色程度',
1185
1182
  sidebarSub: '相对会话区的层次',
@@ -1188,16 +1185,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1188
1185
  scrollSub: '滚动条与滑轨的色相',
1189
1186
  controlSub: '开关与复选框是否跟强调色',
1190
1187
  langSub: '自动跟随宿主界面语言',
1191
- surface: '面板表面',
1192
- surfaceHint: '决定侧栏与右栏相对会话区是「浮起」还是「凹陷」。',
1193
- ok: '✓ 配色已生效',
1194
1188
  bad: '⚠ 部分 token 未生效(宿主可能改名),请查看控制台',
1195
1189
  healed: '配色层被宿主丢过一次,已自动恢复 ',
1196
1190
  healedSuffix: ' 次',
1197
1191
  text: '强度',
1198
- textHint: '让正文/次级文字带配色色味;三档都满足 AA。',
1199
1192
  sidebar: '左侧栏底色',
1200
- sidebarHint: '让侧栏比会话区更浅(或更深),做出区块层次。',
1201
1193
  lang: '语言',
1202
1194
  hc: '高对比',
1203
1195
  custom: '自定义配色',
@@ -1208,6 +1200,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1208
1200
  copy: '复制',
1209
1201
  applyImport: '导入',
1210
1202
  copied: '已复制',
1203
+ copyFail: '复制失败,请手选后 Ctrl+C',
1211
1204
  importOk: '已导入',
1212
1205
  importBad: '短码无效,已忽略',
1213
1206
  hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三级 ≥7)',
@@ -1216,14 +1209,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1216
1209
  nav: '主題色',
1217
1210
  hint: '配色與明暗模式獨立;切換後立即生效並存在 localStorage。',
1218
1211
  md: 'Markdown 強調',
1219
- mdHint: '標題與粗體用「本文色混強調色」,比本文更有色;關掉則與本文同色。',
1220
1212
  control: '開關 / 勾選',
1221
- controlHint: '宿主開關、核取方塊、單選都用近黑/近白;開啟後改用強調色。',
1222
1213
  scroll: '捲軸',
1223
- scrollHint: '預設中性灰;「跟隨」上配色色相,「強調」用強調色。明暗關係不變。',
1224
1214
  scrollHintMono: '目前是黑白配色,沒有色相可跟隨,捲軸保持中性灰。',
1225
1215
  rightbar: '右側欄底色',
1226
- rightbarHint: '右欄本來沒有自己的背景,露的是會話區畫布色。',
1227
1216
  advanced: '更多選項',
1228
1217
  textSub: '本文與強調的著色程度',
1229
1218
  sidebarSub: '相對會話區的層次',
@@ -1232,16 +1221,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1232
1221
  scrollSub: '捲軸與軌道的色相',
1233
1222
  controlSub: '開關與核取方塊是否跟強調色',
1234
1223
  langSub: '自動跟隨宿主介面語言',
1235
- surface: '面板表面',
1236
- surfaceHint: '決定側欄與右欄相對會話區是「浮起」還是「凹陷」。',
1237
- ok: '✓ 配色已生效',
1238
1224
  bad: '⚠ 部分 token 未生效(宿主可能改名),請看主控台',
1239
1225
  healed: '配色層被宿主丟棄過一次,已自動恢復 ',
1240
1226
  healedSuffix: ' 次',
1241
1227
  text: '強度',
1242
- textHint: '讓本文/次級文字帶配色色味;三檔都符合 AA。',
1243
1228
  sidebar: '左側欄底色',
1244
- sidebarHint: '讓側欄比會話區更淺(或更深),做出區塊層次。',
1245
1229
  lang: '語言',
1246
1230
  hc: '高對比',
1247
1231
  custom: '自訂配色',
@@ -1252,6 +1236,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1252
1236
  copy: '複製',
1253
1237
  applyImport: '匯入',
1254
1238
  copied: '已複製',
1239
+ copyFail: '複製失敗,請手選後 Ctrl+C',
1255
1240
  importOk: '已匯入',
1256
1241
  importBad: '短碼無效,已忽略',
1257
1242
  hcSub: '把文字下限抬到 AAA(正文 ≥15、次文 ≥9、三級 ≥7)',
@@ -1260,14 +1245,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1260
1245
  nav: 'Color theme',
1261
1246
  hint: 'Independent of light/dark mode. Applies immediately and is saved to localStorage.',
1262
1247
  md: 'Markdown emphasis',
1263
- mdHint: 'Headings and bold mix the accent into body colour; off keeps them identical to body text.',
1264
1248
  control: 'Switches / checks',
1265
- controlHint: 'Switches, checkboxes and radios use near-black/white in the host. Turn this on to use the accent colour.',
1266
1249
  scroll: 'Scrollbars',
1267
- scrollHint: 'Neutral grey by default. Tint follows the palette hue; Accent uses the accent colour. Lightness is unchanged.',
1268
1250
  scrollHintMono: 'Monochrome has no hue to follow; scrollbars stay neutral.',
1269
1251
  rightbar: 'Right panel surface',
1270
- rightbarHint: 'The right panel has no fill of its own; it shows the canvas.',
1271
1252
  advanced: 'More options',
1272
1253
  textSub: 'How strongly text follows the palette',
1273
1254
  sidebarSub: 'Layer relative to the conversation',
@@ -1276,16 +1257,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1276
1257
  scrollSub: 'Hue of the scrollbar',
1277
1258
  controlSub: 'Switches and checkboxes',
1278
1259
  langSub: 'Follow the host interface language',
1279
- surface: 'Panel surfaces',
1280
- surfaceHint: 'Whether the sidebars sit above or below the conversation area.',
1281
- ok: '✓ Palette applied',
1282
1260
  bad: '⚠ Some tokens did not apply (host may have renamed them); check the console',
1283
1261
  healed: 'Palette layer was dropped by the host; restored ',
1284
1262
  healedSuffix: 'x',
1285
1263
  text: 'Strength',
1286
- textHint: 'Tints body and secondary text toward the palette; all three levels meet AA.',
1287
1264
  sidebar: 'Sidebar surface',
1288
- sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area.',
1289
1265
  lang: 'Language',
1290
1266
  hc: 'High contrast',
1291
1267
  custom: 'Custom palette',
@@ -1296,6 +1272,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1296
1272
  copy: 'Copy',
1297
1273
  applyImport: 'Import',
1298
1274
  copied: 'Copied',
1275
+ copyFail: 'Copy failed — select the code and press Ctrl+C',
1299
1276
  importOk: 'Imported',
1300
1277
  importBad: 'Invalid code, ignored',
1301
1278
  hcSub: 'Raises text floors to AAA (body ≥15, secondary ≥9, tertiary ≥7)',
@@ -1304,14 +1281,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1304
1281
  nav: 'カラーテーマ',
1305
1282
  hint: 'ライト/ダークとは独立です。変更は即時反映され、localStorage に保存されます。',
1306
1283
  md: 'Markdown の強調',
1307
- mdHint: '見出しと太字にアクセント色を混ぜます。オフにすると本文と同じ色になります。',
1308
1284
  control: 'スイッチ / チェック',
1309
- controlHint: 'ホストのスイッチ・チェックボックスはほぼ白黒です。オンにするとアクセント色になります。',
1310
1285
  scroll: 'スクロールバー',
1311
- scrollHint: '既定はニュートラルグレー。「追従」でテーマの色相、「アクセント」でアクセント色になります。',
1312
1286
  scrollHintMono: 'モノクロには色相がないため、スクロールバーはグレーのままです。',
1313
1287
  rightbar: '右パネルの背景',
1314
- rightbarHint: '右パネルは独自の背景を持たず、キャンバス色が見えています。',
1315
1288
  advanced: 'その他のオプション',
1316
1289
  textSub: '本文と強調の色づき具合',
1317
1290
  sidebarSub: '会話エリアとの段差',
@@ -1320,16 +1293,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1320
1293
  scrollSub: 'スクロールバーの色相',
1321
1294
  controlSub: 'スイッチとチェックの色',
1322
1295
  langSub: 'ホストの表示言語に合わせます',
1323
- surface: 'パネル表面',
1324
- surfaceHint: 'サイドバーが会話エリアより浮くか沈むかを決めます。',
1325
- ok: '✓ 配色を適用しました',
1326
1296
  bad: '⚠ 一部の token が適用されていません(ホスト側で改名された可能性)。コンソールを確認してください',
1327
1297
  healed: '配色レイヤーがホストに破棄されたため、自動で復元しました(',
1328
1298
  healedSuffix: ' 回)',
1329
1299
  text: '強さ',
1330
- textHint: '本文と副次テキストをテーマ色に寄せます。3 段階とも AA を満たします。',
1331
1300
  sidebar: '左サイドバーの背景',
1332
- sidebarHint: 'サイドバーを会話エリアより明るく(または暗く)して段差を作ります。',
1333
1301
  lang: '言語',
1334
1302
  hc: 'ハイコントラスト',
1335
1303
  custom: 'カスタム配色',
@@ -1340,6 +1308,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1340
1308
  copy: 'コピー',
1341
1309
  applyImport: '読み込み',
1342
1310
  copied: 'コピーしました',
1311
+ copyFail: 'コピーできません。選択して Ctrl+C してください',
1343
1312
  importOk: '読み込みました',
1344
1313
  importBad: '無効なコードです',
1345
1314
  hcSub: '本文の下限を AAA に引き上げます(本文 ≥15、副次 ≥9、三次 ≥7)',
@@ -1392,8 +1361,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1392
1361
  desc: { mono: '色なし · 既定', warm: 'クリーム + ブラウン', indigo: 'クールホワイト + ブルー', moss: 'グレーグリーン', custom: '3 色から派生' },
1393
1362
  text: { neutral: '控えめ', tinted: '標準', deep: '強め' },
1394
1363
  sidebar: { light: '明るく', match: '同じ', deep: '暗く' },
1395
- rightbar: { sidebar: 'サイドバーに合わせる', canvas: 'キャンバスに従う' },
1396
- scroll: { neutral: 'ニュートラル', tinted: '追従', accent: 'アクセント' },
1364
+ rightbar: { sidebar: 'サイドバー', canvas: 'キャンバス' },
1365
+ scroll: { neutral: '無彩', tinted: '追従', accent: '強調' },
1397
1366
  control: { accent: 'アクセント', mono: 'モノクロ' },
1398
1367
  md: { on: '色づけ', off: '本文と同じ' },
1399
1368
  lang: { auto: '自動' },
@@ -1415,32 +1384,42 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1415
1384
  const health = React.useSyncExternalStore(props.subscribeHealth, props.getHealth)
1416
1385
  const [importText, setImportText] = React.useState('')
1417
1386
  const [importState, setImportState] = React.useState(null)
1387
+ const [copyState, setCopyState] = React.useState(null)
1418
1388
  const [advanced, setAdvanced] = React.useState(false)
1419
1389
  const locale = React.useSyncExternalStore(props.subscribeLocale, props.getLocale)
1420
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')
1421
1394
  const txt = TEXT[locale] || TEXT['zh-CN']
1422
1395
  const lb = LABELS[locale] || LABELS['zh-CN']
1423
1396
 
1424
- const seg = (modes, value, onPick, labels) => h(
1397
+ // A6:读屏需要知道"这是哪个设置的选项组";单选用 radio 语义更准
1398
+ const seg = (modes, value, onPick, labels, labelId, disabled) => h(
1425
1399
  'div',
1426
- { className: 'dsh-tp-seg', role: 'group' },
1400
+ { className: 'dsh-tp-seg', role: 'radiogroup', 'aria-labelledby': labelId, 'aria-disabled': disabled ? 'true' : undefined },
1427
1401
  modes.map((mode) => h(
1428
1402
  'button',
1429
1403
  {
1430
1404
  key: mode.id,
1431
1405
  type: 'button',
1406
+ role: 'radio',
1432
1407
  className: mode.id === value ? 'is-on' : undefined,
1433
- 'aria-pressed': mode.id === value,
1434
- 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) },
1435
1412
  },
1436
1413
  (labels && labels[mode.id]) || mode.label,
1437
1414
  )),
1438
1415
  )
1439
1416
 
1440
- const row = (titleText, subText, control) => h(
1417
+ const rowId = (key) => 'dsh-tp-row-' + key
1418
+ const row = (key, titleText, subText, control) => h(
1441
1419
  'div',
1442
1420
  { className: 'dsh-tp-row' },
1443
- 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)),
1444
1423
  control,
1445
1424
  )
1446
1425
 
@@ -1468,13 +1447,21 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1468
1447
  'aria-label': (lb.custom && lb.custom[field]) || field,
1469
1448
  }),
1470
1449
  h('span', { className: 'dsh-tp-swatch-name' }, (lb.custom && lb.custom[field]) || field),
1471
- 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
+ })))),
1472
1459
  h('p', { className: 'dsh-tp-custom-hint' }, txt.customHint),
1473
1460
  )
1474
1461
 
1475
1462
  const card = (scheme) => {
1476
1463
  const picked = scheme.id === current
1477
- const c = cardColors(scheme, textMode)
1464
+ const c = cardColors(scheme, textMode, sidebarMode)
1478
1465
  return h(
1479
1466
  'button',
1480
1467
  {
@@ -1502,16 +1489,16 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1502
1489
 
1503
1490
  return h(
1504
1491
  'section',
1505
- { className: 'dsh-tp' },
1492
+ { className: 'dsh-tp', lang: langAttr },
1506
1493
  h('div', { className: 'dsh-tp-head' },
1507
1494
  h('h2', null, txt.nav),
1508
1495
  h('span', { className: 'dsh-tp-badge' }, 'v' + PLUGIN_VERSION)),
1509
1496
  h('p', { className: 'dsh-tp-sub' }, txt.hint),
1510
1497
  h('div', { className: 'dsh-tp-grid' }, SCHEMES.map(card)),
1511
1498
  customPanel,
1512
- row(txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode, lb.text)),
1513
- row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode, lb.sidebar)),
1514
- 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'))),
1515
1502
  health && !health.ok && h('p', { className: 'dsh-tp-status is-bad' }, txt.bad),
1516
1503
  health && health.ok && health.reapplies > 0 && h(
1517
1504
  'p',
@@ -1527,35 +1514,43 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1527
1514
  advanced && h(
1528
1515
  'div',
1529
1516
  { className: 'dsh-tp-adv' },
1530
- 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'))),
1531
1518
  h(
1532
1519
  'div',
1533
- { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
1534
- h('div', { style: { fontSize: 14, lineHeight: '22px' } }, txt.config),
1535
- 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' },
1536
1523
  h('input', {
1537
1524
  className: 'dsh-tp-input',
1538
1525
  readOnly: true,
1539
1526
  value: props.getConfigCode(),
1527
+ title: props.getConfigCode(),
1540
1528
  onFocus: (e) => { try { e.target.select() } catch (err) { /* 忽略 */ } },
1541
- 'aria-label': txt.config,
1529
+ 'aria-label': txt.config + ' — ' + txt.copy,
1542
1530
  }),
1543
1531
  h('button', {
1544
1532
  type: 'button',
1545
1533
  className: 'dsh-tp-mini',
1546
- onClick: () => {
1534
+ onClick: async () => {
1547
1535
  const code = props.getConfigCode()
1548
1536
  try {
1549
- if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(code)
1550
- } catch (err) { /* 剪贴板不可用时靠手选 */ }
1551
- 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
+ }
1552
1544
  },
1553
- }, txt.copy)),
1554
- 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' },
1555
1549
  h('input', {
1556
1550
  className: 'dsh-tp-input',
1557
1551
  placeholder: 'dshs1.…',
1558
1552
  value: importText,
1553
+ 'aria-label': txt.applyImport,
1559
1554
  onChange: (e) => { setImportText(e.target.value); setImportState(null) },
1560
1555
  }),
1561
1556
  h('button', {
@@ -1563,14 +1558,14 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1563
1558
  className: 'dsh-tp-mini',
1564
1559
  onClick: () => setImportState(props.applyConfigCode(importText) ? 'ok' : 'bad'),
1565
1560
  }, txt.applyImport),
1566
- importState && h('span', { className: 'dsh-tp-note' },
1567
- importState === 'ok' ? txt.importOk : importState === 'copied' ? txt.copied : txt.importBad)),
1568
- 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),
1569
1564
  ),
1570
- row(txt.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode, lb.md)),
1571
- row(txt.hc, txt.hcSub, seg(HC_MODES, hcMode, props.setHcMode, lb.hc)),
1572
- row(txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode, lb.scroll)),
1573
- 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')),
1574
1569
  ),
1575
1570
  )
1576
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.3",
3
+ "version": "1.6.0",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",