agentcss 0.11.10 → 0.11.11

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 (36) hide show
  1. package/01-tokens/page.css +71 -0
  2. package/02-primitives/button/demo.html +5 -15
  3. package/02-primitives/card/demo.html +3 -13
  4. package/02-primitives/choice/demo.html +1 -0
  5. package/02-primitives/combobox/demo.html +38 -0
  6. package/02-primitives/date-range/demo.html +36 -0
  7. package/02-primitives/input/demo.html +5 -16
  8. package/02-primitives/popover/README.md +15 -0
  9. package/02-primitives/popover/demo.html +45 -0
  10. package/02-primitives/progress/demo.html +25 -0
  11. package/02-primitives/select/demo.html +46 -0
  12. package/02-primitives/skeleton/demo.html +28 -0
  13. package/03-patterns/content/demo.html +20 -0
  14. package/03-patterns/drawer/demo.html +36 -0
  15. package/03-patterns/dropdown/demo.html +39 -0
  16. package/03-patterns/list/demo.html +48 -0
  17. package/03-patterns/nav/demo.html +34 -1
  18. package/03-patterns/pagination/demo.html +40 -0
  19. package/03-patterns/reveal/demo.html +24 -0
  20. package/03-patterns/states/demo.html +25 -1
  21. package/03-patterns/tooltip/demo.html +27 -0
  22. package/03-patterns/tree/demo.html +43 -0
  23. package/04-recipes/analysis-report/demo.html +34 -0
  24. package/04-recipes/data-showcase/README.md +1 -0
  25. package/04-recipes/data-showcase/demo.html +29 -0
  26. package/04-recipes/detail-page/demo.html +28 -0
  27. package/04-recipes/report/demo.html +24 -0
  28. package/04-recipes/table/README.md +22 -0
  29. package/04-recipes/table/demo.html +39 -0
  30. package/CHANGELOG.md +68 -0
  31. package/CONTRIBUTING.md +2 -1
  32. package/ai/components.json +38 -10
  33. package/dist/01-tokens/page.css +1 -1
  34. package/dist/COSTS.md +1 -1
  35. package/index.html +1 -1
  36. package/package.json +1 -1
@@ -19,6 +19,20 @@
19
19
  font-family: var(--font-mono, monospace); font-size: var(--fs-xs);
20
20
  color: var(--text-secondary); min-height: 2.6em;
21
21
  }
22
+ .dd-cap { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); color: var(--text-tertiary); }
23
+ .dd-demo {
24
+ list-style: none; margin: 0; padding: var(--sp-1);
25
+ background: var(--surface-raised);
26
+ border: 1px solid var(--border-decor);
27
+ border-radius: var(--r-sm);
28
+ }
29
+ /* 反例专用:24px 一行(本库把它量出来过,见 dropdown.css 的复盘) */
30
+ .dd-bug {
31
+ display: flex; align-items: center;
32
+ height: 24px; padding: 0 var(--sp-3);
33
+ border-radius: var(--r-xs);
34
+ font-size: var(--fs-sm); cursor: pointer;
35
+ }
22
36
  </style>
23
37
  </head>
24
38
  <body class="page page">
@@ -73,6 +87,31 @@
73
87
  </div>
74
88
  </div>
75
89
 
90
+
91
+
92
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
93
+ <div class="cmp">
94
+ <div class="cmp__pane cmp__bad">
95
+ <h3>❌ 常见写法:菜单项只有 24px</h3>
96
+ <p class="dd-cap">下拉菜单 · 每项高度</p>
97
+ <ul class="dd-demo" aria-hidden="true">
98
+ <li class="dd-bug">按涨跌幅</li>
99
+ <li class="dd-bug">按市值</li>
100
+ <li class="dd-bug">按成交额</li>
101
+ </ul>
102
+ <p class="cmp__why">24px 一行:手指点下去会压到隔壁,触屏用户每点错一次就得重来。本库在这块踩过更隐蔽的坑 —— 写 <code>min-height: 44px</code>,量出来只有 24px(<code>&lt;li&gt;</code> 是 <code>display:list-item</code>),所以现在用的是显式 <code>height</code>。</p>
103
+ </div>
104
+ <div class="cmp__pane cmp__good">
105
+ <h3>✅ 本库:显式 <code>height: 44px</code></h3>
106
+ <p class="dd-cap">下拉菜单 · 每项高度</p>
107
+ <ul class="dd-demo" aria-hidden="true">
108
+ <li class="dd__item">按涨跌幅</li>
109
+ <li class="dd__item">按市值</li>
110
+ <li class="dd__item">按成交额</li>
111
+ </ul>
112
+ <p class="cmp__why">同一个 44,<code>min-height</code> 与 <code>height</code> 在列表项上是两回事。桌面小屏看不出来,只有量了才发现 —— 所以这里把两种结果并排摆出来。</p>
113
+ </div>
114
+ </div>
76
115
  </main>
77
116
 
78
117
  <script src="dropdown.js"></script>
@@ -17,6 +17,9 @@
17
17
  p { max-width: var(--measure-prose); margin: 0 0 var(--sp-3); }
18
18
  p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.65; }
19
19
  .bar { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-4); }
20
+ .ls-demo { list-style: none; margin: 0; padding: 0; background: var(--surface-raised);
21
+ border: 1px solid var(--border-decor); border-radius: var(--r-sm); }
22
+ .ls-clip { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
20
23
  </style>
21
24
  </head>
22
25
  <body class="page page">
@@ -85,6 +88,51 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
85
88
  这一点与欧美惯例相反,是刻意的——做反了会被当成数据出错。
86
89
  </p>
87
90
 
91
+
92
+
93
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
94
+ <div class="cmp">
95
+ <div class="cmp__pane cmp__bad">
96
+ <h3>❌ 常见写法:长文本直接换行</h3>
97
+ <ul class="ls-demo" aria-hidden="true">
98
+ <li class="list__item">
99
+ <span class="list__main">
100
+ <span class="list__title">长鑫存储技术有限公司(合肥)2026 年三季报</span>
101
+ <span class="list__meta">12 条记录 · 半导体</span>
102
+ </span>
103
+ <span class="list__value num list__value--up">+3.21%</span>
104
+ </li>
105
+ <li class="list__item">
106
+ <span class="list__main">
107
+ <span class="list__title">中芯国际集成电路制造</span>
108
+ <span class="list__meta">8 条记录 · 半导体</span>
109
+ </span>
110
+ <span class="list__value num list__value--down">−1.05%</span>
111
+ </li>
112
+ </ul>
113
+ <p class="cmp__why">第一行折成两行,行高就不一样了:一屏能扫到的条目从 8 条掉到 5 条,而且"哪一行对应哪个数字"要重新对。</p>
114
+ </div>
115
+ <div class="cmp__pane cmp__good">
116
+ <h3>✅ 本库:单行省略 + <code>title</code></h3>
117
+ <ul class="ls-demo" aria-hidden="true">
118
+ <li class="list__item">
119
+ <span class="list__main">
120
+ <span class="list__title ls-clip">长鑫存储技术有限公司(合肥)2026 年三季报</span>
121
+ <span class="list__meta">12 条记录 · 半导体</span>
122
+ </span>
123
+ <span class="list__value num list__value--up">+3.21%</span>
124
+ </li>
125
+ <li class="list__item">
126
+ <span class="list__main">
127
+ <span class="list__title ls-clip">中芯国际集成电路制造</span>
128
+ <span class="list__meta">8 条记录 · 半导体</span>
129
+ </span>
130
+ <span class="list__value num list__value--down">−1.05%</span>
131
+ </li>
132
+ </ul>
133
+ <p class="cmp__why">列表是"扫"的不是"读"的:标题单行截断,完整名称放 <code>title</code>,悬停可看。行高一致,眼睛才能顺着往下扫。</p>
134
+ </div>
135
+ </div>
88
136
  </main>
89
137
 
90
138
  <script src="flip.js"></script>
@@ -31,6 +31,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.7
31
31
  .spacer { height: 55vh; }
32
32
 
33
33
  /* 让目录高亮有滚动空间 */
34
+ .toc-clip { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
34
35
  </style>
35
36
  </head>
36
37
  <body class="page page">
@@ -112,7 +113,39 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.7
112
113
  早期版本用的是它 + <code>rootMargin</code>,结果滚到两节之间时观察区里
113
114
  一个 section 都没有,高亮卡住不动 —— 现在改成纯计算,任何滚动位置都有答案。
114
115
  </p>
115
- </main>
116
+
117
+
118
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
119
+ <!-- 目录项一律指向本页真实小节(`#mem` / `#sched` / `#sim`):假锚点
120
+ (`href="#x"`)点下去会跳到页面顶部,读者以为"目录坏了"。
121
+ 第一条故意写得长 —— 真实文档的长章节名就是这样,能不能单行是这条的判据。 -->
122
+ <div class="cmp">
123
+ <div class="cmp__pane cmp__bad">
124
+ <h3>❌ 常见写法:目录项允许换行</h3>
125
+ <nav class="toc" aria-label="示例目录" aria-hidden="true">
126
+ <p class="toc__title">本页</p>
127
+ <ul class="toc__list">
128
+ <li><a class="toc__link" href="#mem" tabindex="-1">存储器构成与 2026 年三季度的产能爬坡节奏</a></li>
129
+ <li><a class="toc__link" href="#sched" tabindex="-1">优先级与准入</a></li>
130
+ <li><a class="toc__link" href="#sim" tabindex="-1">把它跑起来</a></li>
131
+ </ul>
132
+ </nav>
133
+ <p class="cmp__why">一条目录折成两行,下面两条就都被推下去了 —— 目录的价值是"一眼看到全文结构",行高一变,结构感就没了。</p>
134
+ </div>
135
+ <div class="cmp__pane cmp__good">
136
+ <h3>✅ 本库:单行、超出省略</h3>
137
+ <nav class="toc" aria-label="示例目录">
138
+ <p class="toc__title">本页</p>
139
+ <ul class="toc__list">
140
+ <li><a class="toc__link toc-clip" href="#mem">存储器构成与 2026 年三季度的产能爬坡节奏</a></li>
141
+ <li><a class="toc__link" href="#sched">优先级与准入</a></li>
142
+ <li><a class="toc__link" href="#sim">把它跑起来</a></li>
143
+ </ul>
144
+ </nav>
145
+ <p class="cmp__why">目录项单行、超出省略:宁可截断,也要保住"三条等高"的节奏。读者扫的是结构,不是一个条目的全名。</p>
146
+ </div>
147
+ </div>
148
+ </main>
116
149
 
117
150
  </div>
118
151
 
@@ -30,6 +30,7 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
30
30
  .row .v { font-family: var(--mono); font-variant-numeric: tabular-nums; }
31
31
  .live-echo { margin: var(--sp-3) 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }
32
32
  .live-echo b { color: var(--text-secondary); font-family: var(--mono); }
33
+ .pagination__list { flex-wrap: wrap; }
33
34
  </style>
34
35
  </head>
35
36
  <body class="page page">
@@ -118,6 +119,45 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
118
119
  ⇒ <b>不产生横向溢出</b>。
119
120
  </p>
120
121
 
122
+
123
+
124
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
125
+ <div class="cmp">
126
+ <div class="cmp__pane cmp__bad">
127
+ <h3>❌ 常见写法:页码全铺开</h3>
128
+ <nav aria-label="分页示例" aria-hidden="true">
129
+ <ul class="pagination__list">
130
+ <li class="pagination__item"><a class="pagination__link" href="?page=1" aria-label="第 1 页" tabindex="-1">1</a></li>
131
+ <li class="pagination__item"><a class="pagination__link" href="?page=2" aria-label="第 2 页" tabindex="-1">2</a></li>
132
+ <li class="pagination__item"><a class="pagination__link" href="?page=3" aria-label="第 3 页" tabindex="-1">3</a></li>
133
+ <li class="pagination__item"><a class="pagination__link" href="?page=4" aria-label="第 4 页" tabindex="-1">4</a></li>
134
+ <li class="pagination__item"><a class="pagination__link" href="?page=5" aria-label="第 5 页" tabindex="-1">5</a></li>
135
+ <li class="pagination__item"><a class="pagination__link" href="?page=6" aria-label="第 6 页" tabindex="-1">6</a></li>
136
+ <li class="pagination__item"><a class="pagination__link" href="?page=7" aria-label="第 7 页" tabindex="-1">7</a></li>
137
+ <li class="pagination__item"><a class="pagination__link" href="?page=8" aria-label="第 8 页" tabindex="-1">8</a></li>
138
+ <li class="pagination__item"><a class="pagination__link" href="?page=9" aria-label="第 9 页" tabindex="-1">9</a></li>
139
+ <li class="pagination__item"><a class="pagination__link" href="?page=10" aria-label="第 10 页" tabindex="-1">10</a></li>
140
+ <li class="pagination__item"><a class="pagination__link" href="?page=11" aria-label="第 11 页" tabindex="-1">11</a></li>
141
+ <li class="pagination__item"><a class="pagination__link" href="?page=12" aria-label="第 12 页" tabindex="-1">12</a></li>
142
+ </ul>
143
+ </nav>
144
+ <p class="cmp__why">12 个页码硬塞进一行,窄屏上要么换行、要么每个都挤到点不中。而 4~10 页其实是读者最不会去点的区间。</p>
145
+ </div>
146
+ <div class="cmp__pane cmp__good">
147
+ <h3>✅ 本库:7 个槽位封顶 + 省略号</h3>
148
+ <nav aria-label="分页示例">
149
+ <ul class="pagination__list">
150
+ <li class="pagination__item"><a class="pagination__link" href="?page=1" aria-label="第 1 页">1</a></li>
151
+ <li class="pagination__item"><a class="pagination__link" href="?page=2" aria-label="第 2 页">2</a></li>
152
+ <li class="pagination__item"><a class="pagination__link" href="?page=3" aria-label="第 3 页">3</a></li>
153
+ <li class="pagination__item"><span class="pagination__ellipsis">…</span></li>
154
+ <li class="pagination__item"><a class="pagination__link" href="?page=11" aria-label="第 11 页">11</a></li>
155
+ <li class="pagination__item"><a class="pagination__link" href="?page=12" aria-label="第 12 页">12</a></li>
156
+ </ul>
157
+ </nav>
158
+ <p class="cmp__why">USWDS 的上限是 7 个槽位:首尾两页 + 当前页邻域,中间用 <code>pagination__ellipsis</code> 收掉。省略号不是装饰,是"这里还有页"的承诺 —— 所以没跳过页码时不许显示它。</p>
159
+ </div>
160
+ </div>
121
161
  </main>
122
162
 
123
163
  <script src="pagination.js"></script>
@@ -22,6 +22,12 @@
22
22
  border: 1px solid var(--border-subtle); font-size: var(--fs-sm); }
23
23
  .rv-note { font-size: var(--fs-sm); color: var(--text-secondary); }
24
24
  .rv-spacer { height: 40px; }
25
+ .rv-card { padding: var(--sp-3); background: var(--surface-raised);
26
+ border: 1px solid var(--border-decor); border-radius: var(--r-sm); min-height: 64px; }
27
+ .rv-card__t { margin: 0 0 var(--sp-1); font-size: var(--fs-sm); font-weight: 500; }
28
+ .rv-card__d { margin: 0; font-size: var(--fs-xs); color: var(--text-secondary); }
29
+ /* 反例专用:真的藏起来,用来演示"没有兜底"的后果 */
30
+ .rv-hidden { opacity: 0; }
25
31
  </style>
26
32
  </head>
27
33
  <body class="page">
@@ -81,6 +87,24 @@
81
87
  ② 手动调 <code>Reveal.enable()</code>(本页就是这种,方便截图与演示)。<br>
82
88
  都不写 ⇒ 完全不接管:内容可见、位置不偏。<strong>这是刻意的默认值。</strong>
83
89
  </p>
90
+
91
+
92
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
93
+ <div class="cmp">
94
+ <div class="cmp__pane cmp__bad">
95
+ <h3>❌ 常见写法:藏了但没兜底</h3>
96
+ <div class="rv-card rv-hidden" aria-hidden="true"></div>
97
+ <p class="cmp__why">内容真被 <code>opacity: 0</code> 藏着,而脚本没跑起来(被拦、报错、CSP)—— 这一块就永远是空的,读者不知道这里有东西。</p>
98
+ </div>
99
+ <div class="cmp__pane cmp__good">
100
+ <h3>✅ 本库:只有 <code>data-reveal-ready</code> 时才藏</h3>
101
+ <div class="rv-card" aria-hidden="true">
102
+ <p class="rv-card__t">2026 年三季度回顾</p>
103
+ <p class="rv-card__d">营收同比 +18%,毛利率 42.1%。</p>
104
+ </div>
105
+ <p class="cmp__why">隐藏只在 <code>&lt;html data-reveal-ready&gt;</code> 存在时生效:任何一步失败,内容就是全可见的。动画可以有代价,但代价不能是"看不见内容"。</p>
106
+ </div>
107
+ </div>
84
108
  </main>
85
109
 
86
110
  <script src="../../03-patterns/reveal/reveal.js"></script>
@@ -235,6 +235,30 @@ Array.prototype.forEach.call(document.querySelectorAll('.seg button'), function
235
235
  });
236
236
 
237
237
  show('content');
238
- </script></main>
238
+ </script>
239
+
240
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
241
+ <div class="cmp">
242
+ <div class="cmp__pane cmp__bad">
243
+ <h3>❌ 常见写法:empty 态没有出口</h3>
244
+ <div class="state state--empty" aria-hidden="true">
245
+ <div class="state__icon">□</div>
246
+ <p class="state__title">这个时间范围下还没有记录</p>
247
+ <p class="state__desc">把范围放宽到 2023 年试试。</p>
248
+ </div>
249
+ <p class="cmp__why">说明写得很清楚,然后呢?用户读完只剩一个动作:刷新,或者退出。一条"没有数据"的提示不构成一个状态,它只是死路。</p>
250
+ </div>
251
+ <div class="cmp__pane cmp__good">
252
+ <h3>✅ 本库:empty 态必须带动作</h3>
253
+ <div class="state state--empty" aria-hidden="true">
254
+ <div class="state__icon">□</div>
255
+ <p class="state__title">这个时间范围下还没有记录</p>
256
+ <p class="state__desc">把范围放宽到 2023 年试试。</p>
257
+ <div class="state__action"><button class="btn btn--sm btn--primary" type="button" tabindex="-1">放宽到 2023</button></div>
258
+ </div>
259
+ <p class="cmp__why"><code>state__action</code> 是 empty 态的一部分:要么给一条能走的路,要么别显示这个空状态。</p>
260
+ </div>
261
+ </div>
262
+ </main>
239
263
  </body>
240
264
  </html>
@@ -14,6 +14,11 @@
14
14
  <style>
15
15
  .row { display: flex; gap: var(--sp-4); flex-wrap: wrap; align-items: center; }
16
16
  .pad { padding: var(--sp-9) 0; }
17
+ .tt-field { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); }
18
+ .tt-label { font-weight: 500; }
19
+ .tt-icon { color: var(--text-tertiary); cursor: help; }
20
+ .tt-real { font-size: var(--fs-xs); color: var(--text-secondary); }
21
+ .tt-hint { margin: var(--sp-2) 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }
17
22
  </style>
18
23
  </head>
19
24
  <body class="page page">
@@ -57,6 +62,28 @@
57
62
  <button class="btn" id="good">导出 CSV</button>
58
63
  </div>
59
64
 
65
+
66
+
67
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
68
+ <div class="cmp">
69
+ <div class="cmp__pane cmp__bad">
70
+ <h3>❌ 常见写法:关键信息只放在气泡里</h3>
71
+ <div class="tt-field" aria-hidden="true">
72
+ <span class="tt-label">账期</span>
73
+ <span class="tt-icon" title="按合同约定的账期填写,自然日">ⓘ</span>
74
+ </div>
75
+ <p class="tt-hint">(把鼠标停在那颗 ⓘ 上才看得到说明)</p>
76
+ <p class="cmp__why">触屏没有 hover,键盘用户也未必会停留 —— 这条说明对一半以上的读者等于不存在。判断标准很硬:<b>气泡里的字不能是"必须知道才能填对"的信息</b>。</p>
77
+ </div>
78
+ <div class="cmp__pane cmp__good">
79
+ <h3>✅ 本库:该看见的写在页面上</h3>
80
+ <div class="tt-field" aria-hidden="true">
81
+ <span class="tt-label">账期</span>
82
+ <span class="tt-real">按合同约定的账期填写(自然日)</span>
83
+ </div>
84
+ <p class="cmp__why">tooltip 只用来<b>补一句</b>(快捷键、单位、来源),不用来补一个字段能不能填对。真的需要解释,就写在它旁边。</p>
85
+ </div>
86
+ </div>
60
87
  </main>
61
88
 
62
89
  <script src="tooltip.js"></script>
@@ -24,6 +24,10 @@
24
24
  color: var(--text-secondary); min-height: 3em;
25
25
  }
26
26
  .frame { padding: var(--sp-3); border: 1px solid var(--border-decor); border-radius: var(--r-md); }
27
+ .tr-demo { list-style: none; margin: 0; padding: 0; font-size: var(--fs-sm); }
28
+ .tr-demo ul { list-style: none; margin: 0; padding-inline-start: 1.4em; }
29
+ .tr-one { display: inline-block; width: 1em; font-size: .8em;
30
+ color: var(--text-tertiary); text-align: center; }
27
31
  </style>
28
32
  </head>
29
33
  <body class="page page">
@@ -52,6 +56,45 @@
52
56
  <div id="t2"></div>
53
57
  </div>
54
58
 
59
+
60
+
61
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
62
+ <div class="cmp">
63
+ <div class="cmp__pane cmp__bad">
64
+ <h3>❌ 常见写法:靠"旋转"表示状态</h3>
65
+ <ul class="tr-demo" role="tree" aria-label="文件树示例" aria-hidden="true">
66
+ <li class="tree__item" aria-expanded="true">
67
+ <div class="tree__node"><span class="tr-one">▸</span><span class="tree__label">src</span></div>
68
+ <ul role="group">
69
+ <li class="tree__item" aria-expanded="false">
70
+ <div class="tree__node"><span class="tr-one">▸</span><span class="tree__label">components</span></div>
71
+ </li>
72
+ <li class="tree__item">
73
+ <div class="tree__node"><span class="tr-one">▸</span><span class="tree__label">index.js</span></div>
74
+ </li>
75
+ </ul>
76
+ </li>
77
+ </ul>
78
+ <p class="cmp__why">三个节点长得一模一样:已展开的 src、收起的 components、叶子的 index.js 全是 ▸。旋转只能表达"变过",表达不了"现在是哪一态"。</p>
79
+ </div>
80
+ <div class="cmp__pane cmp__good">
81
+ <h3>✅ 本库:三态三种字形</h3>
82
+ <ul class="tr-demo" role="tree" aria-label="文件树示例" aria-hidden="true">
83
+ <li class="tree__item" aria-expanded="true">
84
+ <div class="tree__node"><span class="tree__marker"></span><span class="tree__label">src</span></div>
85
+ <ul role="group">
86
+ <li class="tree__item" aria-expanded="false">
87
+ <div class="tree__node"><span class="tree__marker"></span><span class="tree__label">components</span></div>
88
+ </li>
89
+ <li class="tree__item">
90
+ <div class="tree__node"><span class="tree__marker"></span><span class="tree__label">index.js</span></div>
91
+ </li>
92
+ </ul>
93
+ </li>
94
+ </ul>
95
+ <p class="cmp__why"><code>tree.css</code> 按 <code>aria-expanded</code> 给三种字形:▼ 展开 / ▶ 收起 / · 叶子。形状本身就是信息,不依赖动画。</p>
96
+ </div>
97
+ </div>
55
98
  </main>
56
99
 
57
100
  <script src="tree.js"></script>
@@ -18,6 +18,17 @@
18
18
  <link rel="stylesheet" href="../../09-assets/scientific-plot/scientific-plot.css">
19
19
 
20
20
  <link rel="stylesheet" href="analysis-report.css">
21
+ <style>
22
+ .ar-g { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
23
+ .ar-g--ok { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
24
+ .ar-cell { padding: var(--sp-2); background: var(--surface-raised);
25
+ border: 1px solid var(--border-decor); border-radius: var(--r-xs);
26
+ font-size: var(--fs-xs); }
27
+ /* 反例的关键:这里**不能**写 overflow:hidden —— 一写,min-content 就变 0,
28
+ 那个"列被长内容撑开"的毛病就被自己抹平了(第一版就是这么写坏的) */
29
+ .ar-long { font-family: var(--font-mono, monospace); white-space: nowrap; }
30
+ .ar-g--ok .ar-long { overflow: hidden; text-overflow: ellipsis; }
31
+ </style>
21
32
  </head>
22
33
  <body class="page">
23
34
  <main class="wrap report">
@@ -222,6 +233,29 @@
222
233
  </ol>
223
234
  </footer>
224
235
 
236
+
237
+
238
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
239
+ <!-- cmp--clip:这一块演示的就是"长串把列撑开",栏内必须允许算得比栏宽更宽;
240
+ 越出栏框的部分裁掉(不裁的话 320px 下整页横向溢出 64px,WCAG 1.4.10 红)。 -->
241
+ <div class="cmp cmp--clip">
242
+ <div class="cmp__pane cmp__bad">
243
+ <h3>❌ 常见写法:栅格列写 <code>1fr</code></h3>
244
+ <div class="ar-g" aria-hidden="true">
245
+ <div class="ar-cell">正文一栏:营收同比 +18%。</div>
246
+ <div class="ar-cell ar-long">SH600519-SH000001-SZ300750-SH601318-SZ000858</div>
247
+ </div>
248
+ <p class="cmp__why">右栏里一串不肯断的长代码把列撑开了:它不换行、也不省略,整页横向溢出 —— 而外层容器并<b>没有</b>写错,错的是列定义。</p>
249
+ </div>
250
+ <div class="cmp__pane cmp__good">
251
+ <h3>✅ 本库:<code>minmax(0, 1fr)</code></h3>
252
+ <div class="ar-g ar-g--ok" aria-hidden="true">
253
+ <div class="ar-cell">正文一栏:营收同比 +18%。</div>
254
+ <div class="ar-cell ar-long">SH600519-SH000001-SZ300750-SH601318-SZ000858</div>
255
+ </div>
256
+ <p class="cmp__why"><code>1fr</code> 的最小值是 <code>auto</code>(即"内容最小宽度"),所以挡不住长内容。<code>minmax(0, 1fr)</code> 把下限按到 0,列宽才真正受控;再配一句 <code>text-overflow: ellipsis</code>,内容就收在格子里。</p>
257
+ </div>
258
+ </div>
225
259
  </main>
226
260
 
227
261
  <script src="../../09-assets/scientific-plot/scientific-plot.js"></script>
@@ -46,6 +46,7 @@ python -m http.server 8000
46
46
 
47
47
  ## 反例
48
48
 
49
+ - ❌ **单位与数字同字号同色** —— "1,284.6 亿元" 里"亿元"只是量级标记,却和数字抢同一层注意力;本库把单位降一档(`--fs-sm` + 次级色),数字用 `num` 等宽(纵向才对得齐)
49
50
  - ❌ **把这四个当成"同一种图表的四种皮肤"** —— 它们共享令牌与容器,但**不共享**坐标规则与数据格式,
50
51
  是四个用途。换皮肤换不来正确的表达
51
52
  - ❌ **用 `table` 回答"涨还是跌"** —— 表格给的是具体值,趋势要看形状;
@@ -34,6 +34,14 @@
34
34
  屏幕阅读器会把它读成「图 1 的说明」,语义本身就是信息。 */
35
35
  .figbox figcaption { margin-top: var(--sp-2); font-size: var(--fs-xs);
36
36
  color: var(--text-tertiary); }
37
+ .ds-stat { display: flex; align-items: baseline; gap: var(--sp-2);
38
+ padding: var(--sp-3); background: var(--surface-raised);
39
+ border: 1px solid var(--border-decor); border-radius: var(--r-sm); }
40
+ .ds-k { font-size: var(--fs-xs); color: var(--text-secondary); margin-inline-end: auto; }
41
+ .ds-v { font-size: var(--fs-lg); font-weight: 600; }
42
+ /* 反例专用:单位跟数字同字号同色("抢同一层注意力"就是这个样子) */
43
+ .ds-v .ds-u--flat { font-size: inherit; color: inherit; } /* 特异性要比 .ds-u 高 */
44
+ .ds-u { font-size: var(--fs-sm); color: var(--text-secondary); }
37
45
  </style>
38
46
  </head>
39
47
  <body class="page">
@@ -215,6 +223,27 @@
215
223
  </table>
216
224
  </div>
217
225
 
226
+
227
+
228
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
229
+ <div class="cmp">
230
+ <div class="cmp__pane cmp__bad">
231
+ <h3>❌ 常见写法:单位与数字同字号同色</h3>
232
+ <div class="ds-stat" aria-hidden="true">
233
+ <span class="ds-k">营业收入</span>
234
+ <span class="ds-v"><span class="num">1,284.6</span><span class="ds-u ds-u--flat">亿元</span></span>
235
+ </div>
236
+ <p class="cmp__why">"亿元"只是个量级标记,却和 1,284.6 抢同一层注意力:读者先读到的是四个字,而不是那个数。</p>
237
+ </div>
238
+ <div class="cmp__pane cmp__good">
239
+ <h3>✅ 本库:数字当主角,单位退一档</h3>
240
+ <div class="ds-stat" aria-hidden="true">
241
+ <span class="ds-k">营业收入</span>
242
+ <span class="ds-v"><span class="num">1,284.6</span><span class="ds-u">亿元</span></span>
243
+ </div>
244
+ <p class="cmp__why">数字用 <code>num</code>(等宽数字,纵向才对得齐),单位降一档字号与颜色。一屏里有十几个数字时,这个差别决定读者能不能扫。</p>
245
+ </div>
246
+ </div>
218
247
  </main>
219
248
 
220
249
  <script src="../../09-assets/sparkline/sparkline.js"></script>
@@ -18,6 +18,13 @@
18
18
  .dp-link__t { font-weight: 600; }
19
19
  .dp-link__d { display: block; margin-top: 4px; font-size: var(--fs-sm);
20
20
  color: var(--text-secondary); }
21
+ .dp-kpi { display: flex; align-items: baseline; gap: var(--sp-2);
22
+ padding: var(--sp-3); background: var(--surface-raised);
23
+ border: 1px solid var(--border-decor); border-radius: var(--r-sm); }
24
+ .dp-kpi__k { font-size: var(--fs-xs); color: var(--text-secondary); }
25
+ .dp-kpi__v { font-size: var(--fs-lg); font-weight: 600; }
26
+ .dp-kpi__u { font-size: var(--fs-xs); color: var(--text-secondary); }
27
+ .detail-null__v { font-size: var(--fs-md); font-weight: 500; color: var(--text-tertiary); }
21
28
  </style>
22
29
  </head>
23
30
  <body class="page">
@@ -50,6 +57,27 @@
50
57
  ② 皮肤挂在 <code>section</code> 上,不挂 <code>body</code>;<br>
51
58
  ③ <strong>DOM 不许变</strong>,字段缺失是值缺失,不是节点缺失。
52
59
  </p>
60
+
61
+
62
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
63
+ <div class="cmp">
64
+ <div class="cmp__pane cmp__bad">
65
+ <h3>❌ 常见写法:缺的字段写 0</h3>
66
+ <div class="dp-kpi" aria-hidden="true">
67
+ <span class="dp-kpi__k">研发投入</span>
68
+ <span class="dp-kpi__v"><span class="num">0</span><span class="dp-kpi__u">亿元</span></span>
69
+ </div>
70
+ <p class="cmp__why"><code>0</code> 是一个真实的值:读者会拿它去算"研发强度 = 0",也会拿它去比同比 —— 而这一格其实<b>没有</b>数据。</p>
71
+ </div>
72
+ <div class="cmp__pane cmp__good">
73
+ <h3>✅ 本库:写「未披露」+ 弱化色</h3>
74
+ <div class="dp-kpi" aria-hidden="true">
75
+ <span class="dp-kpi__k">研发投入</span>
76
+ <span class="dp-kpi__v"><span class="detail-null__v">未披露</span></span>
77
+ </div>
78
+ <p class="cmp__why">缺就是缺。同一页里三个变体的 DOM 结构必须完全一致(节点数量与嵌套),所以缺数据时是<b>换文案</b>,不是删节点 —— 删了以后这页越改越不像。</p>
79
+ </div>
80
+ </div>
53
81
  </main>
54
82
  </body>
55
83
  </html>
@@ -13,6 +13,9 @@
13
13
  <link rel="stylesheet" href="report.css">
14
14
  <!-- 本页不写内联 <style>:所有版式都来自 report.css。
15
15
  内联写成活的那一刻,它就不再是这个系统的一部分了。 -->
16
+ <style>
17
+ .rep-p { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); line-height: 1.7; }
18
+ </style>
16
19
  </head>
17
20
  <body class="page">
18
21
  <main class="wrap">
@@ -299,6 +302,27 @@
299
302
  共享基础的六个可变量,以及与 <code>04-recipes/analysis-report</code>(一份具体实例)的分工。
300
303
  </p>
301
304
 
305
+
306
+
307
+ <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
308
+ <div class="cmp">
309
+ <div class="cmp__pane cmp__bad">
310
+ <h3>❌ 常见写法:来源与正文同一副面孔</h3>
311
+ <div class="rep" aria-hidden="true">
312
+ <p class="rep-p">本季度毛利率 42.1%,环比提升 1.3 个百分点。</p>
313
+ <p class="rep-p">数据来源:公司 2026 年三季报,经本库统一口径重算。</p>
314
+ </div>
315
+ <p class="cmp__why">读者读到第二句才知道这是"出处",而它和正文长得一模一样 —— 分不清哪句是结论、哪句是依据。</p>
316
+ </div>
317
+ <div class="cmp__pane cmp__good">
318
+ <h3>✅ 本库:来源用 <code>rep__cap</code> 这一档角色</h3>
319
+ <div class="rep" aria-hidden="true">
320
+ <p class="rep-p">本季度毛利率 42.1%,环比提升 1.3 个百分点。</p>
321
+ <p class="rep__cap">数据来源:公司 2026 年三季报,经本库统一口径重算。</p>
322
+ </div>
323
+ <p class="cmp__why">report 的六个字体角色(眉题 / 元信息 / 图表说明 / 来源 / 编者注 / 风险披露)是<b>不多不少</b>的。多造一种,读者就要重新学一遍"这行字是什么身份"。</p>
324
+ </div>
325
+ </div>
302
326
  </main>
303
327
 
304
328
  <script src="../../09-assets/scientific-plot/scientific-plot.js"></script>
@@ -102,6 +102,28 @@ python -m http.server 8000
102
102
  # http://127.0.0.1:8000/04-recipes/table/demo.html
103
103
  ```
104
104
 
105
+ ## 配色:与本库 `list` 的约定**不同**(已知,未统一)
106
+
107
+ | 组件 | 类名 | 约定 |
108
+ |---|---|---|
109
+ | `table` | `num--pos` / `num--neg` | **会计约定**:正 = 绿、负 = 红(负数即"红字") |
110
+ | `list` | `list__value--up` / `--down` | **市场约定**:涨 = 红、跌 = 绿(A 股惯例) |
111
+
112
+ 两种约定并存不是笔误,是因为两处的数是两种东西:
113
+
114
+ - 表格里多是**量**(营收、毛利率、净利润)—— 负数是"红字",会计惯例;
115
+ - 列表里多是**涨跌**(涨跌幅、日变动)—— A 股惯例是涨红跌绿。
116
+
117
+ ⚠️ **真正的麻烦在这里**:把「涨跌幅」这一列塞进 `table` 时,两种约定会打架 ——
118
+ 负的涨跌幅被渲染成红色,读者第一眼读成"涨"。目前**没有解**:
119
+
120
+ - 加一套 `num--up` / `num--down`(市场约定)是候选,但那会让 `table` 有四组颜色类;
121
+ - 或者由调用方显式指定语义(`data-polarity="market"`),但那要改契约。
122
+
123
+ 在决定之前,这一条就写在这儿 —— 一个库里有两种配色约定而不说明,
124
+ 比约定本身选错更糟。
125
+
126
+
105
127
  ## 反例
106
128
 
107
129
  - ❌ **靠数值大小猜语义** —— 不能用"小于 1 就乘 100"。`0.25` 是比例还是普通测量值,