agentcss 0.0.0-stage → 0.11.2

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.
Files changed (274) hide show
  1. package/01-tokens/README.md +236 -0
  2. package/01-tokens/behavior/dismissable.js +132 -0
  3. package/01-tokens/behavior/emit.js +76 -0
  4. package/01-tokens/behavior/focus-return.js +81 -0
  5. package/01-tokens/behavior/roving.js +152 -0
  6. package/01-tokens/behavior/typeahead.js +124 -0
  7. package/01-tokens/focus-ring-demo.html +133 -0
  8. package/01-tokens/focus-ring.css +102 -0
  9. package/01-tokens/page.css +244 -0
  10. package/01-tokens/polyfill.js +116 -0
  11. package/01-tokens/responsive-demo.html +171 -0
  12. package/01-tokens/scrollbar-demo.html +194 -0
  13. package/01-tokens/scrollbar.css +179 -0
  14. package/01-tokens/theme-toggle.js +103 -0
  15. package/01-tokens/tokens.css +972 -0
  16. package/01-tokens/typography.css +156 -0
  17. package/02-primitives/badge/README.md +71 -0
  18. package/02-primitives/badge/badge.css +141 -0
  19. package/02-primitives/badge/demo.html +101 -0
  20. package/02-primitives/button/README.md +99 -0
  21. package/02-primitives/button/button.css +353 -0
  22. package/02-primitives/button/demo.html +200 -0
  23. package/02-primitives/card/README.md +145 -0
  24. package/02-primitives/card/card.css +246 -0
  25. package/02-primitives/card/demo.html +170 -0
  26. package/02-primitives/choice/README.md +161 -0
  27. package/02-primitives/choice/choice.css +395 -0
  28. package/02-primitives/choice/demo.html +277 -0
  29. package/02-primitives/combobox/README.md +79 -0
  30. package/02-primitives/combobox/combobox.css +223 -0
  31. package/02-primitives/combobox/combobox.js +485 -0
  32. package/02-primitives/combobox/demo.html +109 -0
  33. package/02-primitives/date-range/README.md +100 -0
  34. package/02-primitives/date-range/date-range.css +206 -0
  35. package/02-primitives/date-range/date-range.js +301 -0
  36. package/02-primitives/date-range/demo.html +104 -0
  37. package/02-primitives/input/README.md +170 -0
  38. package/02-primitives/input/demo.html +246 -0
  39. package/02-primitives/input/input.css +239 -0
  40. package/02-primitives/popover/README.md +80 -0
  41. package/02-primitives/popover/demo.html +150 -0
  42. package/02-primitives/popover/popover.css +187 -0
  43. package/02-primitives/popover/popover.js +331 -0
  44. package/02-primitives/progress/README.md +82 -0
  45. package/02-primitives/progress/demo.html +154 -0
  46. package/02-primitives/progress/progress.css +172 -0
  47. package/02-primitives/select/README.md +95 -0
  48. package/02-primitives/select/demo.html +121 -0
  49. package/02-primitives/select/select.css +151 -0
  50. package/02-primitives/select/select.js +549 -0
  51. package/02-primitives/separator/README.md +63 -0
  52. package/02-primitives/separator/demo.html +84 -0
  53. package/02-primitives/separator/separator.css +73 -0
  54. package/02-primitives/skeleton/README.md +69 -0
  55. package/02-primitives/skeleton/demo.html +110 -0
  56. package/02-primitives/skeleton/skeleton.css +96 -0
  57. package/02-primitives/switch/README.md +65 -0
  58. package/02-primitives/switch/demo.html +115 -0
  59. package/02-primitives/switch/switch.css +370 -0
  60. package/03-patterns/accordion/README.md +121 -0
  61. package/03-patterns/accordion/accordion.css +453 -0
  62. package/03-patterns/accordion/accordion.js +369 -0
  63. package/03-patterns/accordion/demo.html +223 -0
  64. package/03-patterns/content/README.md +103 -0
  65. package/03-patterns/content/content.css +286 -0
  66. package/03-patterns/content/demo.html +88 -0
  67. package/03-patterns/drawer/README.md +79 -0
  68. package/03-patterns/drawer/demo.html +101 -0
  69. package/03-patterns/drawer/drawer.css +129 -0
  70. package/03-patterns/drawer/drawer.js +258 -0
  71. package/03-patterns/dropdown/README.md +78 -0
  72. package/03-patterns/dropdown/demo.html +101 -0
  73. package/03-patterns/dropdown/dropdown.css +135 -0
  74. package/03-patterns/dropdown/dropdown.js +451 -0
  75. package/03-patterns/form-validation/README.md +147 -0
  76. package/03-patterns/form-validation/demo.html +280 -0
  77. package/03-patterns/form-validation/form-validation.css +263 -0
  78. package/03-patterns/list/README.md +150 -0
  79. package/03-patterns/list/demo.html +225 -0
  80. package/03-patterns/list/flip.js +201 -0
  81. package/03-patterns/list/list.css +162 -0
  82. package/03-patterns/nav/README.md +225 -0
  83. package/03-patterns/nav/demo.html +131 -0
  84. package/03-patterns/nav/nav.css +314 -0
  85. package/03-patterns/nav/nav.js +430 -0
  86. package/03-patterns/nav/toc.js +111 -0
  87. package/03-patterns/overlay/README.md +196 -0
  88. package/03-patterns/overlay/demo.html +241 -0
  89. package/03-patterns/overlay/overlay.css +319 -0
  90. package/03-patterns/overlay/overlay.js +563 -0
  91. package/03-patterns/pagination/README.md +114 -0
  92. package/03-patterns/pagination/demo.html +165 -0
  93. package/03-patterns/pagination/pagination.css +195 -0
  94. package/03-patterns/pagination/pagination.js +395 -0
  95. package/03-patterns/reveal/README.md +91 -0
  96. package/03-patterns/reveal/demo.html +106 -0
  97. package/03-patterns/reveal/reveal.css +93 -0
  98. package/03-patterns/reveal/reveal.js +172 -0
  99. package/03-patterns/states/README.md +141 -0
  100. package/03-patterns/states/demo.html +240 -0
  101. package/03-patterns/states/states.css +252 -0
  102. package/03-patterns/tabs/README.md +84 -0
  103. package/03-patterns/tabs/demo.html +139 -0
  104. package/03-patterns/tabs/tabs.css +172 -0
  105. package/03-patterns/tabs/tabs.js +307 -0
  106. package/03-patterns/tooltip/README.md +96 -0
  107. package/03-patterns/tooltip/demo.html +78 -0
  108. package/03-patterns/tooltip/tooltip.css +89 -0
  109. package/03-patterns/tooltip/tooltip.js +288 -0
  110. package/03-patterns/tree/README.md +94 -0
  111. package/03-patterns/tree/demo.html +138 -0
  112. package/03-patterns/tree/tree.css +113 -0
  113. package/03-patterns/tree/tree.js +456 -0
  114. package/04-recipes/analysis-report/README.md +80 -0
  115. package/04-recipes/analysis-report/analysis-report.css +187 -0
  116. package/04-recipes/analysis-report/demo.html +274 -0
  117. package/04-recipes/data-showcase/README.md +45 -0
  118. package/04-recipes/data-showcase/demo.html +247 -0
  119. package/04-recipes/detail-page/README.md +93 -0
  120. package/04-recipes/detail-page/demo.html +55 -0
  121. package/04-recipes/detail-page/detail-page.css +190 -0
  122. package/04-recipes/detail-page/full.html +60 -0
  123. package/04-recipes/detail-page/null.html +60 -0
  124. package/04-recipes/detail-page/partial.html +60 -0
  125. package/04-recipes/detail-page/verify-skeleton.py +19 -0
  126. package/04-recipes/longform/README.md +93 -0
  127. package/04-recipes/longform/agc-memory.svg +4 -0
  128. package/04-recipes/longform/content.html +610 -0
  129. package/04-recipes/longform/content.md +51 -0
  130. package/04-recipes/longform/longform.html +627 -0
  131. package/04-recipes/report/README.md +176 -0
  132. package/04-recipes/report/demo.html +355 -0
  133. package/04-recipes/report/report.css +364 -0
  134. package/04-recipes/table/README.md +103 -0
  135. package/04-recipes/table/demo.html +455 -0
  136. package/04-recipes/table/table.css +372 -0
  137. package/04-recipes/tierA-tokens.css +189 -0
  138. package/09-assets/README.md +74 -0
  139. package/09-assets/bar/README.md +94 -0
  140. package/09-assets/bar/bar.css +166 -0
  141. package/09-assets/bar/bar.js +320 -0
  142. package/09-assets/bar/demo.html +176 -0
  143. package/09-assets/echarts-adapter/demo.html +192 -0
  144. package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
  145. package/09-assets/model-viewer/README.md +155 -0
  146. package/09-assets/model-viewer/demo.html +105 -0
  147. package/09-assets/model-viewer/model-viewer.css +143 -0
  148. package/09-assets/model-viewer/model-viewer.js +423 -0
  149. package/09-assets/scientific-plot/README.md +94 -0
  150. package/09-assets/scientific-plot/demo.html +252 -0
  151. package/09-assets/scientific-plot/scientific-plot.css +141 -0
  152. package/09-assets/scientific-plot/scientific-plot.js +610 -0
  153. package/09-assets/sparkline/README.md +153 -0
  154. package/09-assets/sparkline/demo.html +163 -0
  155. package/09-assets/sparkline/sparkline.css +158 -0
  156. package/09-assets/sparkline/sparkline.js +508 -0
  157. package/API.md +332 -0
  158. package/CHANGELOG.md +3116 -0
  159. package/CODE_OF_CONDUCT.md +64 -0
  160. package/CONTRIBUTING.md +89 -0
  161. package/ERRORS.md +1658 -0
  162. package/LICENSE +21 -0
  163. package/README.md +491 -2
  164. package/SECURITY.md +72 -0
  165. package/START-HERE.md +164 -0
  166. package/SUPPORT.md +109 -0
  167. package/adapters/presentation/README.md +113 -0
  168. package/adapters/presentation/check.js +101 -0
  169. package/adapters/presentation/deck.schema.json +74 -0
  170. package/adapters/presentation/to-doc.js +138 -0
  171. package/ai/START-HERE.md +104 -0
  172. package/ai/behaviors.json +380 -0
  173. package/ai/behaviors.schema.json +61 -0
  174. package/ai/capabilities.json +56 -0
  175. package/ai/cli.js +387 -0
  176. package/ai/components.json +1944 -0
  177. package/ai/components.meta.json +168 -0
  178. package/ai/components.schema.json +220 -0
  179. package/ai/contract.schema.json +147 -0
  180. package/ai/deprecations.json +55 -0
  181. package/ai/deprecations.schema.json +85 -0
  182. package/ai/motion-policy.json +22 -0
  183. package/ai/patch.js +323 -0
  184. package/ai/patch.schema.json +63 -0
  185. package/ai/profiles.json +92 -0
  186. package/ai/tokens.audience.json +55 -0
  187. package/ai/tokens.audience.schema.json +73 -0
  188. package/ai/tokens.json +762 -0
  189. package/ai/tokens.schema.json +58 -0
  190. package/ai/tokens.tree.json +752 -0
  191. package/ai/tokens.tree.schema.json +56 -0
  192. package/ai/validate.js +383 -0
  193. package/dist/01-tokens/behavior/dismissable.js +60 -0
  194. package/dist/01-tokens/behavior/emit.js +21 -0
  195. package/dist/01-tokens/behavior/focus-return.js +31 -0
  196. package/dist/01-tokens/behavior/roving.js +77 -0
  197. package/dist/01-tokens/behavior/typeahead.js +61 -0
  198. package/dist/01-tokens/focus-ring.css +1 -0
  199. package/dist/01-tokens/page.css +1 -0
  200. package/dist/01-tokens/polyfill.js +78 -0
  201. package/dist/01-tokens/scrollbar.css +1 -0
  202. package/dist/01-tokens/theme-toggle.js +61 -0
  203. package/dist/01-tokens/tokens.css +1 -0
  204. package/dist/01-tokens/typography.css +1 -0
  205. package/dist/02-primitives/badge/badge.css +1 -0
  206. package/dist/02-primitives/button/button.css +1 -0
  207. package/dist/02-primitives/card/card.css +1 -0
  208. package/dist/02-primitives/choice/choice.css +1 -0
  209. package/dist/02-primitives/combobox/combobox.css +1 -0
  210. package/dist/02-primitives/combobox/combobox.js +439 -0
  211. package/dist/02-primitives/date-range/date-range.css +1 -0
  212. package/dist/02-primitives/date-range/date-range.js +267 -0
  213. package/dist/02-primitives/input/input.css +1 -0
  214. package/dist/02-primitives/popover/popover.css +1 -0
  215. package/dist/02-primitives/popover/popover.js +257 -0
  216. package/dist/02-primitives/progress/progress.css +1 -0
  217. package/dist/02-primitives/select/select.css +1 -0
  218. package/dist/02-primitives/select/select.js +449 -0
  219. package/dist/02-primitives/separator/separator.css +1 -0
  220. package/dist/02-primitives/skeleton/skeleton.css +1 -0
  221. package/dist/02-primitives/switch/switch.css +1 -0
  222. package/dist/03-patterns/accordion/accordion.css +1 -0
  223. package/dist/03-patterns/accordion/accordion.js +219 -0
  224. package/dist/03-patterns/content/content.css +1 -0
  225. package/dist/03-patterns/drawer/drawer.css +1 -0
  226. package/dist/03-patterns/drawer/drawer.js +229 -0
  227. package/dist/03-patterns/dropdown/dropdown.css +1 -0
  228. package/dist/03-patterns/dropdown/dropdown.js +365 -0
  229. package/dist/03-patterns/form-validation/form-validation.css +1 -0
  230. package/dist/03-patterns/list/flip.js +117 -0
  231. package/dist/03-patterns/list/list.css +1 -0
  232. package/dist/03-patterns/nav/nav.css +1 -0
  233. package/dist/03-patterns/nav/nav.js +322 -0
  234. package/dist/03-patterns/nav/toc.js +76 -0
  235. package/dist/03-patterns/overlay/overlay.css +1 -0
  236. package/dist/03-patterns/overlay/overlay.js +398 -0
  237. package/dist/03-patterns/pagination/pagination.css +1 -0
  238. package/dist/03-patterns/pagination/pagination.js +273 -0
  239. package/dist/03-patterns/reveal/reveal.css +1 -0
  240. package/dist/03-patterns/reveal/reveal.js +138 -0
  241. package/dist/03-patterns/states/states.css +1 -0
  242. package/dist/03-patterns/tabs/tabs.css +1 -0
  243. package/dist/03-patterns/tabs/tabs.js +242 -0
  244. package/dist/03-patterns/tooltip/tooltip.css +1 -0
  245. package/dist/03-patterns/tooltip/tooltip.js +213 -0
  246. package/dist/03-patterns/tree/tree.css +1 -0
  247. package/dist/03-patterns/tree/tree.js +383 -0
  248. package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
  249. package/dist/04-recipes/detail-page/detail-page.css +1 -0
  250. package/dist/04-recipes/report/report.css +1 -0
  251. package/dist/04-recipes/table/table.css +1 -0
  252. package/dist/04-recipes/tierA-tokens.css +1 -0
  253. package/dist/09-assets/bar/bar.css +1 -0
  254. package/dist/09-assets/bar/bar.js +222 -0
  255. package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
  256. package/dist/09-assets/model-viewer/model-viewer.css +1 -0
  257. package/dist/09-assets/model-viewer/model-viewer.js +320 -0
  258. package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
  259. package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
  260. package/dist/09-assets/sparkline/sparkline.css +1 -0
  261. package/dist/09-assets/sparkline/sparkline.js +265 -0
  262. package/dist/COSTS.md +56 -0
  263. package/docs/AI-CONTRACT.md +197 -0
  264. package/docs/BACKLOG.md +127 -0
  265. package/docs/BENCHMARK.md +252 -0
  266. package/docs/GUIDANCE.md +154 -0
  267. package/docs/INVARIANT.md +372 -0
  268. package/docs/LAYER-BASELINE.md +158 -0
  269. package/docs/RELEASING.md +275 -0
  270. package/docs/RFC-THEME-SCOPE.md +141 -0
  271. package/docs/ROADMAP.md +182 -0
  272. package/index.html +82 -0
  273. package/package.json +107 -4
  274. package/types/index.d.ts +310 -0
@@ -0,0 +1,972 @@
1
+ /* ============================================================================
2
+ frontend-lib / 01-tokens / tokens.css
3
+ 纸感中性基底 · 第一版
4
+
5
+ 铁律:这里的每一个颜色都必须带着 05-audit/contrast.py 的实测数字进来。
6
+ 凭感觉填的色一律不许入库 —— 第一版就抓出 5 项不达标(见文件末尾"废弃记录")。
7
+
8
+ 使用方式:
9
+ 1) 现代构建:直接 @import 或复制进你的样式入口
10
+ 2) 单文件版:只复制 [核心] 段,其余删掉,可内联进 <style>
11
+
12
+ 改色流程:改值 -> 跑 python 05-audit/contrast.py -> 把新数字写回注释 -> 才允许提交
13
+ ========================================================================= */
14
+
15
+ :root {
16
+ /* ================================================================ [L1] 原始值
17
+ ⚠️ **这一层不是给使用者用的**,它是"配色取值"本身。
18
+
19
+ 换一套品牌配色 = 只改这一块的 30 个值,L2 一行都不用动;
20
+ 暗色段也只覆盖这块(见文件末尾的 `@media (prefers-color-scheme: dark)`)。
21
+
22
+ 🔴 命名带 `--raw-` 是**刻意的**:它在告诉读代码的人
23
+ 「别在组件里 `var(--raw-*)`」—— 组件只该用 L2 的语义名。
24
+ `token-audience-gate` 把这一层登记为 internal。
25
+
26
+ ℹ️ 边界(别夸大它的能力):这是"取值层",**不是**与语义解耦的色阶标尺
27
+ (gray-50 / gray-100 那种)。真正的色阶标尺需要先做一次颜色聚类与命名
28
+ 决策,是独立的后续工程,见 `docs/ROADMAP.md` 的 D5b。
29
+ 本层解决的是"换肤只改一处 + 暗色不可能漏"这两件事。
30
+ ---------------------------------------------------------------- */
31
+ --raw-paper: #f6f7f8;
32
+ --raw-surface: #ffffff;
33
+ --raw-surface-sunken: #f1f3f5;
34
+ --raw-surface-raised: #ffffff;
35
+ --raw-scrollbar-thumb: #7A808A;
36
+ --raw-scrollbar-thumb-hov: #6E747E;
37
+ --raw-text-primary: #15181c;
38
+ --raw-text-secondary: #4f555e;
39
+ --raw-text-tertiary: #606770;
40
+ --raw-text-on-accent: #ffffff;
41
+ --raw-text-on-solid: #ffffff;
42
+ --raw-skeleton-bg: #e9ecef;
43
+ --raw-skeleton-shimmer: rgba(255, 255, 255, 0.72);
44
+ --raw-border-control: #868b94;
45
+ --raw-border-decor: #e2e5e9;
46
+ --raw-border-decor-str: #c8cdd4;
47
+ --raw-danger: #c0392b;
48
+ --raw-danger-hover: #8f1e17;
49
+ --raw-scrim: rgba(21, 24, 28, 0.4);
50
+ --raw-success: #2f6b3a;
51
+ --raw-warning: #8a5a00;
52
+ --raw-info: #1d5f8a;
53
+ --raw-danger-bg: #fdeeec;
54
+ --raw-success-bg: #eaf2e6;
55
+ --raw-warning-bg: #fbf1dd;
56
+ --raw-info-bg: #e8f1f8;
57
+ --raw-accent: #1b4d8f;
58
+ --raw-switch-on: #1b4d8f;
59
+ --raw-accent-hover: #163a6d;
60
+ --raw-accent-soft: #e6eef7;
61
+
62
+ /* 气泡的一对"反色面":浅色深底浅字,暗色换成抬高面。
63
+ 独立成令牌是为了让组件不再按 `prefers-color-scheme` 分支(RFC-THEME-SCOPE D5)。
64
+ 实测 16.60:1(浅)/ 11.93:1(暗)。border 浅色与气泡底同色(不可见),
65
+ 不用 `transparent` 是因为令牌树会把它归成"非颜色"。 */
66
+ --raw-tooltip-bg: #15181c;
67
+ --raw-tooltip-fg: #f6f7f8;
68
+ --raw-tooltip-border: #15181c;
69
+
70
+ /* ---------------------------------------------------------------- 基底
71
+ paper = 页面底(冷灰白)。🔴 不能更浅:与 --surface 的对比度要 >= 1.06:1,
72
+ 否则页面看着就是纯白,"表面浮在底上"的分层完全消失。
73
+ surface= 卡片/面板底,比纸底亮一档,靠明度差而非描边来分层
74
+ ---------------------------------------------------------------- */
75
+ --paper: var(--raw-paper);
76
+ --surface: var(--raw-surface);
77
+ --surface-sunken: var(--raw-surface-sunken);
78
+ /* 🔴 浮层底(下拉/气泡/抽屉面板)。
79
+ 这条**原本只在暗色段定义** ⇒ 浅色下 6 个组件(combobox/popover/select/
80
+ dropdown/tooltip/table)的 `background` 整条失效 ⇒ 实测**完全透明**。
81
+ 浅色值 = --surface:浅色抬高靠阴影(--shadow-*),暗色才靠"面更亮"。 */
82
+ --surface-raised: var(--raw-surface-raised);
83
+ /* ============================================================================
84
+ 🔴 滚动条令牌(判据:WCAG 2.5.5 Target Size)
85
+ ----------------------------------------------------------------------------
86
+ ⭐ 依据(5 个权威来源口径一致):
87
+ · CSS Scrollbars Level 1(W3C)—— scrollbar-width / color / gutter
88
+ · ::-webkit-scrollbar 伪元素族 —— Chromium/WebKit 专属,
89
+ **唯一能给 thumb 加圆角与 hover 的方式**
90
+ · WCAG 1.4.11 非文本对比 —— thumb 对 track **≥ 3:1**
91
+
92
+ ⚠️ 为什么滚动条要**降彩度**、用灰色而不是品牌色:
93
+ 滚动条是**次要**信息("还有多少内容"),不该抢注意力。
94
+ 行业惯例(Radix / shadcn / GitHub)都是中性灰。
95
+
96
+ ⚠️ 为什么 track 透明:轨道代表"没有内容的区域",
97
+ 画出来等于视觉噪声。
98
+ ==========================================================================*/
99
+ --scrollbar-size: 10px; /* 桌面宽度;移动端由浏览器接管 */
100
+ --scrollbar-size-thin: 6px; /* 代码块等次要场景 */
101
+ /* 🔴 色值是**脚本反解**出来的(WCAG 1.4.11 要求 thumb 对 track ≥3:1),
102
+ 不是"看着差不多"挑的:
103
+ #7a808a 对 #ffffff 3.98:1 · 对 #f6f7f8 3.71:1 ✅
104
+ ⚠️ 第一版我随手挑的 #B4BAC4 只有 1.82:1 ⇒ 门禁直接判不合格。 */
105
+ --scrollbar-thumb: var(--raw-scrollbar-thumb); /* 中性**冷**灰(非品牌色,且保持蓝调)*/
106
+ --scrollbar-thumb-hov: var(--raw-scrollbar-thumb-hov); /* hover 更深一档 · 4.06:1 */
107
+ --scrollbar-radius: 5px; /* = size/2 ⇒ 胶囊形 */
108
+ /* 内凹区:代码块、表格头、禁用底 */
109
+
110
+ /* ---------------------------------------------------------------- 文字
111
+ 三档够用了。超过三档说明信息层级没想清楚,不是 token 不够。
112
+ 全部数字由 05-audit/contrast.py 从本文件实测得出,不是手写的。
113
+ 改任何颜色后必须重跑该脚本 —— 手写注释必然漂移(已发生过)。
114
+ ---------------------------------------------------------------- */
115
+ --text-primary: var(--raw-text-primary); /* 16.60 纸底 / 17.81 表面 · AAA */
116
+ --text-secondary: var(--raw-text-secondary); /* 7.01 AAA */
117
+ /* 🔴 由 #6b7280 调深到 #606770。
118
+ axe-core 实测发现:#6b7280 压在**语义底色**上只有 4.13:1
119
+ (4.5 是正文门槛)—— 它只在白底上勉强过线(4.83),
120
+ 一放到 --surface-sunken / --warning-bg 等底上就不达标。
121
+ 调深后最低 4.89(压在最亮的 --surface 上),留了 0.39 余量。
122
+ 梯度仍在:primary 17.8 / secondary 7.5 / tertiary 4.9 —— 明显分层。 */
123
+ --text-tertiary: var(--raw-text-tertiary); /* 5.33 纸底 / 4.89 最亮底(surface) · AA */ /* 4.51 AA */
124
+ --text-on-accent: var(--raw-text-on-accent); /* 8.38 压在 accent 上(不是压纸底)· AAA */
125
+
126
+ /* 🔴 新增( H9 期间由暗色门禁逼出来):
127
+ 「实心态」组件(filled badge 等)压在**语义色实底**上的文字色。
128
+ ⚠️ 为什么不能复用 --text-on-accent:
129
+ 暗色下 --success / --danger / --warning 都**变浅**了(为在深底上更醒目),
130
+ 实心态的底色也随之变浅 ⇒ 白字压上去只有 2.72:1。
131
+ ⇒ 需要一个**独立令牌**,让暗色能把它换成深字。 */
132
+ --text-on-solid: var(--raw-text-on-solid); /* 亮色:实底(深色语义色)⇒ 白字 */
133
+ /* ---------------------------------------------------------------- 骨架屏
134
+ 🔴 新增(对齐通行组件库的基础档位)
135
+ 加载态是移动端最高频的状态;没有它,每个业务方都会自己造一个。 */
136
+ --skeleton-bg: var(--raw-skeleton-bg); /* 骨架底色:比纸面略深一档 */
137
+ --skeleton-shimmer: var(--raw-skeleton-shimmer); /* 微光高光 */
138
+ --skeleton-h: 1em; /* 单行默认高度 */
139
+ --skeleton-size: 40px; /* circle 默认直径 */
140
+ --skeleton-duration: 1.4s; /* 微光扫过周期 */
141
+ /* ---------------------------------------------------------------- 进度条
142
+ 🔴 新增( I5)。组件私有参数集中在这里定义,
143
+ 而不是散在 CSS 里用 fallback —— 否则 deps 门禁会报"未定义令牌"。
144
+ ⭐ --ease-out 刻意独立命名:进度条要"向前"的 feel,
145
+ 匀速(linear)反而像卡住了。 */
146
+ --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
147
+ --progress-h: 6px; /* 线形默认高度 */
148
+ --progress-dur: 240ms; /* 宽度过渡时长 */
149
+ --r-full: 999px; /* 胶囊形圆角 */
150
+ --ring-size: 64px; /* 环形默认直径 */
151
+ --ring-thickness: 8px; /* 环形环宽 */
152
+ /* 进度条的当前值。默认 0% 是**正确默认**(不是占位)——
153
+ 实例用 JS setProperty 覆盖它(见 progress demo)。
154
+ ⚠️ 必须有默认值,否则"忘了设值"会渲染成 0% 且**没人发现**。 */
155
+ --progress: 0%;
156
+ /* 环形用**无单位**数值(conic-gradient 的角度计算不接受带 % 的长度)。
157
+ ⚠️ 这就是环形曾完全不显示的原因,别再改成 --progress。 */
158
+ --progress-num: 0;
159
+
160
+ /* ---------------------------------------------------------------- 气泡卡片
161
+ 🔴 新增( I4)。默认值是**正确默认**,
162
+ 不是占位 —— 没定义就会静默用 0(deps 门禁会报未定义)。 */
163
+ --z-popover: 60; /* 层级:高于导航,低于 dialog */
164
+ --popover-min-w: 200px;
165
+ --popover-max-w: 320px;
166
+ --popover-p: var(--sp-3);
167
+ --popover-gap: 8px; /* 浮层与触发器的距离 */
168
+ --popover-dur: 140ms;
169
+
170
+ /* ---------------------------------------------------------------- 边框
171
+ 🔴 本库最容易被做错的一处,两类不许合并:
172
+
173
+ control = 控件边界(输入框、按钮的轮廓)。WCAG 1.4.11 要求 >= 3.0,
174
+ 因为它是"识别出这是个控件"的唯一线索。必须达标。
175
+ decor = 纯装饰分隔(卡片内线、表格行线)。WCAG 不强制。
176
+ 对它强求 3.0 会把界面做得很重很难看 —— 那是误读规范。
177
+ ---------------------------------------------------------------- */
178
+ --border-control: var(--raw-border-control); /* 3.19 / 3.42 AA Large */
179
+ --border-decor: var(--raw-border-decor); /* 1.18 纯装饰,WCAG 不强制 */
180
+ --border-decor-str: var(--raw-border-decor-str); /* 1.49 装饰,需要更强分隔时用 */
181
+
182
+ /* ---------------------------------------------------------------- 语义色
183
+ 全部实测 >= 5.6 AA(vs 纸底)。可当文字用,也可当底色用。
184
+ ---------------------------------------------------------------- */
185
+ --danger: var(--raw-danger);
186
+ --danger-hover: var(--raw-danger-hover); /* 危险按钮 hover:加深(浅色下加深才对)*/
187
+ --scrim: var(--raw-scrim); /* 抽屉/弹窗遮罩 */ /* 5.07 AA */
188
+ --success: var(--raw-success); /* 5.96 vs 纸底 · AA */
189
+ --warning: var(--raw-warning); /* 5.53 vs 纸底 · AA */
190
+ --info: var(--raw-info); /* 6.41 vs 纸底 · AA */
191
+
192
+ /* 语义色的浅底(用于提示条背景,不放正文) */
193
+ --danger-bg: var(--raw-danger-bg);
194
+ --success-bg: var(--raw-success-bg);
195
+ --warning-bg: var(--raw-warning-bg);
196
+ --info-bg: var(--raw-info-bg);
197
+
198
+ /* ---------------------------------------------------------------- 强调色
199
+ 🔴 一源原则:一个项目只选一个 accent,不要混用。
200
+ accent 决定"可点击"的语义,混用等于取消了这层语义。
201
+ 三选一,把另外两个删掉,别全留着。
202
+ ---------------------------------------------------------------- */
203
+ --accent: var(--raw-accent);
204
+ /* 开关开态轨道色(浅色版):与品牌色一致;暗色下换降彩度值(见 dark 段)。
205
+ 不用 var(--accent) —— 暗色下 accent 太亮,铺满一大块会发光。
206
+ ⚠️ 这段注释原本**缺开头**,导致 --switch-on 被浏览器当成坏声明丢掉
207
+ (实测浅色下取值为空)。已由 token-parse-gate.py 兜住。 */
208
+ --switch-on: var(--raw-switch-on); /* 7.82 AAA */
209
+ --accent-hover: var(--raw-accent-hover); /* 11.29 白字压上 · AAA */
210
+ --accent-soft: var(--raw-accent-soft); /* focus ring / 浅底,不要拿它放正文 */
211
+
212
+ /* 气泡(见 L1 的说明) */
213
+ --tooltip-bg: var(--raw-tooltip-bg);
214
+ --tooltip-fg: var(--raw-tooltip-fg);
215
+ --tooltip-border: var(--raw-tooltip-border);
216
+ /* --accent: #0f7a6c; 薄荷绿 5.00 —— 活泼/工具类 */
217
+ /* --accent-hover: #0c6459; 白字 7.04 AAA */
218
+ /* --accent-soft: #dff1ec; */
219
+ /* --accent: #185fa5; 蓝 6.25 —— 严肃/数据类 */
220
+
221
+ /* ================================================================ [核心]
222
+ 间距:8px 网格为基准,大值流体。
223
+ 想要的值不在表里 => 说明布局没规划,改设计,不要加 token。
224
+
225
+ 🔴 哪些该流体:--sp-4 往上(区块间距,随屏幕变大而变大,观感自然)
226
+ 哪些不该:--sp-1..3(元素内部间距,流体化会让控件尺寸忽大忽小)
227
+ ---------------------------------------------------------------- */
228
+ --sp-1: 4px;
229
+ --sp-2: 8px;
230
+ --sp-3: 12px;
231
+ --sp-4: clamp(1rem, 0.95rem + 0.3vw, 1.25rem); /* 16 → 20px */
232
+ --sp-5: clamp(1.25rem, 1.15rem + 0.7vw, 1.75rem); /* 20 → 28px */
233
+ --sp-6: clamp(1.5rem, 1.3rem + 1.2vw, 2.25rem); /* 24 → 36px */
234
+ --sp-7: clamp(2rem, 1.7rem + 1.8vw, 3.5rem); /* 32 → 56px */
235
+ --sp-8: clamp(2.5rem, 2.1rem + 2.4vw, 4.5rem); /* 40 → 72px */
236
+
237
+ /* ================================================================ [核心]
238
+ 断点令牌
239
+ ----------------------------------------------------------------
240
+ 🔴 CSS 变量**不能**用在 media query 条件里。
241
+ 所以下面四个只是"文档与计算的单一出处",写 media query 时仍要写字面值:
242
+
243
+ @media (min-width: 48rem) { ... } ← 只能写字面值,不能写 var(--bp-md)
244
+
245
+ 留它们的理由:算流体值、给脚本查、以及让"我们有几个断点"这件事有唯一答案。
246
+ ⚠️ 断点用的是 rem 不是 px —— 用户调大字号时断点跟着变,这才是对的。
247
+ ---------------------------------------------------------------- */
248
+ --bp-sm: 30rem; /* 480px 大手机 / 小平板 */
249
+ --bp-md: 48rem; /* 768px 平板 */
250
+ --bp-lg: 64rem; /* 1024px 小笔记本 */
251
+ --bp-xl: 80rem; /* 1280px 桌面 */
252
+
253
+ /* ================================================================ [核心]
254
+ 字号阶梯
255
+ ----------------------------------------------------------------
256
+ 🔴 修正:上下限从 px 改成 rem。
257
+ px 上下限不尊重"用户把浏览器默认字号调大"的设置 ——
258
+ 那样 clamp 的下限会锁死,用户放到 200% 也涨不上去。
259
+ rem 基准跟着用户设置走,这是无障碍底线。
260
+
261
+ 🔴 哪些该流体、哪些不该(这条更容易做错):
262
+
263
+ 该流体:正文、小标题、大标题、间距
264
+ —— 它们随屏幕变大而变大,观感自然
265
+
266
+ **不该流体**:UI 文本(--fs-xs / --fs-sm)、小间距(--sp-1..3)
267
+ —— 它们是控件尺寸的一部分。流体会让按钮和输入框在不同屏幕下
268
+ 大小不一,布局抖动。搜索到的原话:
269
+ "UI typography benefits from minimal scaling.
270
+ Too much fluidity breaks layout."
271
+
272
+ 公式:clamp(MIN-REM, REM基准 + VW系数, MAX-REM)
273
+ VW 系数怎么来:先定 min/max 视口(375px / 1280px),
274
+ 系数 ≈ (max-rem - min-rem) × 100 / (1280 - 375) ≈ 差值 × 0.1105
275
+
276
+ ✅ WCAG 1.4.4 判据:只要 MAX ≤ 2.5 × MIN,就一定通过 resize-text 检查。
277
+ 本阶梯最大比值 2.5rem/1.75rem = 1.43,安全。
278
+ ---------------------------------------------------------------- */
279
+ --fs-xs: 0.75rem; /* 12px 固定:辅助文字不流体 */
280
+ --fs-sm: 0.875rem; /* 14px 固定:UI 文本不流体 */
281
+ --fs-base: clamp(1rem, 0.95rem + 0.3vw, 1.125rem); /* 16 → 18px */
282
+ --fs-md: clamp(1.125rem, 1.05rem + 0.5vw, 1.3125rem); /* 18 → 21px */
283
+ --fs-lg: clamp(1.3125rem, 1.2rem + 0.7vw, 1.5rem); /* 21 → 24px */
284
+ --fs-xl: clamp(1.5rem, 1.3rem + 1.2vw, 1.875rem); /* 24 → 30px */
285
+ --fs-2xl: clamp(1.75rem, 1.45rem + 1.8vw, 2.5rem); /* 28 → 40px */
286
+
287
+ /* 行高:正文 1.7 是中文长文的舒适值(西文常用 1.5,中文需要更松) */
288
+ --lh-tight: 1.25; /* 标题 */
289
+ --lh-base: 1.7; /* 正文 */
290
+ --lh-loose: 1.85; /* 长段落、低龄/无障碍场景 */
291
+
292
+ /* 行长:65ch 左右是阅读上限。超过就要分栏或收窄容器。
293
+ ⚠️ 这里曾经有一个裸的 `--measure: 68ch`,0.7.3 移除 —— 它既没被任何组件
294
+ 引用、也没在任何文档里出现过,而 `--measure-page`(页面列宽)与
295
+ `--measure-prose`(长文列宽)已经把它该管的两个场景分掉了。
296
+ 不要再把它加回来:`token-audience-gate.py` 判据 ① 会拦住没登记受众的令牌。
297
+ ⇒ 版心请用下面这两个带语义后缀的。 */
298
+
299
+ /* ----------------------------------------------------------------
300
+ 🔴 补:页面列宽 --measure-page(0.4.2)
301
+ 此前 33 个页面各自写 `.wrap { max-width: 680px / 720 / 760 / 800 / 820 /
302
+ 860 / 900 / 42rem / 44rem / 46rem / 52rem … }` —— 16 种写法。
303
+ 结果就是「没有全局可改」:想改列宽要改 33 处,漏一处就不一致。
304
+ 实测众数是 820px(8/33 页),取 52rem = 832px(干净的 rem 值,
305
+ 且 rem 会跟随用户字号设置,px 不会)。
306
+ 改这一个令牌 ⇒ 全部页面列宽一起变。
307
+ ---------------------------------------------------------------- */
308
+ --measure-page: 52rem;
309
+
310
+ /* 🔴 补:标题字距 --tracking-title(0.4.2)
311
+ 大字号要收紧字距,否则标题散。此前只有 list/nav/states 三页写了
312
+ `letter-spacing: -0.02em`(写死值),其余 33 页没有 ⇒ 标题观感不一致。 */
313
+ --tracking-title: -0.02em;
314
+
315
+ /* ---------------------------------------------------------------- 圆角
316
+ 一套就够。圆角混搭(卡片 20px、按钮 4px)是典型的没想清楚。
317
+ ---------------------------------------------------------------- */
318
+ --r-sm: 6px;
319
+ /* 🔴 补:内嵌小元素用的小圆角(菜单项、标签、chip)。
320
+ 之前 dropdown.css 里凭空写了 var(--r-xs, 3px) —— 令牌不存在时
321
+ CSS 变量会**静默失效**(整条声明被丢弃),所以一直没被发现。
322
+ ⇒ 补上这个令牌(deps 门禁抓到)。 */
323
+ --r-xs: 3px;
324
+ --r-md: 10px;
325
+ --r-lg: 14px;
326
+ --r-pill: 999px;
327
+
328
+ /* ---------------------------------------------------------------- 阴影
329
+ 两级。第三级阴影出现的时候,通常说明层级设计出了问题。
330
+ y 轴偏移小、模糊大 —— 模拟纸面浮起,不是悬浮在太空。
331
+ ---------------------------------------------------------------- */
332
+ --shadow-1: 0 1px 2px rgba(31, 29, 25, 0.05), 0 1px 3px rgba(31, 29, 25, 0.06);
333
+ --shadow-2: 0 2px 4px rgba(31, 29, 25, 0.05), 0 6px 16px rgba(31, 29, 25, 0.08);
334
+
335
+ /* ---------------------------------------------------------------- 动效
336
+ 🔴 一源原则:全项目只用这一组时长和缓动。
337
+ 六个地方来的六个效果 => 六个不同的缓动曲线 => 看起来像没设计过。
338
+ ---------------------------------------------------------------- */
339
+ --dur-fast: 120ms; /* 颜色、透明度 */
340
+ --dur-base: 180ms; /* 位移、展开 */
341
+ --dur-slow: 280ms; /* 大块入场、遮罩 */
342
+
343
+ --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* UI 默认:入场、展开 */
344
+ --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* 屏上位移、折叠 */
345
+ --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); /* 抽屉式滑入(iOS 观感)*/
346
+
347
+ /* 🔴 修正:上一版这三个曲线是**我自编**的
348
+ (0.22,1,0.36,1 / 0.65,0,0.35,1),现已换成业界通用值 ——
349
+ 与 Tailwind v4 默认、Emil Kowalski 技能库一致。
350
+ **曲线不该自己编**:自编的曲线没有经过大量实现验证,
351
+ 阻尼比、重心位置都是猜的,跨浏览器表现不一致。 */
352
+
353
+ /* 位移距离:动效的"幅度"也要统一,不然同样 180ms 也会感觉乱 */
354
+ --lift-sm: 1px;
355
+ --lift-md: 2px;
356
+ --lift-lg: 8px;
357
+
358
+ /* ---------------------------------------------------------------- z-index
359
+ 乱写 z-index(999、9999)是前端最常见的失控点。
360
+ 只用这六个值。需要更高的 => 说明层级模型有问题。
361
+ ---------------------------------------------------------------- */
362
+ --z-base: 0;
363
+ --z-sticky: 10;
364
+ --z-header: 20;
365
+ /* 🔴 补回:上一轮"清理重复 z 令牌"时把它误删了,
366
+ 而 card / pagination 都在用 ⇒ 它们的 z-index 声明**静默失效**。
367
+ ⚠️ CSS 变量不存在时**不报错**,声明直接被丢弃 —— 这类问题只能靠门禁抓。 */
368
+ --z-raised: 5; /* 浮起层(卡片悬停、下拉、内层可点元素)*/
369
+ --z-overlay: 30;
370
+ --z-modal: 40;
371
+ --z-toast: 50;
372
+
373
+ /* ------------------------------------------------------------
374
+ 命中区尺寸
375
+
376
+ 之前全库到处硬写 `44px`(20 处,各组件各写一遍)⇒ 改一次要翻遍全库。
377
+
378
+ 为什么是 44px:**WCAG 2.5.5 Target Size(Level AAA)** 要求 44×44 CSS px。
379
+ ⚠️ 别混淆:WCAG 2.5.8(AA 级)只要 24×24;本库按 AAA 做。
380
+ ------------------------------------------------------------ */
381
+ --sz-touch: 44px; /* 最小命中区(指针)*/
382
+ --sz-touch-sm: 32px; /* 次要场景(表格行内等密排)*/
383
+
384
+ /* ==========================================================================
385
+ 🔴 补:跳过导航链接(skip link)
386
+ --------------------------------------------------------------------------
387
+ a11y-scan 报了两条违规(都因为入口页没有地标):
388
+ · landmark-one-main —— 文档应有一个 <main>
389
+ · region —— 所有内容应被地标包裹
390
+ ⇒ 修法:入口页包 <main id="main">,并提供"跳到主内容"的链接。
391
+
392
+ ⭐ 为什么要 skip link:
393
+ 键盘用户每次进页面都要 Tab 过几十个导航链接才能到正文。
394
+ skip link 让它们**按一次 Tab 就能跳过**(第一次 Tab 就会出现,
395
+ 聚焦时才显形)—— 这是 WCAG 2.4.1 Bypass Blocks 的标准做法。
396
+ ========================================================================== */
397
+ }
398
+
399
+ /* ============================================================================
400
+ 无障碍兜底 —— 这一段必须随 token 一起走,不许删
401
+ ========================================================================= */
402
+
403
+ /* 尊重系统的"减少动效"设置。评委、无障碍用户、晕动症人群会开这个。
404
+ 不写这段,前面所有动效在那些机器上就是灾难。
405
+
406
+ 🔴 这是**兜底**,不是全部策略(0.9.1 起):
407
+ ① 位移 / 缩放 / 旋转 —— 关掉(前庭不适的主要来源)
408
+ ② 真实进度(spinner / 加载中)—— **保留**,见 ai/motion-policy.json。
409
+ 停掉进度指示会让用户以为页面卡死,比有动画更糟。
410
+ ③ 颜色 / 透明度 —— 允许保留但缩短到 --dur-fast(不强制,也无判据)
411
+ 默认关、例外要写理由并由 `motion.py --reduced-audit` 核对:
412
+ 声明了却没实现 ⇒ 红;实现了却没声明 ⇒ 也红。
413
+ 本段的 `*` 规则必须保留 —— 它是①②③共同的底线,
414
+ 组件要保留动画时用**更高优先级 + !important** 覆盖它(库里三处都这么做)。 */
415
+ @media (prefers-reduced-motion: reduce) {
416
+ *,
417
+ *::before,
418
+ *::after {
419
+ animation-duration: 0.01ms !important;
420
+ animation-iteration-count: 1 !important;
421
+ transition-duration: 0.01ms !important;
422
+ scroll-behavior: auto !important;
423
+ }
424
+ }
425
+
426
+ /* 键盘焦点环。用 :focus-visible 而不是 :focus ——
427
+ 前者只在键盘导航时显示,鼠标点击不会冒出难看的框。
428
+ 🔴 只写 outline: none 而不给替代焦点指示,是无障碍事故。 */
429
+ :focus-visible {
430
+ outline: 2px solid var(--accent);
431
+ outline-offset: 2px;
432
+ border-radius: var(--r-sm);
433
+ }
434
+
435
+ /* ============================================================================
436
+ 废弃记录 —— 保留在这里是为了不再犯同样的错
437
+ =========================================================================
438
+ 第一版自检抓出 5 项不达标,全部是"凭感觉填的":
439
+
440
+ text-tertiary #8a8478 vs 纸底 = 3.56 → 正文需 4.5,不达标
441
+ vs 白面 = 3.72 → 同样不达标
442
+ 改 #6f6a5e(5.16 / 5.39)
443
+ 教训:辅助文字色最容易填浅,因为它"看起来就该浅"。
444
+
445
+ border #cfc9bb vs 纸底 = 1.58 → UI 边界需 3.0,不达标
446
+ 改 #8a8478(3.56)
447
+ 但:装饰分隔线不需要 3.0,硬套会让界面变重
448
+ → 因此把边框拆成 control / decor 两类,这是第一版最大的收获。
449
+ ========================================================================= */
450
+
451
+ /* ============================================================================
452
+ i18n:度量令牌(行长控制)
453
+ ============================================================================
454
+ 🔴 为什么需要这一节
455
+
456
+ 中文排版用 `em` 限宽看起来"完美",**但那是巧合**:1em = 1 个汉字宽。
457
+ 换到英文,同样的 `max-width: 14em` 只能放约 20 个字符 ——
458
+ 实测 84 字符的英文标题会折成 **4 行**,这是坏的排版。
459
+
460
+ `ch` 也不对:ch = "0" 的宽度,**1 汉字 ≈ 2ch**,
461
+ 所以 13ch 只能放 6 个汉字(曾见过标题被挤成三行)。
462
+
463
+ ⇒ 结论:**没有任何单一单位能同时伺候所有语言**,必须按语言切换。
464
+
465
+ 用法:组件用 `var(--measure-*)`,不写死单位。
466
+
467
+ h1 { max-width: var(--measure-title); }
468
+ p { max-width: var(--measure-prose); }
469
+
470
+ ── 各语言的取值依据 ──────────────────────────────────────────────
471
+ | 语言 | 计量 | 正文值 | 舒适区 |
472
+ |-------------|-----------|--------|---------------|
473
+ | 中文/日韩 | em(1em=1字)| 34em | 25-35 字 |
474
+ | 拉丁/西里尔 | ch(1ch≈1字)| 68ch | 60-75 字符 |
475
+ | 阿拉伯 | ch | 60ch | 同上,且 RTL |
476
+ | 印地/泰文 | ch | 60ch | 连字会让 ch 偏小,取小值更安全 |
477
+
478
+ `:lang()` 选的是**内容语言**,不是界面语言 —— 这才是对的。
479
+ ========================================================================= */
480
+
481
+ :root {
482
+ /* 正文行长:中文 34 字 ≈ 拉丁 68 字符,两边都落在各自的舒适区 */
483
+ --measure-prose: 34em;
484
+ --measure-title: 20em; /* 标题比正文窄,避免大字长时间扫视 */
485
+ --measure-lede: 32em; /* 导语 */
486
+ --measure-pull: 30em; /* 引言块 */
487
+ --measure-aside: 32em; /* 边注 */
488
+ }
489
+
490
+ /* 拉丁 / 西里尔 / 希腊:改用 ch(1ch ≈ 1 个字符宽) */
491
+ :root:lang(en), :root:lang(en-US), :root:lang(en-GB),
492
+ :root:lang(fr), :root:lang(de), :root:lang(es), :root:lang(pt),
493
+ :root:lang(ru), :root:lang(el), :root:lang(tr), :root:lang(vi) {
494
+ --measure-prose: 68ch;
495
+ --measure-title: 34ch;
496
+ --measure-lede: 62ch;
497
+ --measure-pull: 58ch;
498
+ --measure-aside: 56ch;
499
+ }
500
+
501
+ /* 阿拉伯语:RTL,行长同样按 ch */
502
+ :root:lang(ar) {
503
+ --measure-prose: 60ch;
504
+ --measure-title: 30ch;
505
+ --measure-lede: 56ch;
506
+ --measure-pull: 52ch;
507
+ --measure-aside: 50ch;
508
+ }
509
+
510
+ /* 印地 / 泰文 / 高棉 / 缅甸:连字与组合字形会让 ch 偏小,取更保守的值 */
511
+ :root:lang(hi), :root:lang(th), :root:lang(km), :root:lang(my) {
512
+ --measure-prose: 56ch;
513
+ --measure-title: 28ch;
514
+ --measure-lede: 52ch;
515
+ --measure-pull: 48ch;
516
+ --measure-aside: 46ch;
517
+ }
518
+
519
+ /* 🔴 兜底:未知语言(`lang` 缺失或写了别的)走 ch 而不是 em ——
520
+ 拉丁字母是更常见的假设;而且 ch 在等宽字体下也准。
521
+ 中文/日韩显式命中上面的 em 分支(:root 默认值),不会落到这里。 */
522
+ :root:lang(zh), :root:lang(zh-CN), :root:lang(zh-TW),
523
+ :root:lang(ja), :root:lang(ko) {
524
+ --measure-prose: 34em;
525
+ --measure-title: 20em;
526
+ --measure-lede: 32em;
527
+ --measure-pull: 30em;
528
+ --measure-aside: 32em;
529
+ }
530
+
531
+ /* ============================================================================
532
+ 暗色模式(prefers-color-scheme: dark)
533
+ ============================================================================
534
+ 🔴 建立。**下面每个值都是按 WCAG 公式算出来的,不是"反过来"。**
535
+
536
+ ── 为什么不能把浅色反过来 ────────────────────────────────────
537
+ 1. **纯黑底会让彩色文字发灰**(视觉上对比度下降)⇒ paper 用 #14171a 不是 #000
538
+ 2. **明度关系要重排**:浅色是"底浅→字深",暗色是"底深→字亮",
539
+ 但**中间灰阶的分布完全不同** —— 浅色的 #6b7280(tertiary)
540
+ 在深底上完全不够用(只有 2.1:1)
541
+ 3. **饱和色要降**:#c0392b 在深底上刺眼且显脏 ⇒ 提到 #e07a6e
542
+ 4. **强调色方向相反**:浅色用深靛蓝(沉),暗色必须**变亮**才看得见
543
+ ⇒ --accent 从 #1b4d8f 变成 #7aa9de。**这不是"反色"能得出的。**
544
+ 5. **按钮上的文字要翻过来**:浅色按钮是"蓝底白字",
545
+ 暗色按钮变亮 ⇒ 必须改成"**亮底深字**"(--text-on-accent: #0d1114)
546
+
547
+ ── 实测数据(WCAG 相对亮度公式算的)──────────────────────────
548
+ | 令牌 | 暗色值 | 在 paper 上的对比度 | 要求 |
549
+ |---|---|---|---|
550
+ | --text-primary | #e8eaed | 14.93 | 4.5 ✅ |
551
+ | --text-secondary | #b8bec6 | 9.61 | 4.5 ✅ |
552
+ | --text-tertiary | #959ba3 | 6.42 | 4.5 ✅ |
553
+ | --danger | #e07a6e | 6.15 | 4.5 ✅ |
554
+ | --success | #6faa7a | 6.60 | 4.5 ✅ |
555
+ | --warning | #c99a3f | 7.01 | 4.5 ✅ |
556
+ | --info | #5b9bc4 | 5.94 | 4.5 ✅ |
557
+ | --accent | #7aa9de | 7.33 | 4.5 ✅ |
558
+ | --text-on-accent | #0d1114 | 7.73(压 accent 上)| 4.5 ✅ |
559
+ | --border-control | #7d848d | 4.76 | 3.0 ✅ |
560
+
561
+ ⚠️ **--text-primary 故意不用最高对比的那个**(#f2f4f6 = 16.32:1)。
562
+ 暗色下过高的对比会**发光、刺眼**,长时间阅读更累。
563
+ **过线即可,不是越高越好。**
564
+
565
+ ── 底色三层(明度差必须够,否则分层直接消失)──────────────────
566
+ | 层 | 值 | 与 paper 的对比 |
567
+ |---|---|---|
568
+ | sunken(内凹)| #080a0c | 1.10 ✅ |
569
+ | paper(页面底)| #14171a | — |
570
+ | surface(卡片)| #1c2024 | 1.10 ✅ |
571
+
572
+ 🔴 我第一版把 sunken 设成 #0f1214,与 paper 只差 **1.05** ——
573
+ **卡片和页面底几乎分不开**,这正是本库在浅色下踩过的坑
574
+ (--paper 曾经是 #fcfcfd,与白色只差 1.025)。**同样的错不能犯两次。**
575
+
576
+ ── 为什么用 @media 而不是 light-dark() ──────────────────────
577
+ `light-dark()` 是 2024 的新函数,**精简档 的老 WebView 不支持**。
578
+ 用 @media 是目前兼容性最好的做法。
579
+ ---------------------------------------------------------------- */
580
+
581
+ @media (prefers-color-scheme: dark) {
582
+ /* ========================================================================
583
+ 第二次修正(上一版被 真机实测否决)
584
+ ------------------------------------------------------------------------
585
+ 反馈:「小方框变成了咖啡色」「背景变成了淡白色」
586
+ 「主业是白色兼灰色有高级感,现在变成了暖色」
587
+
588
+ 前一版的三个问题:
589
+ 1 引入暖灰(#E8E6E3 / #1E1E1E)—— 反馈像咖啡色
590
+ 2 --paper 被写成别处覆盖的值 —— 结果 dark 下背景是淡白
591
+ 3 🔴 注释里写了 `#080a0c` 和中文分号
592
+ ⇒ 被 gen-theme-js.py 的 `[^;]+` 当成**属性值**
593
+ ⇒ --paper 整个丢失 ⇒ 切暗色时背景不变(实测确认)
594
+
595
+ ⭐ 本次原则:**只修该修的,色相一律保持冷灰**
596
+ 主业的"高级感"来自冷灰(白+灰),不是暖灰 —— 这是 最终定的调。
597
+
598
+ 保留三条业界共识(不改变色相):
599
+ 1 层级靠"表面变亮":暗色下阴影不可见,抬高面必须更亮
600
+ 原来的 --surface-sunken 比 paper 还深,方向是错的
601
+ 2 正文不用纯白:纯白在深底上会 halation(字发散)
602
+ 3 tertiary 必须够 4.5:1:用脚本算,不靠眼睛调
603
+ ======================================================================== */
604
+
605
+ /* 冷灰基座 -- 不用纯黑(OLED 拖影),不用暖灰(显廉价) */
606
+ /* ⚠️ `:not([data-theme="light"])` —— 让「强制亮」能压过系统暗色。
607
+ 不加这层 `:not`:系统暗色 + data-theme="light" 时,本段仍会命中
608
+ ⇒ 使用者点了「亮」却还是暗的(三态开关里最容易被忽略的一条路径)。
609
+ ⇒ 强制暗由下面生成的 `[data-theme="dark"]` 段负责,两边互不干扰。 */
610
+ :root:not([data-theme="light"]) {
611
+ --raw-paper: #14171A;
612
+ --raw-surface: #1C2024;
613
+ --raw-surface-raised: #232830;
614
+ /* 凹陷面不再更黑,改为比 paper 略浅(层级靠变亮) */
615
+ --raw-surface-sunken: #1A1D21;
616
+
617
+ /* 🔴滚动条(暗色)—— 见 charter 12
618
+ ⚠️ 暗色下 thumb 要比浅色**更亮**(#4A515C)才看得见;
619
+ 用浅色那档会变成"黑里有黑"。 */
620
+ --raw-scrollbar-thumb: #6E747E; /* 对 #14171a 3.82:1 · 对 #232830 3.15:1 ✅ */
621
+ --raw-scrollbar-thumb-hov: #7D828A; /* 4.24:1 */
622
+
623
+ /* 边框 -- 冷灰,靠变亮分层 */
624
+ --raw-border-decor: #2A3038;
625
+ --raw-border-decor-str: #3A424C;
626
+ --raw-border-control: #8A9099;
627
+
628
+ /* 文本 -- 冷调白,非纯白(避免 halation) */
629
+ --raw-text-primary: #E4E7EA;
630
+ --raw-text-secondary: #AEB6C0;
631
+ /* 实算 原来的 #959ba3 对 paper 只有 4.14:1 不达 AA,提到这个值 */
632
+ --raw-text-tertiary: #9BA3AD;
633
+ --raw-text-on-accent: #0D1114;
634
+ --raw-text-on-solid: #0D1114; /* 暗色:success/danger/warning 都变浅了 ⇒ 实心态换深字 */
635
+ /* 暗色:底色比纸面**略浅**(深底上"更亮"才看得出是占位) */
636
+ --raw-skeleton-bg: #2E343C; /* 亮度差 0.032(契约要求 >0.02)*/
637
+ --raw-skeleton-shimmer: rgba(255, 255, 255, 0.06);
638
+
639
+ /* 语义色 -- 沿用原有冷调 */
640
+ --raw-danger: #E07A6E;
641
+ --raw-danger-hover: #F09A90;
642
+ --raw-danger-bg: #341D1A;
643
+ --raw-success: #6FAA7A;
644
+ --raw-success-bg: #16241A;
645
+ --raw-warning: #C99A3F;
646
+ /* 🔴 最终定的(第二次修正):
647
+ 「本身**淡黄色的那些框**你就改成**暖灰色**,
648
+ **在深色模式下的暖灰色**这个就很好啊」
649
+
650
+ 浅色下 --warning-bg 是 #fbf1dd(淡黄)—— **保持不动**。
651
+ 暗色下原来是 #2B2313(**深红棕**)⇒ 在冷灰背景里
652
+ 像一块"**咖啡色**"的脏斑 ⇒ 这就是 真机实测看到的。
653
+
654
+ ⇒ 暗色下改成**暖灰**(保留一点暖调,与浅色的淡黄呼应,
655
+ 但**不再偏红褐**,与整体的冷灰底融合)。
656
+ ⚠️ 只改这一个令牌,**底色/表面/文本一律不动**。 */
657
+ --raw-warning-bg: #242A31;
658
+ --raw-info: #5B9BC4;
659
+ --raw-info-bg: #13242F;
660
+
661
+ /* 品牌色 -- 沿用原有淡蓝 */
662
+ --raw-accent: #7AA9DE;
663
+ /* 🔴 开关开态的轨道色 -- 暗色版
664
+ 🔴 **降彩度**:#7AA9DE 太亮,铺满一大块会发光。
665
+ 实测:滑块是纯白 #FFF,轨道若也接近白 ⇒ 对比不足、边界糊。
666
+ ⇒ 轨道取**中深蓝**(#3E6EA8 族),与白滑块有清晰对比,
667
+ 在 #1C2024 的深底上又不刺眼。 */
668
+ --raw-switch-on: #3E6EA8;
669
+ --raw-accent-hover: #8FB8E6;
670
+ --raw-accent-soft: #1C2C3D;
671
+
672
+ /* 气泡:暗色下不再"反色"(反色会变成浅底深字,在暗界面里像个白块),
673
+ 改用抬高面 + 正文色 —— 但只比纸面亮一档,所以必须补一条描边。
674
+ 文字/底 11.93:1(AAA),实测见 contrast.py。 */
675
+ --raw-tooltip-bg: #232830;
676
+ --raw-tooltip-fg: #E4E7EA;
677
+ --raw-tooltip-border: #2A3038;
678
+
679
+ --raw-scrim: rgba(0, 0, 0, 0.62);
680
+ /* ⚠️ 这里原本还抄了一份 --sans/--mono/--serif/--tracking-normal —— 是死代码:
681
+ 真正的字体栈定义在后面的 `:root`,同特异性且位置更靠后 ⇒ 无条件覆盖这里
682
+ (实测暗色下 --tracking-normal = 0,不是这里的 0.01em)。
683
+ 留着还会被 theme-toggle.js 的差集规则复制成 inline style ⇒
684
+ inline 优先于样式表 ⇒ 强制暗色时字体被改。已删。 */
685
+ } /* ← :root 闭合*/
686
+ }
687
+
688
+ /* === BEGIN: light-via-attr(由 05-audit/theme-sync.py 生成,勿手改)=== */
689
+ [data-theme="light"] {
690
+ --raw-accent: #1b4d8f;
691
+ --raw-accent-hover: #163a6d;
692
+ --raw-accent-soft: #e6eef7;
693
+ --raw-border-control: #868b94;
694
+ --raw-border-decor: #e2e5e9;
695
+ --raw-border-decor-str: #c8cdd4;
696
+ --raw-danger: #c0392b;
697
+ --raw-danger-bg: #fdeeec;
698
+ --raw-danger-hover: #8f1e17;
699
+ --raw-info: #1d5f8a;
700
+ --raw-info-bg: #e8f1f8;
701
+ --raw-paper: #f6f7f8;
702
+ --raw-scrim: rgba(21, 24, 28, 0.4);
703
+ --raw-scrollbar-thumb: #7A808A;
704
+ --raw-scrollbar-thumb-hov: #6E747E;
705
+ --raw-skeleton-bg: #e9ecef;
706
+ --raw-skeleton-shimmer: rgba(255, 255, 255, 0.72);
707
+ --raw-success: #2f6b3a;
708
+ --raw-success-bg: #eaf2e6;
709
+ --raw-surface: #ffffff;
710
+ --raw-surface-raised: #ffffff;
711
+ --raw-surface-sunken: #f1f3f5;
712
+ --raw-switch-on: #1b4d8f;
713
+ --raw-text-on-accent: #ffffff;
714
+ --raw-text-on-solid: #ffffff;
715
+ --raw-text-primary: #15181c;
716
+ --raw-text-secondary: #4f555e;
717
+ --raw-text-tertiary: #606770;
718
+ --raw-tooltip-bg: #15181c;
719
+ --raw-tooltip-border: #15181c;
720
+ --raw-tooltip-fg: #f6f7f8;
721
+ --raw-warning: #8a5a00;
722
+ --raw-warning-bg: #fbf1dd;
723
+
724
+ --accent: var(--raw-accent);
725
+ --accent-hover: var(--raw-accent-hover);
726
+ --accent-soft: var(--raw-accent-soft);
727
+ --border-control: var(--raw-border-control);
728
+ --border-decor: var(--raw-border-decor);
729
+ --border-decor-str: var(--raw-border-decor-str);
730
+ --danger: var(--raw-danger);
731
+ --danger-bg: var(--raw-danger-bg);
732
+ --danger-hover: var(--raw-danger-hover);
733
+ --info: var(--raw-info);
734
+ --info-bg: var(--raw-info-bg);
735
+ --paper: var(--raw-paper);
736
+ --scrim: var(--raw-scrim);
737
+ --scrollbar-thumb: var(--raw-scrollbar-thumb);
738
+ --scrollbar-thumb-hov: var(--raw-scrollbar-thumb-hov);
739
+ --skeleton-bg: var(--raw-skeleton-bg);
740
+ --skeleton-shimmer: var(--raw-skeleton-shimmer);
741
+ --success: var(--raw-success);
742
+ --success-bg: var(--raw-success-bg);
743
+ --surface: var(--raw-surface);
744
+ --surface-raised: var(--raw-surface-raised);
745
+ --surface-sunken: var(--raw-surface-sunken);
746
+ --switch-on: var(--raw-switch-on);
747
+ --text-on-accent: var(--raw-text-on-accent);
748
+ --text-on-solid: var(--raw-text-on-solid);
749
+ --text-primary: var(--raw-text-primary);
750
+ --text-secondary: var(--raw-text-secondary);
751
+ --text-tertiary: var(--raw-text-tertiary);
752
+ --tooltip-bg: var(--raw-tooltip-bg);
753
+ --tooltip-border: var(--raw-tooltip-border);
754
+ --tooltip-fg: var(--raw-tooltip-fg);
755
+ --warning: var(--raw-warning);
756
+ --warning-bg: var(--raw-warning-bg);
757
+ }
758
+ /* === END: light-via-attr === */
759
+
760
+ /* === BEGIN: dark-via-attr(由 05-audit/theme-sync.py 生成,勿手改)=== */
761
+ :root[data-theme="dark"],
762
+ [data-theme="dark"] {
763
+ --raw-accent: #7AA9DE;
764
+ --raw-accent-hover: #8FB8E6;
765
+ --raw-accent-soft: #1C2C3D;
766
+ --raw-border-control: #8A9099;
767
+ --raw-border-decor: #2A3038;
768
+ --raw-border-decor-str: #3A424C;
769
+ --raw-danger: #E07A6E;
770
+ --raw-danger-bg: #341D1A;
771
+ --raw-danger-hover: #F09A90;
772
+ --raw-info: #5B9BC4;
773
+ --raw-info-bg: #13242F;
774
+ --raw-paper: #14171A;
775
+ --raw-scrim: rgba(0, 0, 0, 0.62);
776
+ --raw-scrollbar-thumb: #6E747E;
777
+ --raw-scrollbar-thumb-hov: #7D828A;
778
+ --raw-skeleton-bg: #2E343C;
779
+ --raw-skeleton-shimmer: rgba(255, 255, 255, 0.06);
780
+ --raw-success: #6FAA7A;
781
+ --raw-success-bg: #16241A;
782
+ --raw-surface: #1C2024;
783
+ --raw-surface-raised: #232830;
784
+ --raw-surface-sunken: #1A1D21;
785
+ --raw-switch-on: #3E6EA8;
786
+ --raw-text-on-accent: #0D1114;
787
+ --raw-text-on-solid: #0D1114;
788
+ --raw-text-primary: #E4E7EA;
789
+ --raw-text-secondary: #AEB6C0;
790
+ --raw-text-tertiary: #9BA3AD;
791
+ --raw-tooltip-bg: #232830;
792
+ --raw-tooltip-border: #2A3038;
793
+ --raw-tooltip-fg: #E4E7EA;
794
+ --raw-warning: #C99A3F;
795
+ --raw-warning-bg: #242A31;
796
+
797
+ --accent: var(--raw-accent);
798
+ --accent-hover: var(--raw-accent-hover);
799
+ --accent-soft: var(--raw-accent-soft);
800
+ --border-control: var(--raw-border-control);
801
+ --border-decor: var(--raw-border-decor);
802
+ --border-decor-str: var(--raw-border-decor-str);
803
+ --danger: var(--raw-danger);
804
+ --danger-bg: var(--raw-danger-bg);
805
+ --danger-hover: var(--raw-danger-hover);
806
+ --info: var(--raw-info);
807
+ --info-bg: var(--raw-info-bg);
808
+ --paper: var(--raw-paper);
809
+ --scrim: var(--raw-scrim);
810
+ --scrollbar-thumb: var(--raw-scrollbar-thumb);
811
+ --scrollbar-thumb-hov: var(--raw-scrollbar-thumb-hov);
812
+ --skeleton-bg: var(--raw-skeleton-bg);
813
+ --skeleton-shimmer: var(--raw-skeleton-shimmer);
814
+ --success: var(--raw-success);
815
+ --success-bg: var(--raw-success-bg);
816
+ --surface: var(--raw-surface);
817
+ --surface-raised: var(--raw-surface-raised);
818
+ --surface-sunken: var(--raw-surface-sunken);
819
+ --switch-on: var(--raw-switch-on);
820
+ --text-on-accent: var(--raw-text-on-accent);
821
+ --text-on-solid: var(--raw-text-on-solid);
822
+ --text-primary: var(--raw-text-primary);
823
+ --text-secondary: var(--raw-text-secondary);
824
+ --text-tertiary: var(--raw-text-tertiary);
825
+ --tooltip-bg: var(--raw-tooltip-bg);
826
+ --tooltip-border: var(--raw-tooltip-border);
827
+ --tooltip-fg: var(--raw-tooltip-fg);
828
+ --warning: var(--raw-warning);
829
+ --warning-bg: var(--raw-warning-bg);
830
+ }
831
+ /* === END: dark-via-attr === */
832
+
833
+
834
+ /* ============================================================================
835
+ 字体栈令牌
836
+ ============================================================================
837
+ 🔴 补。**这是个一直存在但没人发现的缺口** ——
838
+ 全库有 27 处引用 `var(--sans)` / `var(--mono)` / `var(--serif)`,
839
+ 而 tokens.css **从来没定义过它们** ⇒ 全部回退到浏览器默认字体。
840
+ 换句话说:库里"看起来有字体体系",其实没有。
841
+
842
+ ── 为什么字体栈要按语言分 ────────────────────────────────────
843
+ 一个字体通常**不覆盖所有文字**。Segoe UI 没有中文字形、
844
+ 苹方没有拉丁字形 ⇒ 浏览器逐字符 fallback 到系统默认。
845
+ 问题在于:**不同系统 fallback 到的字体不同**
846
+ (macOS 苹方 / Windows 微软雅黑 / Linux 思源)⇒ 同一份代码观感不一致。
847
+
848
+ ⇒ 做法:字体栈里**把各文字的字体都列上**,让浏览器在我们指定的顺序里选,
849
+ 而不是让它按系统默认挑。
850
+
851
+ ⚠️ 全部是**系统字体**,不下载任何字体文件 ——
852
+ 这是精简档(某项目)的硬约束,也是本库"零外部资产"的一部分。
853
+
854
+ 字体栈的排列顺序:**拉丁 → 中文 → 泛型**。
855
+ 浏览器逐字符 fallback,排在前面、优先用。
856
+ ========================================================================= */
857
+
858
+ :root {
859
+ --sans:
860
+ /* 拉丁 */
861
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
862
+ 'Helvetica Neue', Arial,
863
+ /* 中文 */
864
+ 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
865
+ 'Source Han Sans SC', 'Noto Sans CJK SC',
866
+ /* 阿拉伯 / 希伯来 / 泰文(系统通常自带相应字体)*/
867
+ 'Geeza Pro', 'Segoe UI Historic',
868
+ /* 泛型 */
869
+ sans-serif;
870
+
871
+ --mono:
872
+ /* 拉丁等宽 */
873
+ ui-monospace, 'SF Mono', 'Cascadia Mono', 'Segoe UI Mono',
874
+ 'Roboto Mono', Menlo, Consolas,
875
+ /* 中文等宽(中文通常回落到无衬线,但列出更可控)*/
876
+ 'Sarasa Mono SC', 'Microsoft YaHei Mono',
877
+ monospace;
878
+
879
+ --serif:
880
+ /* 拉丁衬线 */
881
+ 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia,
882
+ /* 中文衬线 */
883
+ 'Songti SC', 'SimSun', 'Noto Serif CJK SC', 'Source Han Serif SC',
884
+ serif;
885
+
886
+ /* 字距:默认 0。中文与拉丁的字距需求不同,
887
+ 阿拉伯/希伯来文连字后密度更高,需要更松 —— 下面按语言覆盖。 */
888
+ --tracking-normal: 0;
889
+ }
890
+
891
+ /* 🔴 阿拉伯语与希伯来语:需要**更宽松的字距**。
892
+ 阿拉伯文的字形连接后视觉密度比拉丁高,
893
+ 同样的字距在两套文字里"挤的程度"不同。 */
894
+ :root:lang(ar), :root:lang(fa), :root:lang(he), :root:lang(ur) {
895
+ --tracking-normal: 0.01em;
896
+ }
897
+
898
+
899
+ /* ============================================================================
900
+ 🔴 修 iOS 实机报的「点击时出现奇怪的灰黑纺锤形闪动」
901
+ ----------------------------------------------------------------------------
902
+ 原始反馈:「点击单选和复选的选项的时候会出现非常奇怪的阴影残留,
903
+ 那个阴影居然是**纺锤形**的,或者是极快的那个**灰黑色的底色闪动**」。
904
+
905
+ ⭐ 真因(**iOS Safari 的默认行为**,不是我们的布局错):
906
+ iOS Safari 会给**被点击的元素**加一层半透明灰黑遮罩
907
+ (约 rgba(0,0,0,0.18)),这层遮罩**跟着元素的形状与圆角** ——
908
+ 我们的 `.choice__mark` 是**圆角方块** ⇒ 遮罩就成了「纺锤形」。
909
+
910
+ ⚠️ 而且真正被点的不是那个 mark,是**铺满整个 `.choice` 的
911
+ 透明 input**(`opacity:0; inset:0`)⇒ 遮罩画在这个 input 上,
912
+ iOS 按它的圆角裁切 ⇒ 形状更怪、闪烁更明显。
913
+
914
+ ⇒ 修法(业界标准做法):**关掉 tap highlight**。
915
+ 本库每个控件都有自己的 `:active` / `:checked` 反馈,
916
+ 不需要 iOS 那层系统遮罩 —— 它既不统一又难看。
917
+
918
+ ⚠️ 只在**可点元素**上关,**不要全局 `*`**
919
+ —— 那会把链接与按钮的原生反馈一起干掉。 */
920
+ a,
921
+ button,
922
+ label,
923
+ summary,
924
+ input[type="checkbox"],
925
+ input[type="radio"],
926
+ input[type="button"],
927
+ input[type="submit"],
928
+ [role="button"],
929
+ [role="switch"],
930
+ [role="tab"],
931
+ [role="option"],
932
+ .choice,
933
+ .switch,
934
+ .btn,
935
+ .accordion__trigger,
936
+ .tabs__tab,
937
+ .card,
938
+ .list__row {
939
+ -webkit-tap-highlight-color: transparent;
940
+ }
941
+
942
+ /* ⚠️ 关掉系统遮罩后,点下去**必须仍有反馈**(否则用户会以为没点中)。
943
+ 下面给统一的按压反馈:轻微缩放 + 变背景。
944
+ ⚠️ 用 `:active`(鼠标与触摸都触发),不用 `:focus`
945
+ —— focus 会留下"粘住"的高亮,正是我们要避免的。 */
946
+ .choice:active,
947
+ .switch:active,
948
+ .btn:active,
949
+ .list__row:active,
950
+ .card:active {
951
+ opacity: 0.82;
952
+ }
953
+
954
+ @media (hover: hover) and (pointer: fine) {
955
+ /* 🔴 桌面端不用 :active 做反馈(鼠标按下就闪一下很廉价)
956
+ —— 桌面已有 :hover,够用了。 */
957
+ .choice:active,
958
+ .switch:active,
959
+ .btn:active,
960
+ .list__row:active,
961
+ .card:active { opacity: 1; }
962
+ }
963
+
964
+ /* ==========================================================================
965
+ 🔴 命中区尺寸令牌(做分页时发现的全库缺口)
966
+ --------------------------------------------------------------------------
967
+ 为什么需要:全库到处硬写 `44px`(各组件各写一遍)⇒
968
+ 改一次要翻遍全库,且没人能保证它们一致。
969
+
970
+ 为什么是 44px:**WCAG 2.5.5 Target Size (Level AAA)** 要求 44×44 CSS px。
971
+ ⚠️ 注意区分:
972
+ · WCAG 2.5.8(AA 级)只要 24×24 */