dsh-theme-endfield 1.1.9 → 1.2.3
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/README.md +163 -118
- package/client.js +10186 -10063
- package/docs/PR-description.md +141 -141
- package/docs/audio-notifications.md +133 -133
- package/docs/contour-scan-bounds.md +24 -24
- package/docs/contour-trail.md +61 -61
- package/docs/contour-worker.md +38 -38
- package/docs/design-language.md +212 -212
- package/docs/engineering-notes.md +1138 -1147
- package/docs/features.md +298 -295
- package/docs/glass.md +32 -32
- package/docs/testing.md +427 -427
- package/index.js +1403 -1403
- package/lib/audio.js +426 -426
- package/lib/slots.js +97 -97
- package/lib/tone.js +216 -216
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +113 -105
package/docs/contour-trail.md
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
# Optional mouse trail
|
|
2
|
-
|
|
3
|
-
Enable the contour background, its animation, and **Mouse trail** in the theme
|
|
4
|
-
settings. The persisted `contourTrail` string defaults to `'0'`; `'1'` enables a
|
|
5
|
-
local deformation that follows the mouse and fades. Chinese and English labels
|
|
6
|
-
follow the host locale. Touch and pen input do not produce a trail.
|
|
7
|
-
|
|
8
|
-
The idea and bounded decay model are adapted from the Endfield Glass plugin.
|
|
9
|
-
This port integrates into the current upstream Canvas pipeline and preserves
|
|
10
|
-
the original ambient field, smoothing, contour levels and curve drawing.
|
|
11
|
-
|
|
12
|
-
## Cost and lifecycle
|
|
13
|
-
|
|
14
|
-
- Reuses the existing contour animation loop and selected FPS. It consumes only
|
|
15
|
-
the latest pointer event per field frame, with 8 ms / 2 CSS px admission gates.
|
|
16
|
-
- The pointer handler only stores coordinates and timestamps. A frame reads the
|
|
17
|
-
host rectangle once when consuming a pending point, not on every pointer event
|
|
18
|
-
or streaming DOM mutation. Stored coordinates never leave page memory.
|
|
19
|
-
- At most 24 samples, a 900 ms exponential decay constant and 2700 ms lifetime.
|
|
20
|
-
Head quota is .9; older samples share a fixed .6 geometric tail budget. Expiry
|
|
21
|
-
cannot renormalize or brighten remaining samples. Only bounded neighborhoods
|
|
22
|
-
around samples receive field contributions.
|
|
23
|
-
- Smooth Gaussian contributions are added after the ambient smoothing/temporal
|
|
24
|
-
blend. They never enter ambient history, so expiry leaves no temporal ghost.
|
|
25
|
-
This port uses a 28 CSS px kernel; it does not claim pixel identity with the
|
|
26
|
-
Glass plugin's raw-field overlay followed by spatial smoothing.
|
|
27
|
-
- Static mode, reduced motion, hidden pages, scrolling (when pause is enabled)
|
|
28
|
-
and the startup loader suspend sampling and clear deformation. Resize, blur,
|
|
29
|
-
disabling and disposal also clear history. Mouse leave drops the pending event
|
|
30
|
-
while already drawn samples decay normally.
|
|
31
|
-
- Disabling removes mouse listeners. Unmount/disposal also removes visibility,
|
|
32
|
-
blur and media-query listeners. No second timer or animation loop is added.
|
|
33
|
-
|
|
34
|
-
This branch targets current upstream main independently of the proposed scan,
|
|
35
|
-
Worker/WebGL and glass PRs. Combining it with a Worker renderer requires carrying
|
|
36
|
-
the bounded samples and frame clock into that renderer's frame messages before
|
|
37
|
-
contour extraction; main-thread state must not be read inside a Worker.
|
|
38
|
-
|
|
39
|
-
## Validation
|
|
40
|
-
|
|
41
|
-
`npm run test:trail` runs dependency-free Node checks for bounded history,
|
|
42
|
-
timestamps, local support, monotonic decay, fixed total weight and expiry.
|
|
43
|
-
|
|
44
|
-
`npm run test:trail-browser` uses Chrome/Edge and Node 22+ (the same Node major
|
|
45
|
-
used by CI), with an isolated temporary browser profile. A controlled animation
|
|
46
|
-
clock and fixed terrain seed compare real Canvas pixels against a trail-off
|
|
47
|
-
control: idle and expired trails yield exactly the control image, while an active
|
|
48
|
-
mouse sample visibly deforms it. It also checks pointer-handler layout reads,
|
|
49
|
-
static mode, live reduced-motion changes, visibility/scroll pause, repeated
|
|
50
|
-
enable/disable and complete listener/canvas cleanup. This is not an FPS test.
|
|
51
|
-
|
|
52
|
-
The existing settings tests cover the default, disabled UI state, persisted
|
|
53
|
-
camelCase key and rereading that preference. Both new tests join `test:ci`.
|
|
54
|
-
|
|
55
|
-
Local validation (2026-09-19, Windows Chrome / Node 24.12): the new browser and
|
|
56
|
-
four logic checks pass. The final complete CI run passed 23 of 24 scripts; the
|
|
57
|
-
existing random-layout speck/coverage test reported one sparse region among five
|
|
58
|
-
random layouts. Its isolated rerun passed all five layouts. The original terrain
|
|
59
|
-
generator, coverage scorer, ambient evaluator, level extractor and curve painter
|
|
60
|
-
were also compared with the base commit and are unchanged. The initial random
|
|
61
|
-
failure is recorded rather than presented as an entirely green single CI run.
|
|
1
|
+
# Optional mouse trail
|
|
2
|
+
|
|
3
|
+
Enable the contour background, its animation, and **Mouse trail** in the theme
|
|
4
|
+
settings. The persisted `contourTrail` string defaults to `'0'`; `'1'` enables a
|
|
5
|
+
local deformation that follows the mouse and fades. Chinese and English labels
|
|
6
|
+
follow the host locale. Touch and pen input do not produce a trail.
|
|
7
|
+
|
|
8
|
+
The idea and bounded decay model are adapted from the Endfield Glass plugin.
|
|
9
|
+
This port integrates into the current upstream Canvas pipeline and preserves
|
|
10
|
+
the original ambient field, smoothing, contour levels and curve drawing.
|
|
11
|
+
|
|
12
|
+
## Cost and lifecycle
|
|
13
|
+
|
|
14
|
+
- Reuses the existing contour animation loop and selected FPS. It consumes only
|
|
15
|
+
the latest pointer event per field frame, with 8 ms / 2 CSS px admission gates.
|
|
16
|
+
- The pointer handler only stores coordinates and timestamps. A frame reads the
|
|
17
|
+
host rectangle once when consuming a pending point, not on every pointer event
|
|
18
|
+
or streaming DOM mutation. Stored coordinates never leave page memory.
|
|
19
|
+
- At most 24 samples, a 900 ms exponential decay constant and 2700 ms lifetime.
|
|
20
|
+
Head quota is .9; older samples share a fixed .6 geometric tail budget. Expiry
|
|
21
|
+
cannot renormalize or brighten remaining samples. Only bounded neighborhoods
|
|
22
|
+
around samples receive field contributions.
|
|
23
|
+
- Smooth Gaussian contributions are added after the ambient smoothing/temporal
|
|
24
|
+
blend. They never enter ambient history, so expiry leaves no temporal ghost.
|
|
25
|
+
This port uses a 28 CSS px kernel; it does not claim pixel identity with the
|
|
26
|
+
Glass plugin's raw-field overlay followed by spatial smoothing.
|
|
27
|
+
- Static mode, reduced motion, hidden pages, scrolling (when pause is enabled)
|
|
28
|
+
and the startup loader suspend sampling and clear deformation. Resize, blur,
|
|
29
|
+
disabling and disposal also clear history. Mouse leave drops the pending event
|
|
30
|
+
while already drawn samples decay normally.
|
|
31
|
+
- Disabling removes mouse listeners. Unmount/disposal also removes visibility,
|
|
32
|
+
blur and media-query listeners. No second timer or animation loop is added.
|
|
33
|
+
|
|
34
|
+
This branch targets current upstream main independently of the proposed scan,
|
|
35
|
+
Worker/WebGL and glass PRs. Combining it with a Worker renderer requires carrying
|
|
36
|
+
the bounded samples and frame clock into that renderer's frame messages before
|
|
37
|
+
contour extraction; main-thread state must not be read inside a Worker.
|
|
38
|
+
|
|
39
|
+
## Validation
|
|
40
|
+
|
|
41
|
+
`npm run test:trail` runs dependency-free Node checks for bounded history,
|
|
42
|
+
timestamps, local support, monotonic decay, fixed total weight and expiry.
|
|
43
|
+
|
|
44
|
+
`npm run test:trail-browser` uses Chrome/Edge and Node 22+ (the same Node major
|
|
45
|
+
used by CI), with an isolated temporary browser profile. A controlled animation
|
|
46
|
+
clock and fixed terrain seed compare real Canvas pixels against a trail-off
|
|
47
|
+
control: idle and expired trails yield exactly the control image, while an active
|
|
48
|
+
mouse sample visibly deforms it. It also checks pointer-handler layout reads,
|
|
49
|
+
static mode, live reduced-motion changes, visibility/scroll pause, repeated
|
|
50
|
+
enable/disable and complete listener/canvas cleanup. This is not an FPS test.
|
|
51
|
+
|
|
52
|
+
The existing settings tests cover the default, disabled UI state, persisted
|
|
53
|
+
camelCase key and rereading that preference. Both new tests join `test:ci`.
|
|
54
|
+
|
|
55
|
+
Local validation (2026-09-19, Windows Chrome / Node 24.12): the new browser and
|
|
56
|
+
four logic checks pass. The final complete CI run passed 23 of 24 scripts; the
|
|
57
|
+
existing random-layout speck/coverage test reported one sparse region among five
|
|
58
|
+
random layouts. Its isolated rerun passed all five layouts. The original terrain
|
|
59
|
+
generator, coverage scorer, ambient evaluator, level extractor and curve painter
|
|
60
|
+
were also compared with the base commit and are unchanged. The initial random
|
|
61
|
+
failure is recorded rather than presented as an entirely green single CI run.
|
package/docs/contour-worker.md
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# Optional Worker / WebGL contours
|
|
2
|
-
|
|
3
|
-
`contourRenderer` defaults to `canvas`, retaining the original rendering path.
|
|
4
|
-
Select **Worker / WebGL** in the contour settings to compute and draw the same
|
|
5
|
-
terrain in a dedicated Worker using a transferred OffscreenCanvas. WebGL2 batches
|
|
6
|
-
the smoothed curve segments in a single instanced draw. Canvas2D in the worker is
|
|
7
|
-
the fallback when WebGL2 is unavailable. Unsupported workers, transfer failure,
|
|
8
|
-
render timeouts, message errors or context loss fall back to the main Canvas2D
|
|
9
|
-
path on a fresh canvas; a transferred canvas is never reused for getContext.
|
|
10
|
-
|
|
11
|
-
The worker source is generated from the exact build/evaluate/extract/draw
|
|
12
|
-
functions in client.js, preserving field generation, smoothing, phase policy,
|
|
13
|
-
level filters, stroke colors and DPR. The GPU painter approximates cubic curves
|
|
14
|
-
within 0.04 backing pixels (with a bounded subdivision depth), so antialiasing
|
|
15
|
-
is not claimed to be pixel-identical to Canvas2D. No external URL, CDN, eval or
|
|
16
|
-
runtime dependency is used; a local Blob contains the embedded worker source.
|
|
17
|
-
|
|
18
|
-
The queue has one in-flight job plus at most one latest pending job. Pending
|
|
19
|
-
geometry work survives a later paint-only update. Hidden pages stop submitting
|
|
20
|
-
work; static/reduced-motion/scroll policies retain the existing switches.
|
|
21
|
-
Teardown terminates the worker, revokes its Blob URL and removes its canvas.
|
|
22
|
-
Failure is latched until the backend preference changes, preventing retry loops.
|
|
23
|
-
|
|
24
|
-
The contour wrapper exposes `data-endfield-renderer` (starting, worker-webgl2,
|
|
25
|
-
worker-canvas2d or main-canvas2d) and an explanatory fallback reason attribute.
|
|
26
|
-
CPU/worker completion rate is not display FPS; external video dropped frames
|
|
27
|
-
and total GPU cost require separate measurement on the target hardware.
|
|
28
|
-
|
|
29
|
-
## Developing
|
|
30
|
-
|
|
31
|
-
- `npm run build:worker`: regenerate the embedded source after kernel edits.
|
|
32
|
-
- `npm run check:worker`: fail if the embedded source is stale.
|
|
33
|
-
- `npm run test:worker`: real Chrome test (Node 22+, `CHROME_PATH` supported).
|
|
34
|
-
|
|
35
|
-
The browser test checks the actual backend, paused/static behavior, visibility
|
|
36
|
-
handler, injected message failure with a fresh-canvas fallback, explicit retry,
|
|
37
|
-
repeated enable/disable and final teardown. The temporary profile contains only
|
|
38
|
-
fixture data. The legacy CI suite continues to validate the default backend.
|
|
1
|
+
# Optional Worker / WebGL contours
|
|
2
|
+
|
|
3
|
+
`contourRenderer` defaults to `canvas`, retaining the original rendering path.
|
|
4
|
+
Select **Worker / WebGL** in the contour settings to compute and draw the same
|
|
5
|
+
terrain in a dedicated Worker using a transferred OffscreenCanvas. WebGL2 batches
|
|
6
|
+
the smoothed curve segments in a single instanced draw. Canvas2D in the worker is
|
|
7
|
+
the fallback when WebGL2 is unavailable. Unsupported workers, transfer failure,
|
|
8
|
+
render timeouts, message errors or context loss fall back to the main Canvas2D
|
|
9
|
+
path on a fresh canvas; a transferred canvas is never reused for getContext.
|
|
10
|
+
|
|
11
|
+
The worker source is generated from the exact build/evaluate/extract/draw
|
|
12
|
+
functions in client.js, preserving field generation, smoothing, phase policy,
|
|
13
|
+
level filters, stroke colors and DPR. The GPU painter approximates cubic curves
|
|
14
|
+
within 0.04 backing pixels (with a bounded subdivision depth), so antialiasing
|
|
15
|
+
is not claimed to be pixel-identical to Canvas2D. No external URL, CDN, eval or
|
|
16
|
+
runtime dependency is used; a local Blob contains the embedded worker source.
|
|
17
|
+
|
|
18
|
+
The queue has one in-flight job plus at most one latest pending job. Pending
|
|
19
|
+
geometry work survives a later paint-only update. Hidden pages stop submitting
|
|
20
|
+
work; static/reduced-motion/scroll policies retain the existing switches.
|
|
21
|
+
Teardown terminates the worker, revokes its Blob URL and removes its canvas.
|
|
22
|
+
Failure is latched until the backend preference changes, preventing retry loops.
|
|
23
|
+
|
|
24
|
+
The contour wrapper exposes `data-endfield-renderer` (starting, worker-webgl2,
|
|
25
|
+
worker-canvas2d or main-canvas2d) and an explanatory fallback reason attribute.
|
|
26
|
+
CPU/worker completion rate is not display FPS; external video dropped frames
|
|
27
|
+
and total GPU cost require separate measurement on the target hardware.
|
|
28
|
+
|
|
29
|
+
## Developing
|
|
30
|
+
|
|
31
|
+
- `npm run build:worker`: regenerate the embedded source after kernel edits.
|
|
32
|
+
- `npm run check:worker`: fail if the embedded source is stale.
|
|
33
|
+
- `npm run test:worker`: real Chrome test (Node 22+, `CHROME_PATH` supported).
|
|
34
|
+
|
|
35
|
+
The browser test checks the actual backend, paused/static behavior, visibility
|
|
36
|
+
handler, injected message failure with a fresh-canvas fallback, explicit retry,
|
|
37
|
+
repeated enable/disable and final teardown. The temporary profile contains only
|
|
38
|
+
fixture data. The legacy CI suite continues to validate the default backend.
|
package/docs/design-language.md
CHANGED
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
# 设计语言
|
|
2
|
-
|
|
3
|
-
本文是这套主题的设计说明:**颜色是什么、为什么是这个值、落在哪个界面角色上**。想拿它当设计语言学习入口的,读这一篇就够。
|
|
4
|
-
|
|
5
|
-
工程实现细节在 [engineering-notes.md](engineering-notes.md),测试在 [testing.md](testing.md)。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 一、核心概念:工业编辑风
|
|
10
|
-
|
|
11
|
-
终末地官网的视觉不是「深色科技感」,而是**印刷编辑风**:一张有质感的纸、压在上面的墨黑字、以及一个用来做信号的高饱和色。三条规则贯穿全篇:
|
|
12
|
-
|
|
13
|
-
1. **纸与墨是主体,强调色是信号。** 强调色只用于「需要被看见的那一处」——按钮、悬停、选中行、焦点环——而不是大面积铺色。
|
|
14
|
-
2. **全直角。** 圆角会把界面推向「柔和产品」的观感;直角把它推向「技术文档 / 工程图纸」。只有形状本身是圆的(状态点、头像、加载圈)才保留圆形。
|
|
15
|
-
3. **数字等宽。** 开启 `tnum`,让表格、耗时、百分比在跳动时不产生横向抖动。
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 二、色板
|
|
20
|
-
|
|
21
|
-
### 中性色(纸 / 墨 / 线)
|
|
22
|
-
|
|
23
|
-
两套模式共用同一套语义,只换取值。
|
|
24
|
-
|
|
25
|
-
| 角色 | 令牌 | 亮色 | 暗色 |
|
|
26
|
-
| --- | --- | --- | --- |
|
|
27
|
-
| 纸底(应用背景、侧栏) | `--dsw-alias-bg-base` | `#e8e8e2` | `#101110` |
|
|
28
|
-
| 面板 / 气泡 | `--dsw-alias-bg-layer-1` | `#f2f2ec` | `#181a18` |
|
|
29
|
-
| 沉底块 | `--dsw-alias-bg-layer-2` | `#dcddd6` | `#1e201d` |
|
|
30
|
-
| 浮层(菜单、弹层) | `--dsw-alias-bg-overlay` | `#f2f2ec` | `#1c1e1c` |
|
|
31
|
-
| 细线 | `--dsw-alias-border-l1` | `#d8d9d5` | `#343633` |
|
|
32
|
-
| 重线 | `--dsw-alias-border-l2` | `#b6b8b3` | `#4a4d49` |
|
|
33
|
-
| 正文 | `--dsw-alias-label-primary` | `#101110` | `#f5f5f0` |
|
|
34
|
-
| 次要文字 | `--dsw-alias-label-secondary` | `#4a4c48` | `#898d89` |
|
|
35
|
-
|
|
36
|
-
亮色不用纯白 `#fff`、暗色不用纯黑 `#000`:`#e8e8e2` 带一点暖灰,是「纸」而不是「屏幕」;`#101110` 留一点绿倾向,避免深灰界面常见的死黑。
|
|
37
|
-
|
|
38
|
-
### 强调色:两套配色
|
|
39
|
-
|
|
40
|
-
设置页「主题配色」在两者间切换,**默认谷地黄**。
|
|
41
|
-
|
|
42
|
-
| 配色 | 强调色 | 相对亮度 | 说明 |
|
|
43
|
-
| --- | --- | --- | --- |
|
|
44
|
-
| **谷地黄**(默认) | `#fff500` | 86.6% | 终末地官网的信号黄 |
|
|
45
|
-
| **武陵青** | `#14d0d0` | 49.8% | 青碧色 |
|
|
46
|
-
|
|
47
|
-
存储字段 `palette`(UI/存储键 `dsh-theme-endfield-palette`,由 `PREFS_KEY_TO_FIELD` 映射),取值 `valley` / `wuling`。只有精确值 `wuling` 选中武陵青,未设置或异常值都回落到默认。
|
|
48
|
-
|
|
49
|
-
### 语义状态色
|
|
50
|
-
|
|
51
|
-
不参与配色切换——错误就该是红的。
|
|
52
|
-
|
|
53
|
-
| 角色 | 亮色 | 暗色 |
|
|
54
|
-
| --- | --- | --- |
|
|
55
|
-
| 错误 | `#ff3b30` | `#ff6b61` |
|
|
56
|
-
| 成功 | `#2f9e44` | `#4fbf5c` |
|
|
57
|
-
| 警告 | `#d9822b` | `#ffb700` |
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## 三、强调色的派生角色
|
|
62
|
-
|
|
63
|
-
一个强调色不够用。同一个色在不同表面上需要不同的取值,这些派生量在样式表里都是 CSS 变量,**声明在 `body` 上**(原因见[工程笔记](engineering-notes.md#变量必须声明在-body-而不是-root))。
|
|
64
|
-
|
|
65
|
-
| 变量 | 谷地黄 | 武陵青 | 用途 |
|
|
66
|
-
| --- | --- | --- | --- |
|
|
67
|
-
| `--edge-accent` | `#fff500` | `#14d0d0` | 实心底、图标、焦点环、光标 |
|
|
68
|
-
| `--edge-accent-rgb` | `255, 245, 0` | `20, 208, 208` | 约 30 处 `rgba(var(--edge-accent-rgb), α)` 半透明色块 |
|
|
69
|
-
| `--edge-accent-deep` | `#e8e000` | `#10b8b8` | 悬停 / 按下加深 |
|
|
70
|
-
| `--edge-accent-onpaper` | `#d9c700` | `#14d0d0` | 亮色模式下唯一需要「作填充压在纸上」的槽位 |
|
|
71
|
-
| `--edge-status-light` | `#6b5d00` | `#006a6a` | 亮色模式回合状态文字(0.2 经 `--dsw-alias-label-deep-diving` 生效) |
|
|
72
|
-
| `--edge-status-light-mid` | `#3f3600` | `#003f3f` | 亮色模式流光亮带(0.2 经 `--dsw-alias-label-deep-diving-shimmer` 生效) |
|
|
73
|
-
| `--edge-status-dark` | `#fff500` | `#14d0d0` | 暗色模式回合状态文字(同上) |
|
|
74
|
-
| `--edge-status-dark-mid` | `#a08a00` | `#7ee7e7` | 暗色模式流光亮带(同上) |
|
|
75
|
-
| `--edge-glow-light` | `0.08` | `0.08` | hero 背景光晕透明度 |
|
|
76
|
-
| `--edge-glow-dark` | `0.05` | `0.04` | 同上,暗色 |
|
|
77
|
-
|
|
78
|
-
### 为什么亮色模式的强调色要「下沉」
|
|
79
|
-
|
|
80
|
-
这是最容易踩的一条。**在奶油纸底上,`#fff500` 不是一种配色选择,而是一次抹除**——黄色文字压在纸底上只有 1.07:1(面板 `#f2f2ec` 上更低,1.02:1),完全不可见。同理青色也过不了:`#14d0d0` 在纸底上仅 1.56:1。
|
|
81
|
-
|
|
82
|
-
所以亮色模式里凡是「强调色作为文字」的地方,都换成同一色相的**深色档**:
|
|
83
|
-
|
|
84
|
-
- 谷地黄 → `#6b5d00`(5.35:1,亮色下第一个达标的金)
|
|
85
|
-
- 武陵青 → `#006a6a`(5.22:1)
|
|
86
|
-
|
|
87
|
-
在纸上,`#007070` 以上的青都过不了双底 AA——这是**纸的属性**,不是强调色亮度的问题,所以武陵青提亮后这两个值完全没变。
|
|
88
|
-
|
|
89
|
-
而暗色模式方向相反:底是近黑,强调色本身就够亮,直接用原色。
|
|
90
|
-
|
|
91
|
-
### 为什么暗色的「亮带」方向不一致
|
|
92
|
-
|
|
93
|
-
回合状态标签(0.1.x 是渐变流光文字,0.2 是遮罩扫光文字,见[工程笔记](engineering-notes.md#四类回合状态标签))中间有一条亮带扫过字形。
|
|
94
|
-
|
|
95
|
-
- 谷地黄的亮带是**压暗**的 `#a08a00`——黄色向上已经到顶,再亮就成白色。
|
|
96
|
-
- 武陵青的亮带是**提亮**的 `#7ee7e7`——青色在这个亮度向下的余量比黄色小,更深的青(`#0a7d7d`)只有 3.54:1 不达标。
|
|
97
|
-
|
|
98
|
-
同一个规则(AA 4.5:1),两个相反的方向。这类「不能照抄」的地方是本主题最主要的复杂度来源。
|
|
99
|
-
|
|
100
|
-
### 武陵青为什么是 `#14d0d0`
|
|
101
|
-
|
|
102
|
-
初版用了 `#0daaaa`,相对亮度只有 31.7%——不到谷地黄(86.6%)的四成,在近黑页面上存在感明显弱一截。`#14d0d0` 保持同一条色轴(R 低、G = B,仍是青碧而非灰青),亮度提高到 49.8%。
|
|
103
|
-
|
|
104
|
-
上界同样有约束:到 `#16dcdc` 时,亮色模式下色块与纸底只剩 1.39:1(现行 1.56:1),开始「融进纸里」,而且那么浅的青读起来偏白。**`#14d0d0` 是两种模式下都还站得住的最亮一档。**
|
|
105
|
-
|
|
106
|
-
这两条边界写成了断言(亮度下限 45%、上限 56%、必须留在青碧色轴上)。
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## 四、对比度规则
|
|
111
|
-
|
|
112
|
-
**每个角色都要对它实际落在的表面验算,不达标就构建失败。** `test/palette-contrast.test.js` 覆盖全部 27 项。
|
|
113
|
-
|
|
114
|
-
| 角色 | 谷地黄 | 武陵青 | 门槛 |
|
|
115
|
-
| --- | --- | --- | --- |
|
|
116
|
-
| 实心强调底 + 墨色字 | 16.50:1 | 9.88:1 | AA 4.5 |
|
|
117
|
-
| 悬停加深底 + 墨色字 | 13.60:1 | 7.72:1 | AA 4.5 |
|
|
118
|
-
| 暗色图标 / 链接强调色 | 15.26:1 | 9.14:1 | ≥3 |
|
|
119
|
-
| 回合状态·亮色主色 | 5.35:1 | 5.22:1 | AA 4.5 |
|
|
120
|
-
| 回合状态·亮色亮带 | 9.82:1 | 9.58:1 | AA 4.5 |
|
|
121
|
-
| 回合状态·暗色主色 | 15.26:1 | 9.14:1 | AA 4.5 |
|
|
122
|
-
| 回合状态·暗色亮带 | 5.11:1 | 12.06:1 | AA 4.5 |
|
|
123
|
-
|
|
124
|
-
三条附加规则:
|
|
125
|
-
|
|
126
|
-
- **回合状态要对两种底色都达标**(`bg-base` 与 `bg-layer-1`),因为亮带会扫过字形;0.1.x 的渐变文字在 `prefers-reduced-motion` 下被上游把 `background-size` 钉成 100%,那条亮带会**永久留在**字里,0.2 的遮罩扫光同样要把两个令牌都按「会压在字形上」来取值。
|
|
127
|
-
- **两配色的等高线合成对比度相差 ≤20%**:相同 alpha ≠ 相同存在感,所以 alpha 按「合成后对比度对齐」分别调参,而不是照抄。
|
|
128
|
-
- **hero 光晕只许更轻,不许更响**:判据是单侧的——比它替换的 `#6187D8` @8% 更安静永远可接受。
|
|
129
|
-
|
|
130
|
-
> **装饰性表面的对比度描述的是「有多抢眼」,不是可读性。** 水印永远在正文之后,等高线在所有内容之下,它们的对比度数值用来控制干扰强度。经验下限约 `1.06:1`,低于此形同不存在。
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 五、令牌映射:中和残留品牌蓝
|
|
135
|
-
|
|
136
|
-
DSH 自带 DeepSeek 品牌蓝。主题把这些令牌整组重映射,否则会在纸墨界面里留下零散的蓝点。
|
|
137
|
-
|
|
138
|
-
| 令牌组 | 处理 |
|
|
139
|
-
| --- | --- |
|
|
140
|
-
| `--dsw-static-deepseek-50…900` | 整组换成中性灰阶(亮色为纸阶,暗色为墨阶) |
|
|
141
|
-
| `--dsw-static-deepseek-450` | 亮色 → `--edge-accent-onpaper`,暗色 → `--edge-accent` |
|
|
142
|
-
| `--dsw-static-deepseek-500/600` | → 墨 / 纸色(**不可动**,见下) |
|
|
143
|
-
| `--dsw-alias-button-info-fill` | 亮色 → 墨黑,暗色 → 强调色 |
|
|
144
|
-
| `--dsw-alias-state-business-primary` | 同上 |
|
|
145
|
-
| `--dsw-specific-sidebar-nav-item-active-accent` | 同上 |
|
|
146
|
-
| `--dsw-alias-label-primary-bluish` | → 正文色 |
|
|
147
|
-
| `--dsw-specific-bubble` / `-highlight` | → 面板 / 沉底块 |
|
|
148
|
-
|
|
149
|
-
`--dsw-static-deepseek-500/200` 是**共享令牌**:同时支撑 `button-info-fill`、`state-business-primary` 与 `bubble-highlight`。要改回合状态标签的颜色时不能动它们,只能覆盖那条规则自己的 `background-image`。
|
|
150
|
-
|
|
151
|
-
### 强调色交互一览
|
|
152
|
-
|
|
153
|
-
| 表面 | 声明 |
|
|
154
|
-
| --- | --- |
|
|
155
|
-
| 文本选中 | `::selection { color:#000; background: var(--edge-accent) }` |
|
|
156
|
-
| 输入光标 | `caret-color: var(--edge-accent)` |
|
|
157
|
-
| 焦点环 | `outline: 2px solid var(--edge-accent)`,`offset: 1px` |
|
|
158
|
-
| 滚动条 | 轨道透明,滑块 `--edge-line`,悬停 `--edge-accent` |
|
|
159
|
-
| 链接悬停 | 下划线着色;暗色模式下文字也变强调色 |
|
|
160
|
-
| 表格行悬停 | 实心强调底 + 墨色字 |
|
|
161
|
-
| 半透明色块 | `rgba(var(--edge-accent-rgb), α)`,亮色 α 略低于暗色 |
|
|
162
|
-
|
|
163
|
-
亮色与暗色的 α 不同:**在近黑底上叠加亮度,比在纸底上减去亮度显得响得多**。同一个 α 在暗色下更吵,所以暗色的半透明块普遍取更高的 α(因为强调色本身在暗色下要更突出),而装饰性图层(水印)在暗色下反而要**降低** α。
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
## 六、直角化怎么实现
|
|
168
|
-
|
|
169
|
-
直角不是逐个组件改,而是**先全部清零,再恢复该圆的**:
|
|
170
|
-
|
|
171
|
-
```css
|
|
172
|
-
body:not(.theme-endfield-round) [class] { border-radius: 0 !important; }
|
|
173
|
-
body:not(.theme-endfield-round) [class*='avatar'],
|
|
174
|
-
body:not(.theme-endfield-round) [class*='spinner'],
|
|
175
|
-
body:not(.theme-endfield-round) [class*='dot'],
|
|
176
|
-
body:not(.theme-endfield-round) [class$='_iconButton'] { border-radius: 50% !important; }
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
用 `[class]` 而非 `*`,避免命中无类名的结构性节点;用 `body:not(.theme-endfield-round)` 作前缀,让「主题圆角」开关只是 `<body>` 上加减一个 class,不需要重新注册样式表。
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## 七、配色切换是一个 class
|
|
184
|
-
|
|
185
|
-
整份样式表里所有强调色都读**调色板变量**,所以切换配色只是 `<body>` 上加减一个 class:不重新注册令牌层、不重算样式、无 JS 重绘。
|
|
186
|
-
|
|
187
|
-
这一点能成立依赖一个事实:**应用是把主题令牌写成 `<body>` 的行内样式的**(`dsh-client-ui-layout` 对每个令牌调 `body.style.setProperty`)。因此主题把 `--dsw-alias-brand-primary` 的暗色值声明为 `var(--edge-accent)`,该令牌就随 class 自动重解析。
|
|
188
|
-
|
|
189
|
-
唯一需要 JS 介入的表面是**等高线画布**——canvas 描边不能是 CSS 变量,所以它由一个监听 `<body>` class 的 `MutationObserver` 触发重绘(顺带让「别的标签页改了配色」也能跟着变)。
|
|
190
|
-
|
|
191
|
-
配色切换后的实测像素占比(`test/verify-shots.js` 解码真实截图统计):
|
|
192
|
-
|
|
193
|
-
| 截图 | 黄色像素 | 青色像素 |
|
|
194
|
-
| --- | --- | --- |
|
|
195
|
-
| 谷地黄 · 亮色 | **5.47%** | 0.44% |
|
|
196
|
-
| 武陵青 · 亮色 | 0.39% | **6.51%** |
|
|
197
|
-
| 谷地黄 · 暗色 | **2.51%** | 0.42% |
|
|
198
|
-
| 武陵青 · 暗色 | 0.34% | **5.40%** |
|
|
199
|
-
|
|
200
|
-
约 93% 的中性像素(纸底、墨字、边框)在两版之间基本不变——切换只动强调色。
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## 八、可复用的几条经验
|
|
205
|
-
|
|
206
|
-
如果只带走几句话:
|
|
207
|
-
|
|
208
|
-
1. **颜色是量出来的,不是挑出来的。** 每个色值都对它实际落在的表面算过对比度;不达标的写法(哪怕看起来「就该是这个颜色」)一律改掉。
|
|
209
|
-
2. **同一个色在亮/暗模式下往往需要相反的处理。** 亮色下沉、暗色提亮,或者反过来,取决于该色相在那个亮度还剩多少余量。
|
|
210
|
-
3. **「装饰」和「前景」用两套标准。** 前景守 AA 4.5:1;装饰守「不抢眼但可见」的区间(约 1.06 ~ 1.6:1)。
|
|
211
|
-
4. **主题令牌自身可以是 `var()` 引用。** 这是把「换配色」从一次重绘降级成一次 class 切换的关键。
|
|
212
|
-
5. **底色归主题、文字归应用 = 裂缝。** 只接管背景令牌而不接管前景,会造出「白字压在黄底上」这类 1.05:1 的不可见组合。改一个背景令牌时,要一起检查谁在上面写字。
|
|
1
|
+
# 设计语言
|
|
2
|
+
|
|
3
|
+
本文是这套主题的设计说明:**颜色是什么、为什么是这个值、落在哪个界面角色上**。想拿它当设计语言学习入口的,读这一篇就够。
|
|
4
|
+
|
|
5
|
+
工程实现细节在 [engineering-notes.md](engineering-notes.md),测试在 [testing.md](testing.md)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、核心概念:工业编辑风
|
|
10
|
+
|
|
11
|
+
终末地官网的视觉不是「深色科技感」,而是**印刷编辑风**:一张有质感的纸、压在上面的墨黑字、以及一个用来做信号的高饱和色。三条规则贯穿全篇:
|
|
12
|
+
|
|
13
|
+
1. **纸与墨是主体,强调色是信号。** 强调色只用于「需要被看见的那一处」——按钮、悬停、选中行、焦点环——而不是大面积铺色。
|
|
14
|
+
2. **全直角。** 圆角会把界面推向「柔和产品」的观感;直角把它推向「技术文档 / 工程图纸」。只有形状本身是圆的(状态点、头像、加载圈)才保留圆形。
|
|
15
|
+
3. **数字等宽。** 开启 `tnum`,让表格、耗时、百分比在跳动时不产生横向抖动。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 二、色板
|
|
20
|
+
|
|
21
|
+
### 中性色(纸 / 墨 / 线)
|
|
22
|
+
|
|
23
|
+
两套模式共用同一套语义,只换取值。
|
|
24
|
+
|
|
25
|
+
| 角色 | 令牌 | 亮色 | 暗色 |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| 纸底(应用背景、侧栏) | `--dsw-alias-bg-base` | `#e8e8e2` | `#101110` |
|
|
28
|
+
| 面板 / 气泡 | `--dsw-alias-bg-layer-1` | `#f2f2ec` | `#181a18` |
|
|
29
|
+
| 沉底块 | `--dsw-alias-bg-layer-2` | `#dcddd6` | `#1e201d` |
|
|
30
|
+
| 浮层(菜单、弹层) | `--dsw-alias-bg-overlay` | `#f2f2ec` | `#1c1e1c` |
|
|
31
|
+
| 细线 | `--dsw-alias-border-l1` | `#d8d9d5` | `#343633` |
|
|
32
|
+
| 重线 | `--dsw-alias-border-l2` | `#b6b8b3` | `#4a4d49` |
|
|
33
|
+
| 正文 | `--dsw-alias-label-primary` | `#101110` | `#f5f5f0` |
|
|
34
|
+
| 次要文字 | `--dsw-alias-label-secondary` | `#4a4c48` | `#898d89` |
|
|
35
|
+
|
|
36
|
+
亮色不用纯白 `#fff`、暗色不用纯黑 `#000`:`#e8e8e2` 带一点暖灰,是「纸」而不是「屏幕」;`#101110` 留一点绿倾向,避免深灰界面常见的死黑。
|
|
37
|
+
|
|
38
|
+
### 强调色:两套配色
|
|
39
|
+
|
|
40
|
+
设置页「主题配色」在两者间切换,**默认谷地黄**。
|
|
41
|
+
|
|
42
|
+
| 配色 | 强调色 | 相对亮度 | 说明 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| **谷地黄**(默认) | `#fff500` | 86.6% | 终末地官网的信号黄 |
|
|
45
|
+
| **武陵青** | `#14d0d0` | 49.8% | 青碧色 |
|
|
46
|
+
|
|
47
|
+
存储字段 `palette`(UI/存储键 `dsh-theme-endfield-palette`,由 `PREFS_KEY_TO_FIELD` 映射),取值 `valley` / `wuling`。只有精确值 `wuling` 选中武陵青,未设置或异常值都回落到默认。
|
|
48
|
+
|
|
49
|
+
### 语义状态色
|
|
50
|
+
|
|
51
|
+
不参与配色切换——错误就该是红的。
|
|
52
|
+
|
|
53
|
+
| 角色 | 亮色 | 暗色 |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| 错误 | `#ff3b30` | `#ff6b61` |
|
|
56
|
+
| 成功 | `#2f9e44` | `#4fbf5c` |
|
|
57
|
+
| 警告 | `#d9822b` | `#ffb700` |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 三、强调色的派生角色
|
|
62
|
+
|
|
63
|
+
一个强调色不够用。同一个色在不同表面上需要不同的取值,这些派生量在样式表里都是 CSS 变量,**声明在 `body` 上**(原因见[工程笔记](engineering-notes.md#变量必须声明在-body-而不是-root))。
|
|
64
|
+
|
|
65
|
+
| 变量 | 谷地黄 | 武陵青 | 用途 |
|
|
66
|
+
| --- | --- | --- | --- |
|
|
67
|
+
| `--edge-accent` | `#fff500` | `#14d0d0` | 实心底、图标、焦点环、光标 |
|
|
68
|
+
| `--edge-accent-rgb` | `255, 245, 0` | `20, 208, 208` | 约 30 处 `rgba(var(--edge-accent-rgb), α)` 半透明色块 |
|
|
69
|
+
| `--edge-accent-deep` | `#e8e000` | `#10b8b8` | 悬停 / 按下加深 |
|
|
70
|
+
| `--edge-accent-onpaper` | `#d9c700` | `#14d0d0` | 亮色模式下唯一需要「作填充压在纸上」的槽位 |
|
|
71
|
+
| `--edge-status-light` | `#6b5d00` | `#006a6a` | 亮色模式回合状态文字(0.2 经 `--dsw-alias-label-deep-diving` 生效) |
|
|
72
|
+
| `--edge-status-light-mid` | `#3f3600` | `#003f3f` | 亮色模式流光亮带(0.2 经 `--dsw-alias-label-deep-diving-shimmer` 生效) |
|
|
73
|
+
| `--edge-status-dark` | `#fff500` | `#14d0d0` | 暗色模式回合状态文字(同上) |
|
|
74
|
+
| `--edge-status-dark-mid` | `#a08a00` | `#7ee7e7` | 暗色模式流光亮带(同上) |
|
|
75
|
+
| `--edge-glow-light` | `0.08` | `0.08` | hero 背景光晕透明度 |
|
|
76
|
+
| `--edge-glow-dark` | `0.05` | `0.04` | 同上,暗色 |
|
|
77
|
+
|
|
78
|
+
### 为什么亮色模式的强调色要「下沉」
|
|
79
|
+
|
|
80
|
+
这是最容易踩的一条。**在奶油纸底上,`#fff500` 不是一种配色选择,而是一次抹除**——黄色文字压在纸底上只有 1.07:1(面板 `#f2f2ec` 上更低,1.02:1),完全不可见。同理青色也过不了:`#14d0d0` 在纸底上仅 1.56:1。
|
|
81
|
+
|
|
82
|
+
所以亮色模式里凡是「强调色作为文字」的地方,都换成同一色相的**深色档**:
|
|
83
|
+
|
|
84
|
+
- 谷地黄 → `#6b5d00`(5.35:1,亮色下第一个达标的金)
|
|
85
|
+
- 武陵青 → `#006a6a`(5.22:1)
|
|
86
|
+
|
|
87
|
+
在纸上,`#007070` 以上的青都过不了双底 AA——这是**纸的属性**,不是强调色亮度的问题,所以武陵青提亮后这两个值完全没变。
|
|
88
|
+
|
|
89
|
+
而暗色模式方向相反:底是近黑,强调色本身就够亮,直接用原色。
|
|
90
|
+
|
|
91
|
+
### 为什么暗色的「亮带」方向不一致
|
|
92
|
+
|
|
93
|
+
回合状态标签(0.1.x 是渐变流光文字,0.2 是遮罩扫光文字,见[工程笔记](engineering-notes.md#四类回合状态标签))中间有一条亮带扫过字形。
|
|
94
|
+
|
|
95
|
+
- 谷地黄的亮带是**压暗**的 `#a08a00`——黄色向上已经到顶,再亮就成白色。
|
|
96
|
+
- 武陵青的亮带是**提亮**的 `#7ee7e7`——青色在这个亮度向下的余量比黄色小,更深的青(`#0a7d7d`)只有 3.54:1 不达标。
|
|
97
|
+
|
|
98
|
+
同一个规则(AA 4.5:1),两个相反的方向。这类「不能照抄」的地方是本主题最主要的复杂度来源。
|
|
99
|
+
|
|
100
|
+
### 武陵青为什么是 `#14d0d0`
|
|
101
|
+
|
|
102
|
+
初版用了 `#0daaaa`,相对亮度只有 31.7%——不到谷地黄(86.6%)的四成,在近黑页面上存在感明显弱一截。`#14d0d0` 保持同一条色轴(R 低、G = B,仍是青碧而非灰青),亮度提高到 49.8%。
|
|
103
|
+
|
|
104
|
+
上界同样有约束:到 `#16dcdc` 时,亮色模式下色块与纸底只剩 1.39:1(现行 1.56:1),开始「融进纸里」,而且那么浅的青读起来偏白。**`#14d0d0` 是两种模式下都还站得住的最亮一档。**
|
|
105
|
+
|
|
106
|
+
这两条边界写成了断言(亮度下限 45%、上限 56%、必须留在青碧色轴上)。
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 四、对比度规则
|
|
111
|
+
|
|
112
|
+
**每个角色都要对它实际落在的表面验算,不达标就构建失败。** `test/palette-contrast.test.js` 覆盖全部 27 项。
|
|
113
|
+
|
|
114
|
+
| 角色 | 谷地黄 | 武陵青 | 门槛 |
|
|
115
|
+
| --- | --- | --- | --- |
|
|
116
|
+
| 实心强调底 + 墨色字 | 16.50:1 | 9.88:1 | AA 4.5 |
|
|
117
|
+
| 悬停加深底 + 墨色字 | 13.60:1 | 7.72:1 | AA 4.5 |
|
|
118
|
+
| 暗色图标 / 链接强调色 | 15.26:1 | 9.14:1 | ≥3 |
|
|
119
|
+
| 回合状态·亮色主色 | 5.35:1 | 5.22:1 | AA 4.5 |
|
|
120
|
+
| 回合状态·亮色亮带 | 9.82:1 | 9.58:1 | AA 4.5 |
|
|
121
|
+
| 回合状态·暗色主色 | 15.26:1 | 9.14:1 | AA 4.5 |
|
|
122
|
+
| 回合状态·暗色亮带 | 5.11:1 | 12.06:1 | AA 4.5 |
|
|
123
|
+
|
|
124
|
+
三条附加规则:
|
|
125
|
+
|
|
126
|
+
- **回合状态要对两种底色都达标**(`bg-base` 与 `bg-layer-1`),因为亮带会扫过字形;0.1.x 的渐变文字在 `prefers-reduced-motion` 下被上游把 `background-size` 钉成 100%,那条亮带会**永久留在**字里,0.2 的遮罩扫光同样要把两个令牌都按「会压在字形上」来取值。
|
|
127
|
+
- **两配色的等高线合成对比度相差 ≤20%**:相同 alpha ≠ 相同存在感,所以 alpha 按「合成后对比度对齐」分别调参,而不是照抄。
|
|
128
|
+
- **hero 光晕只许更轻,不许更响**:判据是单侧的——比它替换的 `#6187D8` @8% 更安静永远可接受。
|
|
129
|
+
|
|
130
|
+
> **装饰性表面的对比度描述的是「有多抢眼」,不是可读性。** 水印永远在正文之后,等高线在所有内容之下,它们的对比度数值用来控制干扰强度。经验下限约 `1.06:1`,低于此形同不存在。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 五、令牌映射:中和残留品牌蓝
|
|
135
|
+
|
|
136
|
+
DSH 自带 DeepSeek 品牌蓝。主题把这些令牌整组重映射,否则会在纸墨界面里留下零散的蓝点。
|
|
137
|
+
|
|
138
|
+
| 令牌组 | 处理 |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `--dsw-static-deepseek-50…900` | 整组换成中性灰阶(亮色为纸阶,暗色为墨阶) |
|
|
141
|
+
| `--dsw-static-deepseek-450` | 亮色 → `--edge-accent-onpaper`,暗色 → `--edge-accent` |
|
|
142
|
+
| `--dsw-static-deepseek-500/600` | → 墨 / 纸色(**不可动**,见下) |
|
|
143
|
+
| `--dsw-alias-button-info-fill` | 亮色 → 墨黑,暗色 → 强调色 |
|
|
144
|
+
| `--dsw-alias-state-business-primary` | 同上 |
|
|
145
|
+
| `--dsw-specific-sidebar-nav-item-active-accent` | 同上 |
|
|
146
|
+
| `--dsw-alias-label-primary-bluish` | → 正文色 |
|
|
147
|
+
| `--dsw-specific-bubble` / `-highlight` | → 面板 / 沉底块 |
|
|
148
|
+
|
|
149
|
+
`--dsw-static-deepseek-500/200` 是**共享令牌**:同时支撑 `button-info-fill`、`state-business-primary` 与 `bubble-highlight`。要改回合状态标签的颜色时不能动它们,只能覆盖那条规则自己的 `background-image`。
|
|
150
|
+
|
|
151
|
+
### 强调色交互一览
|
|
152
|
+
|
|
153
|
+
| 表面 | 声明 |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| 文本选中 | `::selection { color:#000; background: var(--edge-accent) }` |
|
|
156
|
+
| 输入光标 | `caret-color: var(--edge-accent)` |
|
|
157
|
+
| 焦点环 | `outline: 2px solid var(--edge-accent)`,`offset: 1px` |
|
|
158
|
+
| 滚动条 | 轨道透明,滑块 `--edge-line`,悬停 `--edge-accent` |
|
|
159
|
+
| 链接悬停 | 下划线着色;暗色模式下文字也变强调色 |
|
|
160
|
+
| 表格行悬停 | 实心强调底 + 墨色字 |
|
|
161
|
+
| 半透明色块 | `rgba(var(--edge-accent-rgb), α)`,亮色 α 略低于暗色 |
|
|
162
|
+
|
|
163
|
+
亮色与暗色的 α 不同:**在近黑底上叠加亮度,比在纸底上减去亮度显得响得多**。同一个 α 在暗色下更吵,所以暗色的半透明块普遍取更高的 α(因为强调色本身在暗色下要更突出),而装饰性图层(水印)在暗色下反而要**降低** α。
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 六、直角化怎么实现
|
|
168
|
+
|
|
169
|
+
直角不是逐个组件改,而是**先全部清零,再恢复该圆的**:
|
|
170
|
+
|
|
171
|
+
```css
|
|
172
|
+
body:not(.theme-endfield-round) [class] { border-radius: 0 !important; }
|
|
173
|
+
body:not(.theme-endfield-round) [class*='avatar'],
|
|
174
|
+
body:not(.theme-endfield-round) [class*='spinner'],
|
|
175
|
+
body:not(.theme-endfield-round) [class*='dot'],
|
|
176
|
+
body:not(.theme-endfield-round) [class$='_iconButton'] { border-radius: 50% !important; }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
用 `[class]` 而非 `*`,避免命中无类名的结构性节点;用 `body:not(.theme-endfield-round)` 作前缀,让「主题圆角」开关只是 `<body>` 上加减一个 class,不需要重新注册样式表。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 七、配色切换是一个 class
|
|
184
|
+
|
|
185
|
+
整份样式表里所有强调色都读**调色板变量**,所以切换配色只是 `<body>` 上加减一个 class:不重新注册令牌层、不重算样式、无 JS 重绘。
|
|
186
|
+
|
|
187
|
+
这一点能成立依赖一个事实:**应用是把主题令牌写成 `<body>` 的行内样式的**(`dsh-client-ui-layout` 对每个令牌调 `body.style.setProperty`)。因此主题把 `--dsw-alias-brand-primary` 的暗色值声明为 `var(--edge-accent)`,该令牌就随 class 自动重解析。
|
|
188
|
+
|
|
189
|
+
唯一需要 JS 介入的表面是**等高线画布**——canvas 描边不能是 CSS 变量,所以它由一个监听 `<body>` class 的 `MutationObserver` 触发重绘(顺带让「别的标签页改了配色」也能跟着变)。
|
|
190
|
+
|
|
191
|
+
配色切换后的实测像素占比(`test/verify-shots.js` 解码真实截图统计):
|
|
192
|
+
|
|
193
|
+
| 截图 | 黄色像素 | 青色像素 |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| 谷地黄 · 亮色 | **5.47%** | 0.44% |
|
|
196
|
+
| 武陵青 · 亮色 | 0.39% | **6.51%** |
|
|
197
|
+
| 谷地黄 · 暗色 | **2.51%** | 0.42% |
|
|
198
|
+
| 武陵青 · 暗色 | 0.34% | **5.40%** |
|
|
199
|
+
|
|
200
|
+
约 93% 的中性像素(纸底、墨字、边框)在两版之间基本不变——切换只动强调色。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 八、可复用的几条经验
|
|
205
|
+
|
|
206
|
+
如果只带走几句话:
|
|
207
|
+
|
|
208
|
+
1. **颜色是量出来的,不是挑出来的。** 每个色值都对它实际落在的表面算过对比度;不达标的写法(哪怕看起来「就该是这个颜色」)一律改掉。
|
|
209
|
+
2. **同一个色在亮/暗模式下往往需要相反的处理。** 亮色下沉、暗色提亮,或者反过来,取决于该色相在那个亮度还剩多少余量。
|
|
210
|
+
3. **「装饰」和「前景」用两套标准。** 前景守 AA 4.5:1;装饰守「不抢眼但可见」的区间(约 1.06 ~ 1.6:1)。
|
|
211
|
+
4. **主题令牌自身可以是 `var()` 引用。** 这是把「换配色」从一次重绘降级成一次 class 切换的关键。
|
|
212
|
+
5. **底色归主题、文字归应用 = 裂缝。** 只接管背景令牌而不接管前景,会造出「白字压在黄底上」这类 1.05:1 的不可见组合。改一个背景令牌时,要一起检查谁在上面写字。
|