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,124 @@
1
+ /**
2
+ * typeahead.js — 「首字母跳转 / 连续打字定位」微行为核
3
+ *
4
+ * ==========================================================================
5
+ * 🔴 为什么要抽成核(实测,不是推测)
6
+ * --------------------------------------------------------------------------
7
+ * 迁移前只有两处实现,而它们**各做了一半**:
8
+ *
9
+ * · select:累积多字符前缀("co" ⇒ components),但**不循环** ——
10
+ * 连按两次同一个字母,游标**不动**(用户以为卡了)
11
+ * · tree: 单字符跳转且会往后找,但**不累积** ——
12
+ * 打 "co" 只会先跳到 c 项再跳到 o 项,永远到不了 "components"
13
+ *
14
+ * ⇒ 两处都缺另一半,而且缺的**不是同一半**。
15
+ * 这正是"同一件事写 N 份"的典型代价:没有一处是完整的,
16
+ * 而"完整"这件事没有任何地方定义过。
17
+ *
18
+ * ==========================================================================
19
+ * 边界(照 APG 的 typeahead 约定,不是我的设计)
20
+ * --------------------------------------------------------------------------
21
+ * ① 打一个可打印字符 ⇒ 跳到**下一个**名称以该字符开头的项(会绕回)
22
+ * ② 快速连打多个字符 ⇒ 缓冲**累积**,按前缀匹配
23
+ * ③ 连打**同一个**字符 ⇒ 在匹配项之间**循环**(不是停在原地)
24
+ * ④ 停顿超过 `timeout`(默认 500ms)⇒ 缓冲清空,重新累积
25
+ * ⑤ 禁用项不参与匹配
26
+ * ⑥ 不匹配 ⇒ 不动焦点,也不报错(APG 不要求"没找到"有任何反馈)
27
+ *
28
+ * ==========================================================================
29
+ * 用法
30
+ * --------------------------------------------------------------------------
31
+ * var ta = flTypeahead({
32
+ * items: function () { return options; }, // 候选集(按视觉顺序)
33
+ * textOf: function (x) { return x.textContent; }, // 项 → 用于匹配的文本
34
+ * usable: function (x) { return !x.disabled; }, // 可选:跳过禁用项
35
+ * timeout: 500, // 缓冲保留时长(ms)
36
+ * onHit: function (i, item, buf) { setActive(i); }
37
+ * });
38
+ * ta.type(ch); // 喂一个字符,返回命中索引(没命中 -1)
39
+ * ta.clear(); // 主动清空缓冲(关闭/重开时调)
40
+ * ta.destroy(); // 清掉定时器
41
+ *
42
+ * ⚠️ 核**不监听键盘**:什么时候算"打字"由组件决定
43
+ * (本组件的 Enter/Space/Tab 各有语义,核不该抢)。
44
+ *
45
+ * ==========================================================================
46
+ * ⚠️ 注入块里的注释会**被复制进每个使用点**(gzip 也要算)
47
+ * --------------------------------------------------------------------------
48
+ * ⇒ 长解释一律写在**本文件头**(BEGIN 标记之外),注入块内只留一行提示。
49
+ *
50
+ * ④ 为什么"连打同一个字符"要特判:不特判的话 buf 会变成 "bb",
51
+ * 而没有任何项以 "bb" 开头 ⇒ 连按两次同一个字母就再也匹配不上,
52
+ * 用户眼里就是"按第二次没反应"。按首字符匹配 + 从当前项之后找 ⇒ 变成循环。
53
+ * ④b 超时清空**只清缓冲、不清 last**:清空后重新打字仍应"从当前项往后找",
54
+ * 否则会突然跳回第一个匹配项(用户眼里就是"乱跳")。
55
+ *
56
+ * ==========================================================================
57
+ * 约束:ES5 · 零依赖 · 可被整块注入 · 不新增任何全局
58
+ * ==========================================================================
59
+ */
60
+ /* ==== BEHAVIOR CORE BEGIN: typeahead ==== */
61
+ var flTypeahead = function (opts) {
62
+ var opt = opts || {};
63
+ var items = opt.items || function () { return []; };
64
+ var textOf = opt.textOf || function (x) { return String((x && x.textContent) || ''); };
65
+ var usable = opt.usable || function () { return true; };
66
+ var timeout = opt.timeout || 500;
67
+ var onHit = opt.onHit || function () {};
68
+ var buf = '';
69
+ var timer = null;
70
+ var last = -1;
71
+
72
+ function stop() {
73
+ if (timer) { clearTimeout(timer); timer = null; }
74
+ }
75
+
76
+ /* 连打同一字符 ⇒ 只按首字符匹配(理由见文件头 ③④)*/
77
+ function repeated(s) {
78
+ if (s.length < 2) return false;
79
+ for (var i = 1; i < s.length; i++) {
80
+ if (s.charAt(i) !== s.charAt(0)) return false;
81
+ }
82
+ return true;
83
+ }
84
+
85
+ function find(needle) {
86
+ var l = items() || [];
87
+ var n = l.length;
88
+ if (!n) return -1;
89
+ for (var k = 0; k < n; k++) {
90
+ var i = (last + 1 + k) % n; /* ① 从当前项之后开始 ⇒ 会绕回 */
91
+ if (!usable(l[i])) continue; /* ⑤ */
92
+ if (textOf(l[i]).toLowerCase().indexOf(needle) === 0) return i;
93
+ }
94
+ return -1; /* ⑥ */
95
+ }
96
+
97
+ function type(ch) {
98
+ if (!ch) return -1;
99
+ buf += String(ch).toLowerCase(); /* ② */
100
+ stop();
101
+ timer = setTimeout(clear, timeout); /* ④ */
102
+ var needle = repeated(buf) ? buf.charAt(0) : buf; /* ③ */
103
+ var i = find(needle);
104
+ if (i >= 0) {
105
+ last = i;
106
+ onHit(i, (items() || [])[i], buf);
107
+ }
108
+ return i;
109
+ }
110
+
111
+ /* 只清缓冲,不清 last(理由见文件头)*/
112
+ function clear() {
113
+ buf = '';
114
+ stop();
115
+ }
116
+
117
+ return {
118
+ type: type,
119
+ clear: clear,
120
+ buffer: function () { return buf; },
121
+ destroy: function () { stop(); },
122
+ };
123
+ };
124
+ /* ==== BEHAVIOR CORE END: typeahead ==== */
@@ -0,0 +1,133 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
+ <meta name="color-scheme" content="light dark">
7
+ <title>焦点环 ·· frontend-lib</title>
8
+ <link rel="icon" href="data:,">
9
+ <link rel="stylesheet" href="tokens.css">
10
+ <link rel="stylesheet" href="typography.css">
11
+ <link rel="stylesheet" href="focus-ring.css">
12
+ <link rel="stylesheet" href="scrollbar.css">
13
+ <link rel="stylesheet" href="../01-tokens/page.css">
14
+ <style>
15
+ table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); margin: 0 0 var(--sp-4); }
16
+ th, td { text-align: left; padding: var(--sp-2) var(--sp-3);
17
+ border-bottom: 1px solid var(--border-decor); }
18
+ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
19
+ .ok { color: var(--success); }
20
+ .no { color: var(--danger); }
21
+ /* 🔴 读屏专用:视觉隐藏但**仍在无障碍树里** (不能用 display:none —— 那会让读屏也读不到) */
22
+ .fr-sr {
23
+ position: absolute; width: 1px; height: 1px;
24
+ padding: 0; margin: -1px; overflow: hidden;
25
+ clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
26
+ }
27
+ .fr-field { position: relative; display: inline-flex; }
28
+ .row { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center;
29
+ margin: 0 0 var(--sp-4); }
30
+ .btn {
31
+ min-height: 44px; padding: var(--sp-2) var(--sp-4); font: inherit;
32
+ font-size: var(--fs-sm); color: var(--text-on-accent);
33
+ background: var(--accent); border: 0; border-radius: var(--r-sm); cursor: pointer;
34
+ }
35
+ .btn--ghost { background: var(--surface); color: var(--text-primary);
36
+ border: 1px solid var(--border-control); }
37
+ .btn--round { border-radius: 50%; width: 44px; padding: 0; }
38
+ input[type="text"], input[type="email"] {
39
+ min-height: 44px; padding: var(--sp-2) var(--sp-3); font: inherit;
40
+ font-size: var(--fs-sm); color: var(--text-primary);
41
+ background: var(--surface); border: 1px solid var(--border-control);
42
+ border-radius: var(--r-sm); max-width: 260px;
43
+ }
44
+ a { color: var(--accent); }
45
+ .tip { font-size: var(--fs-xs); color: var(--text-tertiary); }
46
+ </style>
47
+ </head>
48
+ <body class="page page">
49
+ <div class="wrap">
50
+
51
+ <h1>焦点环</h1>
52
+ <p class="lede">
53
+ <code></code>。<b>按 Tab 键</b>体验,<b>点鼠标</b>不会显示。
54
+ </p>
55
+
56
+ <h2 class="rule-top">1 · 基本形态</h2>
57
+ <p class="note">
58
+ <code>:focus-visible</code> —— <b>只给键盘用户看</b>,鼠标点击不出现。
59
+ 这正是它比 <code>:focus</code> 好的地方(WCAG 2.4.7 只要求"键盘操作时有可见焦点")。
60
+ </p>
61
+ <div class="row">
62
+ <button class="btn" type="button">按钮</button>
63
+ <button class="btn btn--ghost" type="button">次要按钮</button>
64
+ <button class="btn btn--ghost btn--round" type="button" aria-label="圆形按钮">◎</button>
65
+ <!-- 🔴 a11y 门禁抓到:<input> 只有 placeholder,没有可访问名称
66
+ ⚠️ **placeholder 不是 label**(WCAG 3.3.2 / 4.1.2):
67
+ 占位符在输入后就消失了,读屏软件也未必会念它。
68
+ ⇒ 补一个 <label for>;视觉上用 .sr-only 隐藏(不改变布局)。 -->
69
+ <span class="fr-field">
70
+ <label class="fr-sr" for="fr-demo-input">示例输入框</label>
71
+ <input id="fr-demo-input" type="text" placeholder="输入框">
72
+ </span>
73
+ <a href="#">链接</a>
74
+ </div>
75
+ <p class="tip">
76
+ 试试:先点一下页面空白处(清掉焦点),再按 <b>Tab</b>。
77
+ </p>
78
+
79
+ <h2 class="rule-top">2 · 为什么用 <code>outline</code> 而不是 <code>box-shadow</code></h2>
80
+ <table>
81
+ <tr><th>做法</th><th>是否占布局</th><th>跟随圆角</th><th>高对比度模式</th></tr>
82
+ <tr><td><code>outline</code> ✅</td>
83
+ <td class="ok">不占</td><td class="ok">自动跟随</td><td class="ok">浏览器自动加强</td></tr>
84
+ <tr><td><code>box-shadow</code></td>
85
+ <td class="no">占(会引起回流)</td><td class="no">要自己设</td><td class="no">不响应</td></tr>
86
+ </table>
87
+ <p class="note">
88
+ 关键差别:<code>box-shadow</code> <b>会占布局</b> ⇒ 聚焦时元素尺寸变化 ⇒
89
+ 周围内容<b>回流</b>。焦点环是<b>纯视觉提示</b>,不该推动任何东西。
90
+ </p>
91
+
92
+ <h2 class="rule-top">3 · 单一真相源</h2>
93
+ <p class="note">
94
+ 全库的焦点环只认这三个令牌(<code>01-tokens/focus-ring.css</code>):
95
+ </p>
96
+ <table>
97
+ <tr><th>令牌</th><th class="num">值</th><th>作用</th></tr>
98
+ <tr><td><code>--focus-ring-width</code></td><td class="num">2px</td><td>环的粗细</td></tr>
99
+ <tr><td><code>--focus-ring-offset</code></td><td class="num">2px</td><td>环离元素的距离</td></tr>
100
+ <tr><td><code>--focus-ring-color</code></td><td class="num">→ accent</td><td>环的颜色(跟随主题)</td></tr>
101
+ </table>
102
+ <p class="note">
103
+ 要改焦点环的观感,<b>只改这三个令牌</b>,不要去每个组件里改。
104
+ <br>(实测:改造前全库有 <b>33 处</b> <code>:focus-visible</code>,
105
+ 其中 6 处已是统一的 <code>2px solid var(--accent)</code>,
106
+ 其余是散落的写法 ⇒ 现在统一到一处。)
107
+ </p>
108
+
109
+ <h2 class="rule-top">4 · 暗色下的加强</h2>
110
+ <p class="note">
111
+ 暗色底上纯 outline 可能对比不足 ⇒ 加一层
112
+ <code>box-shadow: 0 0 0 1px rgba(255,255,255,.9)</code> 的内描边。
113
+ <br>⚠️ 用 box-shadow <b>而不是改 outline</b>,这样<b>仍然不占布局</b>。
114
+ </p>
115
+
116
+ <h2 class="rule-top">5 · 焦点不被遮住(WCAG 2.4.11)</h2>
117
+ <p class="note">
118
+ 固定头 / 吸底栏可能盖住聚焦的元素 ⇒
119
+ 给所有可聚焦元素加了 <code>scroll-margin-block</code>,
120
+ 浏览器聚焦时会<b>自动留出空间</b>。
121
+ </p>
122
+
123
+ <h2 class="rule-top">6 · 禁区:不得裸写 <code>outline: none</code></h2>
124
+ <p class="note">
125
+ 那会让键盘用户<b>完全看不到焦点</b>。
126
+ 若必须去掉默认环,必须在<b>同一规则里</b>立刻给出替代样式。
127
+ <br><b>本库现状</b>:4 处 <code>outline:none</code>,
128
+ <span class="ok">全部紧跟替代样式 ✅</span>(门禁会持续盯这个)。
129
+ </p>
130
+
131
+ </div>
132
+ </body>
133
+ </html>
@@ -0,0 +1,102 @@
1
+ /* ============================================================================
2
+ focus-ring.css — 焦点环(全局基础设施 ·
3
+ ----------------------------------------------------------------------------
4
+ 🔴 判据:WCAG 2.4.13 Focus Appearance。
5
+ ============================================================================
6
+ ⭐ 依据
7
+ ----------------------------------------------------------------------------
8
+ · **WCAG 2.4.7 Focus Visible**(A 级)—— 键盘操作必须有可见焦点
9
+ · **WCAG 2.4.11 Focus Not Obscured (Minimum) / 2.4.13 (Enhanced)**
10
+ —— 焦点元素**不能被固定元素遮住**(悬浮头/吸底栏会盖住)
11
+ · **WCAG 1.4.11 非文本对比** —— 焦点指示器需 ≥ 3:1
12
+ · **`:focus-visible` 的语义** —— **只给键盘用户看**,
13
+ 鼠标点击不显示(这正是它比 `:focus` 好的地方)
14
+
15
+ ⚠️ 为什么用 `outline` 而不是 `box-shadow`
16
+ · `outline` **不占布局空间** ⇒ 聚焦时**不会引起回流**(`box-shadow` 会)
17
+ · `outline` 会**自动跟随元素的 `border-radius`**(现代浏览器)
18
+ · `outline` 是**系统级**的:高对比度模式下浏览器会自动加强它
19
+ ⇒ 通行做法是 `outline`,不是自绘的 box-shadow。
20
+
21
+ ⚠️ 禁区:**不得裸写 `outline: none`**
22
+ 那会让键盘用户**完全看不到焦点**。
23
+ 若必须去掉默认环,必须在**同一规则里**立刻给出替代样式。
24
+ (本文件末尾有一道门禁扫这个。)
25
+ ========================================================================== */
26
+
27
+ :root {
28
+ /* 🔴 单一真相源:全库的焦点环只认这三个令牌 */
29
+ --focus-ring-width: 2px;
30
+ --focus-ring-offset: 2px;
31
+ --focus-ring-color: var(--accent);
32
+ --focus-ring-inset: rgba(255, 255, 255, .9); /* 暗色下在深底上更清楚 */
33
+ }
34
+
35
+ /* ============================================================================
36
+ 一、默认焦点环
37
+ ----------------------------------------------------------------------------
38
+ ⚠️ 用 `:focus-visible` 而不是 `:focus`:
39
+ 键盘 Tab 才出现,鼠标点击不出现 —— 这正是 WCAG 想要的行为。
40
+ ========================================================================== */
41
+ :focus-visible {
42
+ outline: var(--focus-ring-width) solid var(--focus-ring-color);
43
+ outline-offset: var(--focus-ring-offset);
44
+ }
45
+
46
+ /* ============================================================================
47
+ 二、暗色下的加强
48
+ ----------------------------------------------------------------------------
49
+ 暗色底上纯 `outline` 可能不够清楚(对比不足)⇒ 加一层内描边。
50
+ ⚠️ 用 `box-shadow` 而不是改 outline —— 这样**不占布局**。
51
+ ==========================================================================
52
+ 🔴 暗色改为纯 CSS 表达(见 docs/BENCHMARK.md 一、二节)
53
+ ----------------------------------------------------------------------------
54
+ ⚠️ 两个选择器都要写:
55
+ · data-theme-current = **解析后**生效的明暗(由 JS 落,覆盖"跟随系统")
56
+ · data-theme = **使用者显式选的**(纯 CSS,不加载 JS 也成立)
57
+ 只写前者 ⇒ 不加载 JS 时强制暗色的页面拿不到加强;只写后者 ⇒ 跟随系统
58
+ 进入的暗色拿不到加强。两个都不能省。
59
+
60
+ 🔴 `data-theme` 两项**不写 `:root` 限定**:暗色可挂在任意容器上
61
+ (RFC-THEME-SCOPE D1)。`data-theme-current` 仍限 `:root` —— 那是 JS 落在根上的。 */
62
+ [data-theme="dark"] :focus-visible,
63
+ [data-theme="dark"]:focus-visible,
64
+ :root[data-theme-current="dark"] :focus-visible,
65
+ :root[data-theme-current="dark"]:focus-visible {
66
+ outline-color: var(--accent-hover);
67
+ box-shadow: 0 0 0 1px var(--focus-ring-inset);
68
+ }
69
+
70
+ /* ============================================================================
71
+ 三、焦点不被遮住(WCAG 2.4.11)
72
+ ----------------------------------------------------------------------------
73
+ 固定头/吸底栏可能盖住聚焦元素。
74
+ ⇒ 给这些固定元素加 `scroll-margin`,聚焦时浏览器会自动留出空间。
75
+ ========================================================================== */
76
+ :where(a, button, input, select, textarea, [tabindex]) {
77
+ scroll-margin-block: var(--focus-ring-offset) 5vh;
78
+ }
79
+
80
+ /* ============================================================================
81
+ 四、只在键盘时"上浮"(可选的增强)
82
+ ----------------------------------------------------------------------------
83
+ 聚焦时轻微提亮,帮助用户追踪焦点在长内容里的位置。
84
+ ⚠️ 用 `transition` 且尊重 reduced-motion(见 charter 07)。
85
+ ========================================================================== */
86
+ @media (prefers-reduced-motion: no-preference) {
87
+ :where(a, button, [role="button"], [role="tab"]) {
88
+ transition: outline-offset var(--dur-fast) var(--ease-out);
89
+ }
90
+ :where(a, button, [role="button"], [role="tab"]):focus-visible {
91
+ outline-offset: calc(var(--focus-ring-offset) + 1px);
92
+ }
93
+ }
94
+
95
+ /* ============================================================================
96
+ 五、工具类:给需要"内嵌焦点环"的场景(如已有一层边框的按钮)
97
+ ----------------------------------------------------------------------------
98
+ 用 `outline-offset: -2px` 把环画在**元素内侧**,不撑大布局。
99
+ ========================================================================== */
100
+ .focus-inset:focus-visible {
101
+ outline-offset: calc(var(--focus-ring-width) * -1);
102
+ }
@@ -0,0 +1,244 @@
1
+ /* ============================================================================
2
+ frontend-lib / 01-tokens / page.css
3
+ 页面骨架(跨页面共享的"全局"层)
4
+
5
+ 依赖:tokens.css(只用变量,不写死值)
6
+ 版本:A ✅ / B ✅ / C ✅
7
+
8
+ 🔴🔴 为什么要有这个文件(0.4.2 · 实测事故驱动)
9
+ ----------------------------------------------------------------------------
10
+ 事故:用本库做十几页的成套页面,改一处"全局"的东西,整批立刻崩。
11
+
12
+ 复盘结论不是"模型笨",而是**这个库根本没有"全局"**:
13
+ 「页面骨架」被 33 个页面**各抄了一份**在内联 <style> 里。实测(脚本量过):
14
+
15
+ 选择器 出现在 写法种数
16
+ h1 36 页 8
17
+ h2 35 页 11
18
+ .wrap 33 页 16 ← 列宽从 680px 到 900px 有 13 个不同数字
19
+ body 32 页 9
20
+ code 24 页 9
21
+ .lede 21 页 7 ← typography.css 里**已经有了**,页面还在重抄
22
+ .note 21 页 10 ← 同上
23
+ .box 13 页 2
24
+
25
+ 于是"改全局"这件事在物理上不可能成立:
26
+ 没有一处可改,只有 33 处要改 —— 漏一处就是不一致,改错一处就是崩。
27
+
28
+ 本文件把上面这些收编成**一处定义**。页面不再声明它们,
29
+ 改这里(或改对应令牌)⇒ 全部页面一起变。
30
+ ============================================================================
31
+
32
+ 🔴 为什么全部挂在 `.page` 下面(作用域,不是偷懒)
33
+ ----------------------------------------------------------------------------
34
+ 裸标签选择器(`h1 {}`)的作用域是**整个文档**。
35
+ 本库若留下这种规则,宿主页面后面怎么改都会被它影响
36
+ (05-audit/bleed-gate.py 就是拦这个的,它禁止组件 CSS 里的裸标签)。
37
+
38
+ 骨架也必须先遵守这条 ⇒ 全部写成 `.page h1`、`.page .wrap`。
39
+ 用法:`<body class="page">` 才生效 —— **不引就不生效**,
40
+ 宿主页面不会莫名其妙被改。
41
+
42
+ 代价:每个页面要写一次 `class="page"`。
43
+ 收益:库对宿主页面**零副作用**,且骨架仍然只有一处定义。
44
+ 这两件事没有第三个选项(要么污染全局,要么显式挂载),这里选后者。
45
+ ============================================================================
46
+
47
+ ⚠️ 加载顺序:本文件要放在 tokens.css / typography.css **之后**、
48
+ 组件 CSS 与页面内联 <style> **之前**。
49
+ 这样组件若要覆盖骨架,靠"后来者同特异性胜出"就能做到,不需要 !important。
50
+ ========================================================================= */
51
+
52
+ /* ============================================================================
53
+ 1. 页面框(body)
54
+ ----------------------------------------------------------------------------
55
+ 众数写法是 `background: var(--paper); margin: 0; padding: var(--sp-7) var(--sp-5)`
56
+ (10/32 页)。这里在众数基础上补两件众数里漏掉、但真机上必需的事:
57
+
58
+ ① safe-area:iOS 有刘海/底部横条的机型上,
59
+ 不写 env() 的内容会被刘海和横条压住(实测 10-review/ios 就是这个原因)。
60
+ env() 在无安全区的设备上求值为 0 ⇒ 对普通机型**无副作用**。
61
+
62
+ ② 字体:众数写法**没有** font-family / font-size,
63
+ 靠浏览器默认(Times 一类的衬线默认字体,16px)。
64
+ 页面因此在不同系统上观感不一致 ⇒ 这里显式落到令牌上。
65
+ ========================================================================= */
66
+ .page {
67
+ margin: 0;
68
+ /* 🔴 两行是有顺序的,别合并、别调换:
69
+ 先 100vh(所有浏览器都认),再 100dvh(动态视口,新浏览器覆盖上一行)。
70
+ 只用 100vh 时,iOS Safari 收起地址栏后 100vh 不跟着变 ⇒ 底部留白
71
+ (10-review/ios 就是为这个才单独写了一份 body —— 现在回到骨架里)。 */
72
+ min-height: 100vh;
73
+ min-height: 100dvh;
74
+ background: var(--paper);
75
+ color: var(--text-primary);
76
+ font-family: var(--sans);
77
+ font-size: var(--fs-base);
78
+ line-height: var(--lh-base);
79
+
80
+ /* iOS 横屏会自动放大字号,导致布局跟竖屏不一致 —— 关掉这个行为。 */
81
+ -webkit-text-size-adjust: 100%;
82
+
83
+ padding-block: calc(env(safe-area-inset-top, 0px) + var(--sp-6))
84
+ calc(env(safe-area-inset-bottom, 0px) + var(--sp-6));
85
+ padding-inline: max(env(safe-area-inset-right, 0px), var(--sp-4))
86
+ max(env(safe-area-inset-left, 0px), var(--sp-4));
87
+ }
88
+
89
+ /* ============================================================================
90
+ 1b. 盒模型(`border-box`)—— 只在 .page 范围内生效
91
+ ----------------------------------------------------------------------------
92
+ 🔴 0.4.2 补。实测证据(02-primitives/date-range/demo.html @393px):
93
+ `.drange__input { width: 100%; padding: 0 var(--sp-3); border: 1px }`
94
+ 算出来内容是 162.08px,**实际盒宽 188px**(162 + 12×2 + 1×2)
95
+ ⇒ 比它的容器宽 26px ⇒ 两个日期框并排时把整页撑出横向滚动。
96
+
97
+ 根因不是这一个组件写错了,而是**全库没有统一的盒模型**:
98
+ 翻遍 01/02/03/04/09,`box-sizing` 只出现 5 次,都是某一处临时补的
99
+ (input.css / switch.css / dropdown.css / overlay.css / model-viewer.css)。
100
+ ⇒ 谁记得写谁没事,谁忘了谁静默溢出 —— 又是一次"漏一处就崩"。
101
+
102
+ ⚠️ 为什么不放在 tokens.css 写成 `* { box-sizing: border-box }`:
103
+ 那是**全局 reset**,会改掉宿主页面自己的所有盒子。
104
+ 组件库不该改宿主的盒模型(compare 05-audit/bleed-gate.py 的立论)。
105
+ ⇒ 放进骨架、挂 `.page`:**选配**。宿主不用骨架就不受影响。
106
+
107
+ 为什么是 `.page *` 而不是逐个组件补:
108
+ 逐个补 = 永远会漏下一个新组件。这一条把"新组件忘了写"变成不可能。
109
+ ========================================================================= */
110
+ .page *,
111
+ .page *::before,
112
+ .page *::after {
113
+ box-sizing: border-box;
114
+ }
115
+
116
+ /* ============================================================================
117
+ 2. 内容列 `.wrap`
118
+ ----------------------------------------------------------------------------
119
+ 🔴 这是本次事故最典型的一处:33 个页面写了 16 种 `.wrap`,
120
+ max-width 出现过的数字有 680 / 720 / 760 / 800 / 820 / 860 / 900 px
121
+ 和 42 / 44 / 46 / 52 rem,还有 1 页写 72ch。
122
+ 它们之间**没有语义差别**,纯粹是每次手敲时随手写的。
123
+
124
+ 现在只有一处:`min(var(--measure-page), 100%)`。
125
+ · --measure-page(52rem)来自实测众数 820px,见 tokens.css 里的注释
126
+ · min(…, 100%):列宽永远不超过视口 ⇒ 窄屏上不会横向溢出
127
+ ========================================================================= */
128
+ .page .wrap {
129
+ margin-inline: auto;
130
+ max-width: min(var(--measure-page), 100%);
131
+ }
132
+
133
+ /* 窄列:长文。68ch 是阅读上限,宽了眼睛回扫会跳行。
134
+ 只有长文页需要,其余页面一律用上面的默认列宽。 */
135
+ .page .wrap--prose {
136
+ max-width: min(var(--measure-prose), 100%);
137
+ }
138
+
139
+ /* ============================================================================
140
+ 3. 标题阶梯
141
+ ----------------------------------------------------------------------------
142
+ h1:实测 8 种写法。这里取**最讲究的那一种**(list / nav / states 三页在用:
143
+ font-weight 500 + 收紧字距 + --measure-title 限宽),
144
+ 而不是取数量最多的那一种(19 页,只有 font-size/line-height/margin)。
145
+ 理由:19 页那种是"没写完",不是"另一种设计" ——
146
+ 它缺少的正是标题该有的三件事(字重、字距、限宽)。
147
+ 收敛成"讲究版"之后,19 页的标题会变整齐,这是改进不是回归。
148
+
149
+ h2:实测 11 种写法,但它们其实是**两种东西**混在一起:
150
+ · 光秃秃的小节标题(16 页)
151
+ · 带分隔线的小节标题(13 页用上边线、6 页用下边线)
152
+ ⇒ 骨架只定义"光秃秃"的基础款;分隔线做成两个修饰类,
153
+ 要线的页面加 class,不要自己再写一遍 border。
154
+ ========================================================================= */
155
+ .page h1 {
156
+ font-family: var(--sans);
157
+ font-size: var(--fs-2xl);
158
+ font-weight: 500;
159
+ letter-spacing: var(--tracking-title);
160
+ line-height: var(--lh-tight);
161
+ margin: 0 0 var(--sp-3);
162
+ /* 标题比正文窄:大字长时间扫视会累(--measure-title 见 tokens.css) */
163
+ max-width: var(--measure-title);
164
+ }
165
+
166
+ .page h2 {
167
+ font-size: var(--fs-lg);
168
+ margin: var(--sp-7) 0 var(--sp-3);
169
+ }
170
+
171
+ /* 小节分隔线:加在 h2(或任何块)上。两种方向,二选一,不要同时用。 */
172
+ .page .rule-top {
173
+ border-top: 1px solid var(--border-decor);
174
+ padding-top: var(--sp-4);
175
+ }
176
+ .page .rule-bottom {
177
+ border-bottom: 1px solid var(--border-decor);
178
+ padding-bottom: var(--sp-2);
179
+ margin-bottom: var(--sp-4);
180
+ }
181
+
182
+ /* ============================================================================
183
+ 4. 行内代码 `code`
184
+ ----------------------------------------------------------------------------
185
+ typography.css 已经定义了 `code { font-family: var(--mono) }`。
186
+ 这里补齐"行内代码片段"剩下的部分(底色 / 内边距 / 圆角 / 相对字号),
187
+ 取自实测众数(badge / separator / switch / accordion / tabs / sparkline 六页)。
188
+
189
+ ⚠️ 字号用 em 不用 rem:行内代码要跟着**它所在的那段文字**缩放。
190
+ 在 .small 里就该更小,在 h2 里就该更大。写成固定值会在小字区里显得突兀。
191
+ ========================================================================= */
192
+ .page code {
193
+ font-family: var(--mono);
194
+ font-size: 0.9em;
195
+ background: var(--surface-sunken);
196
+ padding: 1px 4px;
197
+ border-radius: var(--r-xs);
198
+ }
199
+
200
+ /* ============================================================================
201
+ 5. 演示块 `.box`
202
+ ----------------------------------------------------------------------------
203
+ 13 页里 12 页写法完全一致(`background: var(--surface)` + 1px 边框 +
204
+ --r-md 圆角 + --sp-5 内边距),只有 1 页多了个 margin。
205
+ ⇒ 直接收编,多出来的那页用 --box--tight 之外的方式解决(它其实不需要)。
206
+ ========================================================================= */
207
+ .page .box {
208
+ background: var(--surface);
209
+ border: 1px solid var(--border-decor);
210
+ border-radius: var(--r-md);
211
+ padding: var(--sp-5);
212
+ }
213
+
214
+ /* 反例块:同一块,底色沉一档,用来放"错误示范" */
215
+ .page .box.bad {
216
+ background: var(--surface-sunken);
217
+ }
218
+
219
+ /* ============================================================================
220
+ 反模式(本文件自己的边界)
221
+ ----------------------------------------------------------------------------
222
+ 1. 在页面内联 <style> 里再写一遍上面任何一个选择器
223
+ 写了也不会生效吗?**会生效一部分,而且是静默的**:
224
+ `.page h1`(0,1,1)会盖掉页面的 `h1`(0,0,1),
225
+ 页面那句变成死代码 —— 不报错、看不出来,只在你以为改了的时候没改。
226
+ ⇒ 05-audit/shell-gate.py 拦这个。
227
+
228
+ 2. 在这里加组件样式
229
+ 这是骨架层,只放"改一处必须全库跟随"的东西。
230
+ 组件的样式属于组件自己的 CSS。
231
+
232
+ 3. 为了一个页面改这里的公共值
233
+ 一页的特殊需求请用页面自己的 class(不要挂到骨架类上),
234
+ 或者在 tokens.css 里加令牌。**不要**为了迁就一页而把骨架改窄。
235
+
236
+ ⚠️ 有意没收编的(不是遗漏,是判断后不收):
237
+ · `.row` 15 页 14 种写法 —— 它是各页的局部排布工具,不是骨架;
238
+ 强行压成 1 种会破坏 9 个页面的实际布局。
239
+ · `.log` 10 页 7 种写法,且其中 2 页的 `.log` 是"一行淡字"、
240
+ 另 8 页是"日志面板" —— 一个类名两种语义,收编前要先改名。
241
+ · `.pad` 6 页 4 种写法,都是各页自己的占位高度。
242
+ 这三项的重复**不会**造成"改全局崩"(它们不是全局),
243
+ 所以留在页面里;shell-gate 会把它们作为"已知重复"报出来,但不 fail。
244
+ ========================================================================= */