agentcss 0.11.16 → 0.12.0

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.
@@ -0,0 +1,93 @@
1
+ # metric · 指标块
2
+
3
+ 报表里**“一个数”的最小完整单元**:一个标签、一个值、一句上下文。
4
+
5
+ ```html
6
+ <link rel="stylesheet" href="02-primitives/metric/metric.css">
7
+
8
+ <dl class="metric">
9
+ <dt class="metric__label">营业总收入</dt>
10
+ <dd class="metric__value">1,294.06<span class="metric__unit">亿元</span></dd>
11
+ <dd class="metric__foot">
12
+ <span class="delta delta--up">12.40%</span>
13
+ <span class="metric__note">同比</span>
14
+ </dd>
15
+ </dl>
16
+ ```
17
+
18
+ ## 类
19
+
20
+ | 类 | 作用 |
21
+ |---|---|
22
+ | `.metric` | 基础块(推荐 `<dl>`,用 `<div>` 也能渲染) |
23
+ | `.metric__label` | 标签:12px、次要色 |
24
+ | `.metric__value` | 值:等宽字族(`--mono`)+ 默认不换行(装不下才断) |
25
+ | `.metric__unit` | 单位:与数字同基线,小一号 |
26
+ | `.metric__foot` | 脚下那行:通常放 `delta` + 说明词 |
27
+ | `.metric__note` | 说明词(“同比” / “较上季”) |
28
+ | `.metric__spark` | 迷你走势槽位,放不放都行 |
29
+ | `.metric--lg` | 主指标档 |
30
+ | `.metric--sm` | 紧凑档(密集网格) |
31
+ | `.metric--null` | 缺数档:值降为 tertiary、去字重 |
32
+ | `.metric-grid` | KPI 行的自适应网格 |
33
+
34
+ ## 三条硬规矩
35
+
36
+ 1. **缺数写 `—`(U+2014)并说明原因**,不填 0、不留空、不只把字变淡。
37
+ 2. **单位放 `metric__unit`**,不要拼进值字符串。
38
+ 3. **变化量放 `metric__foot`**,不要混进 `metric__value`。
39
+
40
+ ## 反例
41
+
42
+ - ❌ **缺数填 0** —— `0.00` 与 `—` 在报表上是两个不同结论。填 0 会让读者把「未披露」读成「现金流为零」,而它其实可能只是没出报表
43
+ - ❌ **把单位拼进值字符串** —— `1,294.06亿元` 让单位跟着 30px 字号走、和数字一样重,读者第一眼会把“亿元”当成数值的一部分;而且没法单选单位调样式
44
+ - ❌ **把 `delta` 塞进 `metric__value`** —— 值里混进变化量之后等宽对齐失效(两段数字位数不同),读屏也会念成一串。变化量属于 `metric__foot`
45
+ - ❌ **用 `opacity` 表达缺数** —— `--text-tertiary` 的 5.33:1 已经是文字色地板,再乘一次透明度就跌破 4.5。缺数靠“不给数字”表达,不靠让它看不清
46
+ - ❌ **把 `metric` 当卡片用** —— 它没有边框、没有内边距、不承载操作区。要“一块带边框的内容”用 `card`;`metric` 只负责一个数
47
+
48
+ ## 为什么推荐 `<dl>`
49
+
50
+ “标签—值”在 HTML 里有现成语义:术语 + 描述。读屏会念
51
+ 「营业总收入 1,294.06 亿元 +12.40% 同比」;用 `<div>` 只会念一串没有关系的词。
52
+ CSS 只认类名,所以 `<div>` 也能渲染(兼容既有页面),但文档一律示范 `<dl>`。
53
+
54
+ ## 网格
55
+
56
+ ```html
57
+ <div class="metric-grid">
58
+ <dl class="metric"> … </dl>
59
+ <dl class="metric"> … </dl>
60
+ </div>
61
+ ```
62
+
63
+ 内部是 `repeat(auto-fit, minmax(min(100%, 12rem), 1fr))`。
64
+ 那层 `min()` 不是冗余:窄容器下 12rem 会比可用宽度还大,没有它就会横向溢出
65
+ (`05-audit/metric-check.js` 判据 ⑧ 在 375px 视口下直接量 `scrollWidth`)。
66
+
67
+ ## 缺数是一等状态
68
+
69
+ ```html
70
+ <dl class="metric metric--null">
71
+ <dt class="metric__label">经营活动现金流净额</dt>
72
+ <dd class="metric__value">—</dd>
73
+ <dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
74
+ </dl>
75
+ ```
76
+
77
+ `metric--null` 只把值的颜色降到 tertiary、去掉字重,**不隐藏这一块** ——
78
+ 版面塌掉比显示“没有”更糟。这是本库从 `04-recipes/detail-page` 的 null 变体
79
+ 继承下来的同一条纪律。
80
+
81
+ ## 对本组件不做的事
82
+
83
+ - **不做数值格式化**。千分位、小数位、单位换算由数据侧决定;CSS 不知道
84
+ `1,294.06` 与 `1294.06` 哪个对。库保证的是等宽对齐与不换行。
85
+ - **不判断方向**。涨跌颜色交给 `delta`,本组件不含任何方向令牌引用
86
+ (`metric-check` 判据 ⑦ 静态检查这件事)。
87
+
88
+ ## 预览
89
+
90
+ ```bash
91
+ python -m http.server 8000
92
+ # http://127.0.0.1:8000/02-primitives/metric/demo.html
93
+ ```
@@ -0,0 +1,176 @@
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">
6
+ <link rel="icon" href="data:,">
7
+ <title>metric · 指标块</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/page.css">
11
+ <link rel="stylesheet" href="../../02-primitives/metric/metric.css">
12
+ <link rel="stylesheet" href="../../02-primitives/delta/delta.css">
13
+ <link rel="stylesheet" href="../../09-assets/sparkline/sparkline.css">
14
+ <style>
15
+ .pad { padding: var(--sp-4) 0; }
16
+ .frame { border: 1px solid var(--border-decor); border-radius: var(--r-md);
17
+ padding: var(--sp-5); background: var(--surface); }
18
+ </style>
19
+ </head>
20
+ <body class="page page">
21
+ <main class="wrap">
22
+ <h1>metric · 指标块</h1>
23
+ <p class="lede">
24
+ 报表里<strong>“一个数”的最小完整单元</strong>:一个标签、一个值、一句上下文。
25
+ KPI 行、详情页顶部、监控面板,都由它排出来。
26
+ </p>
27
+
28
+ <h2 class="rule-top">一个完整的 metric</h2>
29
+ <div class="frame">
30
+ <dl class="metric" style="max-width: 18rem">
31
+ <dt class="metric__label">营业总收入</dt>
32
+ <dd class="metric__value">1,294.06<span class="metric__unit">亿元</span></dd>
33
+ <dd class="metric__foot">
34
+ <span class="delta delta--up">12.40%</span>
35
+ <span class="metric__note">同比</span>
36
+ </dd>
37
+ </dl>
38
+ </div>
39
+ <p class="small">
40
+ 四件必须一起做对的事:<strong>数字等宽</strong>(上下两行数位对齐,竖着扫能比大小)、
41
+ <strong>单位分离成槽位</strong>(拼进字符串就没法单独调样式,也没法让数字右对齐时把单位甩在外面)、
42
+ <strong>缺数是一等状态</strong>(见下)、<strong>方向交给 delta</strong>(本组件不管颜色)。
43
+ <br>
44
+ 结构用 <code>&lt;dl&gt;</code> 是因为“标签—值”在 HTML 里有现成语义:
45
+ 读屏会念「营业总收入 1,294.06 亿元 +12.40% 同比」。用 <code>&lt;div&gt;</code>
46
+ 只会念一串没有关系的词(CSS 只认类名,用 div 也能渲染,但不推荐)。
47
+ </p>
48
+
49
+ <h2 class="rule-top">一行四个 KPI</h2>
50
+ <div class="metric-grid">
51
+ <dl class="metric">
52
+ <dt class="metric__label">营业总收入</dt>
53
+ <dd class="metric__value">1,294.06<span class="metric__unit">亿元</span></dd>
54
+ <dd class="metric__foot"><span class="delta delta--up">12.40%</span><span class="metric__note">同比</span></dd>
55
+ </dl>
56
+ <dl class="metric">
57
+ <dt class="metric__label">归母净利润</dt>
58
+ <dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
59
+ <dd class="metric__foot"><span class="delta delta--up">8.60%</span><span class="metric__note">同比</span></dd>
60
+ </dl>
61
+ <dl class="metric">
62
+ <dt class="metric__label">销售毛利率</dt>
63
+ <dd class="metric__value">41.28<span class="metric__unit">%</span></dd>
64
+ <dd class="metric__foot"><span class="delta delta--down">1.34%</span><span class="metric__note">同比</span></dd>
65
+ </dl>
66
+ <dl class="metric metric--null">
67
+ <dt class="metric__label">经营活动现金流净额</dt>
68
+ <dd class="metric__value">—</dd>
69
+ <dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
70
+ </dl>
71
+ </div>
72
+ <p class="small">
73
+ 第四块是<strong>缺数</strong>:值写 <code>—</code>(U+2014),并在脚下说明原因。
74
+ 它<strong>不隐藏、不变灰到看不见、也不填 0</strong> —— 版面塌掉比显示“没有”更糟,
75
+ 而填 0 是伪造数据:读者会把“未披露”读成“现金流为零”。
76
+ <br>
77
+ 网格用 <code>metric-grid</code>,它内部是
78
+ <code>minmax(min(100%, 12rem), 1fr)</code> —— 那层 <code>min()</code> 不是冗余:
79
+ 窄容器下 12rem 会比可用宽度还大,没有它就会横向溢出。
80
+ </p>
81
+
82
+ <h2 class="rule-top">三档字号</h2>
83
+ <div class="frame">
84
+ <div class="metric-grid">
85
+ <dl class="metric metric--lg">
86
+ <dt class="metric__label">主指标(lg)</dt>
87
+ <dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
88
+ </dl>
89
+ <dl class="metric">
90
+ <dt class="metric__label">默认</dt>
91
+ <dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
92
+ </dl>
93
+ <dl class="metric metric--sm">
94
+ <dt class="metric__label">紧凑档(sm)</dt>
95
+ <dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
96
+ </dl>
97
+ </div>
98
+ </div>
99
+ <p class="small">
100
+ 三档够了。出现第四档说明 KPI 行的层级没规划好(与 <code>badge</code> / <code>card</code> 同一判断标准)。
101
+ </p>
102
+
103
+ <h2 class="rule-top">迷你走势槽位</h2>
104
+ <div class="frame">
105
+ <div class="metric-grid">
106
+ <dl class="metric">
107
+ <dt class="metric__label">近九个交易日收盘</dt>
108
+ <dd class="metric__value">1,684.00</dd>
109
+ <dd class="metric__foot"><span class="delta delta--up">5.12%</span><span class="metric__note">五日</span></dd>
110
+ <dd class="metric__spark">
111
+ <span class="sparkline" data-spark="1602,1618,1609,1631,1625,1648,1662,1655,1684"></span>
112
+ </dd>
113
+ </dl>
114
+ <dl class="metric">
115
+ <dt class="metric__label">近九个交易日成交量</dt>
116
+ <dd class="metric__value">3.42<span class="metric__unit">亿手</span></dd>
117
+ <dd class="metric__foot"><span class="delta delta--down">2.08%</span><span class="metric__note">五日</span></dd>
118
+ <dd class="metric__spark">
119
+ <span class="sparkline" data-spark="3.9,3.7,3.8,3.6,3.5,3.55,3.48,3.5,3.42"></span>
120
+ </dd>
121
+ </dl>
122
+ </div>
123
+ </div>
124
+ <p class="small">
125
+ <code>metric__spark</code> 只是一个<strong>槽位</strong> —— 它不关心里面放什么。
126
+ 上面放的是 <code>sparkline</code>,也可以什么都不放(前三块就没有)。
127
+ 槽位存在但为空时不留空白高度,所以同一个网格里有没有走势图都能对齐。
128
+ </p>
129
+
130
+ <h2 class="rule-top">反例:缺数填 0</h2>
131
+ <div class="cmp">
132
+ <div class="cmp__pane cmp__bad" data-bad=".metric__value|glyphs|=4">
133
+ <h3>错:拿 0 冒充“没有”</h3>
134
+ <dl class="metric">
135
+ <dt class="metric__label">经营活动现金流净额</dt>
136
+ <dd class="metric__value">0.00</dd>
137
+ <dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
138
+ </dl>
139
+ <p class="small">
140
+ 值有 4 个字形。读者读到的是“现金流为零”—— 一个具体的、错误的结论。
141
+ </p>
142
+ </div>
143
+ <div class="cmp__pane cmp__good" data-good=".metric__value|glyphs|=1">
144
+ <h3>对:留 <code>—</code> 并说明原因</h3>
145
+ <dl class="metric metric--null">
146
+ <dt class="metric__label">经营活动现金流净额</dt>
147
+ <dd class="metric__value">—</dd>
148
+ <dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
149
+ </dl>
150
+ <p class="small">
151
+ 值只有 1 个字形(U+2014),且 <code>metric--null</code> 把颜色降到 tertiary。
152
+ </p>
153
+ </div>
154
+ </div>
155
+ <p class="small">
156
+ <strong>为什么不能靠“变淡”表达缺数</strong>:颜色淡了但字还在,读者仍会读那个数字。
157
+ 缺数的正确表达是<strong>不给出数字</strong>,再加一句为什么。
158
+ <br>
159
+ ⚠️ 也不要用 <code>opacity</code> 压淡 —— <code>--text-tertiary</code> 的 5.33:1
160
+ 已经是地板,再乘一次透明度就跌破 4.5。改令牌才是对的做法。
161
+ </p>
162
+
163
+ <h2 class="rule-top">两条边界</h2>
164
+ <p class="small">
165
+ ① <strong>本组件不做格式化</strong>。千分位、小数位、单位换算都由数据侧决定 ——
166
+ CSS 不知道“1,294.06”和“1294.06”哪个对。库能保证的是
167
+ <strong>等宽对齐</strong>与<strong>不换行</strong>。
168
+ <br>
169
+ ② <strong>不要把 delta 写进 <code>metric__value</code></strong>。值里混进变化量之后,
170
+ 等宽对齐就失效了(数字位数不同),而且读屏会念成一串。
171
+ 变化量放 <code>metric__foot</code>。
172
+ </p>
173
+ </main>
174
+ <script src="../../09-assets/sparkline/sparkline.js"></script>
175
+ </body>
176
+ </html>
@@ -0,0 +1,157 @@
1
+ /* ============================================================================
2
+ Metric — 指标块(一个标签 + 一个值 + 一句上下文)
3
+ ============================================================================
4
+ 一句话:**报表里“一个数”的最小完整单元**。
5
+ 财务页、监控页、详情页顶部的 KPI 行,都是由它排出来的。
6
+
7
+ 🔴 为什么它是组件,而不是几个工具类
8
+ ----------------------------------------------------------------------------
9
+ 因为“一个数”有四件事必须一起做对,少一件就会在真实报表里出错:
10
+
11
+ ① 数字要**等宽**(用等宽字族,不靠比例字体的默认)—— 否则上下两行的数位对不齐,
12
+ 竖着扫一眼比大小这件事就废了;这是「财务表格」与「装饰表格」的分界
13
+ ② 单位要**分离**成槽位 —— 拼进字符串后("1,294.06亿元")既不能单独调样式,
14
+ 也没法让数字右对齐时把单位甩在外面
15
+ ③ **缺数是一等状态** —— 不能渲染成 0、不能留空、不能只把字变淡;
16
+ 必须给出“为什么没有”(本库的既有纪律,见 04-recipes/detail-page 的 null 变体)
17
+ ④ 变化的**方向**要能表达 —— 所以它给 delta 留了位置,但它自己不管颜色
18
+
19
+ 这四件事里任意一件交给调用方“记得做”,就会漏。本库在同一条路上栽过:
20
+ `table__unit` 的括号是组件加的,不是要求每个调用方记得加。
21
+
22
+ 🔴 为什么推荐 `<dl>`
23
+ ----------------------------------------------------------------------------
24
+ “标签—值”在 HTML 里有现成语义:术语 + 描述。用 `<dl>` 读屏会念
25
+ 「营业总收入 1,294.06 亿元 +12.4% 同比」,用 `<div>` 只会念一串没有关系的词。
26
+
27
+ 本文件的 CSS **只认类名,不认标签** —— 用 `<div>` 也能正常渲染
28
+ (兼容既有页面),但 demo 与文档一律示范 `<dl>`。
29
+
30
+ 🔴 缺数怎么写
31
+ ----------------------------------------------------------------------------
32
+ <dl class="metric metric--null">
33
+ <dt class="metric__label">经营现金流</dt>
34
+ <dd class="metric__value">—</dd> <!-- U+2014,不是 0、不是空 -->
35
+ <dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
36
+ </dl>
37
+
38
+ `metric--null` 只做一件事:把值的颜色降到 tertiary、去掉字重。
39
+ 它**不隐藏**这块 —— 版面塌掉比显示“没有”更糟。
40
+ ========================================================================= */
41
+
42
+ .metric {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: var(--sp-1);
46
+ margin: 0; /* dl 自带外边距,会破坏网格对齐 */
47
+ min-width: 0; /* flex/grid 子项默认不缩,长标签会撑破列 */
48
+ }
49
+
50
+ /* dl 的 dd 默认有 40px 起始缩进 —— 只在结构与类名同时出现时清掉 */
51
+ .metric > dd {
52
+ margin-inline-start: 0;
53
+ }
54
+
55
+ .metric__label {
56
+ margin: 0;
57
+ font-size: var(--fs-xs);
58
+ font-weight: 500;
59
+ line-height: 1.5;
60
+ color: var(--text-secondary);
61
+ letter-spacing: var(--tracking-normal); /* 小字号中文给一点字距更清;令牌在暗色段有另一档 */
62
+ }
63
+
64
+ /* 值:这一块的全部意义所在 */
65
+ .metric__value {
66
+ margin: 0;
67
+ font-family: var(--mono); /* 等宽字族 ⇒ 数字天然对齐,不需要 tabular-nums */
68
+ font-size: var(--fs-xl);
69
+ font-weight: 600;
70
+ line-height: var(--lh-tight);
71
+ color: var(--text-primary);
72
+ /* 🔴 数字**默认不换行**,但极端情况下必须能断,否则顶破视口。
73
+ 第一版写的是 `white-space: nowrap`,RTL 下阿语膨胀 1.3× 后
74
+ 整个值变成一串装不下的长词,横向溢出 212px(实测)。
75
+ `overflow-wrap: anywhere` 的语义正好是「只有当一个词装不下整行时才断」——
76
+ 正常列宽下 `1,294.06` 绝不会被拆开,只有真的放不下才断。 */
77
+ white-space: normal;
78
+ overflow-wrap: anywhere;
79
+ /* 值 + 单位按 flex 排,**允许整体换行**(单位可以整块掉到下一行)。 */
80
+ display: flex;
81
+ flex-wrap: wrap;
82
+ align-items: baseline;
83
+ }
84
+
85
+ /* 单位:与数字同基线、小一号、次要色
86
+ ⚠️ 用 margin-inline-start 而不是空格字符:
87
+ 空格在等宽字族里也占一格,而 4 位数 × 十几行就会看出错位。 */
88
+ .metric__unit {
89
+ margin-inline-start: 0.3em;
90
+ font-size: var(--fs-sm);
91
+ font-weight: 400;
92
+ color: var(--text-secondary);
93
+ /* 🔴 单位**允许换行**。第一版给了 `nowrap`,结果 RTL 下阿语膨胀 1.3×
94
+ 直接顶破列宽(实测溢出 379px):`nowrap` 会把单位变成列的**最小内容宽度**,
95
+ 网格再想收也收不了。
96
+ `overflow-wrap: anywhere` 是给「一个词长过整列」的兜底 ——
97
+ 阿/希的单位词本来就比中文长,不加它还是会溢出。 */
98
+ white-space: normal;
99
+ overflow-wrap: anywhere;
100
+ min-width: 0; /* flex 子项默认不缩 —— 不写这句上面的换行等于没写 */
101
+ }
102
+
103
+ /* 脚下那行:通常放一个 delta + 一个说明词("同比" / "较上季")*/
104
+ .metric__foot {
105
+ margin: 0;
106
+ display: flex;
107
+ align-items: baseline;
108
+ flex-wrap: wrap;
109
+ gap: var(--sp-2);
110
+ font-size: var(--fs-xs);
111
+ line-height: 1.5;
112
+ color: var(--text-tertiary);
113
+ }
114
+
115
+ .metric__note {
116
+ color: var(--text-tertiary);
117
+ }
118
+
119
+ /* 迷你图槽位:放 sparkline 之类。给它一点上间距,避免贴着数字 */
120
+ .metric__spark {
121
+ margin-block-start: var(--sp-1);
122
+ color: var(--text-tertiary);
123
+ }
124
+
125
+ /* 缺数:值降到 tertiary、去字重,但**仍然占位**
126
+ ⚠️ 不要用 opacity —— 它会让对比度跌破 4.5(--text-tertiary 已经是 5.33 的地板,
127
+ 再乘一次透明度就没了)。改令牌才是对的做法。 */
128
+ .metric--null .metric__value {
129
+ color: var(--text-tertiary);
130
+ font-weight: 400;
131
+ }
132
+
133
+ /* ---------------------------------------------------------------- 字号三档
134
+ 三档够了。出现第四档说明 KPI 行的层级没规划好
135
+ (与 badge / card 的判断标准一致)。 */
136
+ .metric--lg .metric__value {
137
+ font-size: var(--fs-2xl);
138
+ }
139
+
140
+ .metric--sm .metric__value {
141
+ font-size: var(--fs-md);
142
+ font-weight: 500;
143
+ }
144
+
145
+ /* ---------------------------------------------------------------- 网格容器
146
+ KPI 行几乎总是成排出现,把重复的栅格收进来一次。
147
+ ⚠️ `minmax(min(100%, 12rem), 1fr)` 里的 `min(100%, …)` 不是冗余:
148
+ 窄容器(手机单列、侧栏)下 12rem 会比可用宽度还大,
149
+ 没有这层 min() 就会横向溢出 —— 这是 auto-fit 最常见的翻车方式。 */
150
+ .metric-grid {
151
+ display: grid;
152
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
153
+ gap: var(--sp-5) var(--sp-5);
154
+ margin: 0;
155
+ padding: 0;
156
+ list-style: none; /* 用 <ul> 包 metric 时不想看到圆点 */
157
+ }
@@ -1,15 +1,16 @@
1
1
  /* ============================================================================
2
- 精简档 专用:内联用令牌(某项目 / 单文件)
2
+ 精简档:内联用令牌(单文件 / 无构建项目)
3
3
  ============================================================================
4
4
  本文件由 `python 05-audit/make-tiersnippet.py` 从 01-tokens/tokens.css
5
5
  **自动抽取**,请不要手改 —— 改源头,然后重新生成。
6
6
 
7
- 🔴 它为什么存在:
8
- "精简档只需复制 tokens.css 的 [核心] 段",
9
- 但**那个区块从来不存在**。复用的人照着做只会找不到。
10
- 这个文件就是那个"核心",真的、能复制、能用。
7
+ 用法:整段复制进 `<style>`,零外链、零构建。
11
8
 
12
- 体积:浅色 102 个令牌 + 暗色 33 个,零外链零构建。
9
+ 它只含**中间层语义令牌**(L1 原语已内联成具体值),所以体积小、能直接改;
10
+ 但它**不含** `market.css` 的涨跌口径切换 —— 需要国际口径时见
11
+ `01-tokens/README.md` 的「涨跌口径」一节。
12
+
13
+ 体积:浅色 104 个令牌 + 暗色 35 个,零外链零构建。
13
14
  --------------------------------------------------------------------- */
14
15
 
15
16
  :root {
@@ -68,6 +69,9 @@
68
69
  --warning-bg: #fbf1dd;
69
70
  --info-bg: #e8f1f8;
70
71
 
72
+ --dir-up: #c0392b;
73
+ --dir-down: #2f6b3a;
74
+
71
75
  --accent: #1b4d8f;
72
76
  --switch-on: #1b4d8f;
73
77
  --accent-hover: #163a6d;
@@ -169,6 +173,8 @@
169
173
  --danger-bg: #341D1A;
170
174
  --success: #6FAA7A;
171
175
  --success-bg: #16241A;
176
+ --dir-up: #E07A6E;
177
+ --dir-down: #6FAA7A;
172
178
  --warning: #C99A3F;
173
179
 
174
180
  --warning-bg: #242A31;
package/API.md CHANGED
@@ -329,9 +329,24 @@ python3 05-audit/prefix-build.py --prefix fl
329
329
  | `--border-decor` | ✅ | 分隔线 |
330
330
  | `--shadow-1` / `--shadow-2` | ✅ | 阴影 |
331
331
  | `--dur` / `--ease-out` | ✅ | 动效 |
332
+ | `--dir-up` / `--dir-down` | ✅ | 涨跌方向色(**方向 ≠ 好恶**:默认红涨绿跌,`[data-market="intl"]` 整体对调) |
332
333
 
333
334
  新增令牌算**小版本变更**;移除或改默认值算**破坏性变更**。
334
335
 
336
+ ### 口径切换:`[data-market]`
337
+
338
+ 涨跌配色是**全局约定**,不是组件参数:
339
+
340
+ | 写法 | 效果 |
341
+ |---|---|
342
+ | 不写 | A 股口径:涨 = 红、跌 = 绿 |
343
+ | `<html data-market="intl">` | 国际口径:涨 = 绿、跌 = 红 |
344
+ | `<section data-market="intl">` | 只切这一块 |
345
+
346
+ 实现方式是**换令牌的指向**(`--dir-up` 永远表示"涨"),组件 CSS 里没有任何分支
347
+ —— 与暗色同一手法。`05-audit/delta-check.js` 在浏览器里读 computed color,
348
+ 四种组合(两种口径 × 明暗)各判一遍,并断言 `intl` 是**完整互换**而不是只改一边。
349
+
335
350
  ---
336
351
 
337
352
  ## 无障碍保证
package/CHANGELOG.md CHANGED
@@ -16,6 +16,141 @@
16
16
 
17
17
  (无)
18
18
 
19
+ ## [0.12.0] - 2026-10-29
20
+
21
+ **新增两个组件:`delta`(变化量)与 `metric`(指标块)。这是「数值展示层」的第一批。**
22
+
23
+ 起因是一条量出来的空白:拿「做一张 A 股财报分析页」当尺子逐段量现有 35 个组件,
24
+ **KPI 数字行那一整段是空的** —— 全库没有 `metric` / `statistic` / `kpi`,
25
+ `.stat__*` 只是 `table--stats` 预设下的统计单元格(标准误、p 值)。
26
+
27
+ 更具体的一处:`table` 的 `num--pos` / `num--neg` 是**会计约定**(正 = 绿、负数即红字),
28
+ 而 A 股涨跌幅是**市场约定**(涨 = 红)。两者塞进同一列会打架 ——
29
+ 这条此前登记在 `04-recipes/table/README.md` 的「已知未统一」里,列了两个候选解法
30
+ (给 `table` 加 `num--up/down`,或加 `data-polarity`),**都说明没解**。
31
+
32
+ 这一版走第三条路:涨跌不该由 `table` 的颜色类承担,而该由一个**独立原子**承担。
33
+ `delta` 自带市场语义、自带符号,放进任何单元格都不会被 `num--pos/neg` 命中;
34
+ `table` 一行不用改,会计约定保持不动。
35
+
36
+ ### ① 方向色独立成令牌:`--dir-up` / `--dir-down`
37
+
38
+ 不复用 `--danger` / `--success`。这两组读起来都是红绿,但它们是两件事:
39
+
40
+ | 令牌 | 语义 | 例子 |
41
+ |---|---|---|
42
+ | `--danger` / `--success` | **评价**(错了 / 成了) | 已停牌、审计失败 |
43
+ | `--dir-up` / `--dir-down` | **事实**(涨了 / 跌了) | 同比 +12.4% |
44
+
45
+ 一句就够说明为什么不能共用:「研发费用同比 +38%」是**涨**,但那是成本失控。
46
+ 用 `--danger` 渲染它,颜色说着“错了”,语义说着“涨了”,读者收到矛盾信号。
47
+
48
+ 当前取值与 `--danger`/`--success` 相同(这是巧,不是同一件事),但允许将来单独调
49
+ —— 金融终端普遍用比 `--danger` 更饱和的红。
50
+
51
+ 实测对比度(`contrast.py`,四组底各算一遍):
52
+ 浅色红 4.89–5.44 / 绿 5.75–5.96;暗色红 5.60–6.15 / 绿 6.01–6.60,全部 AA。
53
+
54
+ ### ② 口径可整体切换:`[data-market="intl"]`
55
+
56
+ 默认 A 股(红涨绿跌)。给任意祖先加 `data-market="intl"` 切成国际口径。
57
+ 实现与暗色同构:`--dir-up` 永远表示“涨”,切换只改**令牌的指向**,
58
+ 组件 CSS 里没有任何分支 —— 两栏 HTML 逐字相同,只差那个属性。
59
+
60
+ 交换规则放在**独立可选文件** `01-tokens/market.css`:引它才有国际口径,
61
+ 不引就永远是 A 股口径、`data-market` 被完全忽略。核心令牌不放条件规则
62
+ (原因见下面 E137 —— 那会让五六个工具静默读错)。
63
+
64
+ ### ③ `metric`:缺数是一等状态
65
+
66
+ `metric--null` + 值写 `—`(U+2014)+ 脚下说明原因。三条禁止都进了判据:
67
+ 不填 0(`0.00` 是“现金流为零”这个具体结论)、不留空、不用 `opacity` 压淡
68
+ (`--text-tertiary` 的 5.33:1 已是地板)。
69
+
70
+ 单位独立成槽位(`metric__unit`),不拼进值字符串 —— 与 `table__unit` 同一理由。
71
+
72
+ ### 过程中踩到并修掉的五个真缺陷
73
+
74
+ - **E134**:我在 `:root { }` 里写了一个 `[data-market]` 规则块。`theme-sync.py`
75
+ 按「声明序列」解析这个块,遇到嵌套规则把选择器切碎,生成了一段
76
+ `where([data-market="intl"]) { … }` —— 无效 CSS,且第二条声明丢失。
77
+ CSS 的 `:root` 只能装声明。(`theme-sync` 本身没坏,是我喂了它不认识的形状。)
78
+ - **E135**:口径交换写成了 `:where([data-market="intl"])`,想把特异性压到 0。
79
+ **实测完全不生效** —— `:where()` 是 0,`:root` 是 (0,1,0),两者都匹配 `<html>`,
80
+ `:root` 赢。压得越低越抢不过。改成 `:root[data-market="intl"]`,并**挪到两个主题
81
+ 属性段之后**(它们由 `theme-sync` 生成,会连 L2 一起重声明,同权重下靠顺序定胜负)。
82
+ - **E136**:`delta-check` 的「红涨」判据第一版写的是 `up === --danger`。
83
+ 这是**假判据**:把整个色板对调(danger 改成绿)之后它照样绿,因为两边一起变了。
84
+ 「红涨」说的是颜色本身,只能判**色相**(红区 ≤20° 或 ≥345°,绿区 90–165°)。
85
+ - **E137**:口径交换规则最初写在 `tokens.css` 末尾,直接污染了工具链。
86
+ `05-audit/_css.py` 的 `declared_tokens()` 是**纯词法**的 —— 遍历所有 `--x: y;`,
87
+ 后者覆盖前者,**不看选择器**。于是它把「条件性交换」当成默认值叠上去:
88
+ `contrast.py` 报告里「暗色 涨」显示成绿色,`token-tree-gate` 报「浏览器解析到了
89
+ `--dir-down`,导出里没有」,`gen-ai-contract --check` 报看似随机的错位 ——
90
+ 而浏览器里的值一直是对的。**门禁冤枉代码比漏判更难查。**
91
+ 修法:交换规则移到独立可选文件 `01-tokens/market.css`,`tokens.css` 只留说明注释。
92
+ - **E138**:E137 的修法又带出一个新缺陷 —— 把规则挪到 `market.css` 之后,
93
+ `delta` 的 demo **没跟着引新文件**,那一栏「国际口径」静默显示成 A 股配色:
94
+ 页面在说谎,而所有门禁都是绿的(CSS 不报错,`data-market` 属性也照样存在,
95
+ 只是没东西接它)。修法:demo 补引用,并给 `delta-check` 加判据 ⑧ ——
96
+ 用了 `data-market` 的页面必须引 `market.css` 且排在 `tokens.css` 之后,
97
+ 92 个页面全扫描(破坏实验确认删掉引用会红)。
98
+
99
+ ### 全量门禁又抓出六个(E139–E144)
100
+
101
+ 本地单项验证绿了不等于全量绿 —— 这一轮全量门禁(208 项)第二次跑才发现:
102
+
103
+ - **E139**:`tierA-tokens.css`(精简档)漂了两轮没人管。文件本身有 `--check`,
104
+ 但它**连 `--dir-*` 都没有**(用户写 `var(--dir-up)` 会拿到未定义令牌),
105
+ 文档里「浅色 102 + 暗色 33」也停在旧值。⇒ 重抽文件 + `doc-facts-gate` 加 F6,
106
+ 把「浅色 N + 暗色 M」变成可核对的判据。
107
+ - **E140**:两个新门禁起名 `dir-gate` / `metric-gate`,而成熟度探测器要求
108
+ 文件名含 `check`/`contract`/`geometry` ⇒ 「有没有专属门禁」判 False,
109
+ 两个组件被判成 `alpha`。⇒ 改名 `delta-check.js` / `metric-check.js`,
110
+ 重新生成契约(**stable 33 / beta 4**)。
111
+ - **E141**:脚本删块加注释时漏了 `*/`,`tokens.css` 后半段(含字体栈)
112
+ 被整段注释吞掉;而**视觉基线是在那之后录的**。⇒ 补闭合、先跑语法级判据再录基线。
113
+ - **E142**:库里早有 `.num` / `.num-lg`(`typography.css`),我又在两个组件里
114
+ 各写了一遍 `font-variant-numeric`。⇒ 不是加豁免,而是**删掉**:
115
+ 两个原子都用 `--mono`,等宽字族天然对齐,那条声明是无效动作。
116
+ - **E143**:`metric` 的值写了 `white-space: nowrap`,单位是它的子元素 ⇒
117
+ RTL 下阿语膨胀后顶破视口 212px。⇒ 值改 flex + `overflow-wrap: anywhere`,
118
+ 单位 `min-width: 0`。
119
+ - **E144**:精简档生成器的头注释里写着内部协作用语,
120
+ 随包发布 ⇒ `leak-scan` 报「过程痕迹」。⇒ 改生成器文案(产物注释只写给使用者看)。
121
+
122
+
123
+
124
+ `delta` 的 `+` / `−` 由 CSS `::before` 生成,所以它们**不在** `textContent` 里。
125
+ 实测(Chromium + CDP 可访问性树):元素的可访问名字只有 `3.21%`,**不含符号**;
126
+ 但符号作为**独立的 StaticText 节点**存在,读屏会按顺序念「加」「3.21%」。
127
+
128
+ 也就是说「符号对读屏可用」是成立的 —— 但成立在一个很容易被改坏的机制上:
129
+ 换成空串、换成背景图、或让伪元素 `visibility: hidden`,视觉上可能仍有符号,
130
+ 可访问性树里就没了,而颜色对读屏毫无意义。
131
+
132
+ ⇒ `delta-check` 加了判据 ⑦:直接数十字树里的 `+` / `−` 节点。
133
+ 破坏实验(给 `::before` 加 `visibility: hidden`)确认它会红。
134
+
135
+ ### 门禁 204 → 208
136
+
137
+ 新增 `dir-color` / `dir-color-rev` / `metric` / `metric-rev`。
138
+ 两道主门禁都要起浏览器,自检不需要(纯判据)。
139
+ 三个新组件页全部纳入视觉基线(明/暗/RTL 各三张)与无障碍树快照(38 → 40 页)。
140
+
141
+ CSS 体积预算 125 → **132 KB**(剥注释后口径):两个新组件 + `market.css` 的
142
+ 真实规则合计 +2.1 KB(127.1 KB 实测)。**这是有意增长,不是放宽**——
143
+ 涨的是规则,不是注释;单文件上限仍是 24 KB(最大 `tokens.css` 14.4 KB)。
144
+
145
+ ### 已知边界
146
+
147
+ - `delta` 的方向**不由数值推断**(没有 JS)。类名写错就颜色错且不报错 ——
148
+ 这是有意的:本库不带运行时,读数值意味着把格式化逻辑也拉进来。
149
+ - `metric` 不做数值格式化(千分位、小数位、单位换算归数据侧)。
150
+ - 真机读屏(NVDA / VoiceOver)仍是 0 条记录,`docs/A11Y-MANUAL-PROTOCOL.md` 表格还是空的。
151
+
152
+ ---
153
+
19
154
  ## [0.11.16] - 2026-10-29
20
155
 
21
156
  **这一版没有新组件,补的是两处「说了但没做到」。**
package/CONTRIBUTING.md CHANGED
@@ -8,7 +8,7 @@ npm run gate # 全量:含浏览器类
8
8
  npm run gate:fast # 只跑静态层,不用装浏览器
9
9
  ```
10
10
 
11
- 全量是 **204 道门禁**(本机约 5–7 分钟:组件层要起浏览器,还要逐张比 96 张视觉截图;
11
+ 全量是 **208 道门禁**(本机约 5–7 分钟:组件层要起浏览器,还要逐张比 96 张视觉截图;
12
12
  0.11.15 起默认 4 路并发执行,之前串行要 15–20 分钟)。
13
13
  只想确认没改坏静态契约的话用 `gate:fast`,几秒到几分钟。
14
14