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 +21 -0
- package/README-zh.md +1 -0
- package/README.md +1 -0
- package/client.js +344 -35
- package/package.json +3 -2
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.
|
|
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 <
|
|
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) >
|
|
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,
|
|
441
|
-
['700', lightCard,
|
|
442
|
-
['600', lightCard,
|
|
443
|
-
['400', darkModule,
|
|
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) <
|
|
453
|
-
ramps.deepseek['400'] = solveForeground(ramps.deepseek, '400', darkModule,
|
|
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) >=
|
|
471
|
-
return solveForeground(r.deepseek, '500', bg,
|
|
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 (!
|
|
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 =
|
|
570
|
+
const key = params.id + ':' + textMode + hcKey + customKey
|
|
498
571
|
let cached = rampCache.get(key)
|
|
499
572
|
if (cached === undefined) {
|
|
500
|
-
cached = tuneRamps(buildRamps(
|
|
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
|
|
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.
|
|
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
|
},
|