agentcss 0.14.0 → 0.16.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.
@@ -440,6 +440,35 @@
440
440
  ========================================================================== */
441
441
  }
442
442
 
443
+ /* ============================================================================
444
+ 容器查询的**槽位标记**(0.16.0)
445
+
446
+ `[data-cq]` 让一个元素成为"查询容器":里面的组件按**自己所在的槽宽**
447
+ 自适应,而不是按视口宽度。`card--row` 与 `analysis-report` 已用它切换布局。
448
+
449
+ 🔴 为什么是显式属性,而不是给组件自动加 `container-type`
450
+ ① `container-type: inline-size` 会带上 `contain: layout style inline-size`
451
+ ⇒ 元素变成 fixed / absolute 后代的**包含块**。
452
+ 若给 `.page` 或抽屉的宿主自动加上,抽屉(position: fixed)
453
+ 会被关进那个盒子里 —— 静默的、只在特定嵌套下出现的破坏。
454
+ ② 库不知道"槽"在哪:同一张卡片既可能在 320px 侧栏,也可能在 900px 主列。
455
+ 只有页面作者知道该把哪一层声明成容器。
456
+
457
+ ⇒ 契约:**加了 `data-cq` 的那一层,就是组件眼里的"窗口"**。
458
+ 不加 ⇒ 整条容器查询不生效(实测:没有容器时 `@container` **不会**退回视口),
459
+ 组件仍按视口断点工作 —— 与 0.15.0 完全一致。
460
+
461
+ ⚠️ 不要把它加在会承载抽屉 / 全屏浮层的祖先上(理由见 ①)。
462
+ 它下面可以放 popover / dropdown(那些用 absolute,不受影响)。
463
+
464
+ ⚠️ 为什么在 tokens.css 而不是 page.css:tokens.css 是**唯一必须引入**的文件
465
+ ("只拿一个组件"的最小页面也带着它)。放在 page.css 里,
466
+ 按需取用的页面会拿到一份永远不生效的 `@container`。
467
+ ========================================================================= */
468
+ [data-cq] {
469
+ container-type: inline-size;
470
+ }
471
+
443
472
  /* ============================================================================
444
473
  无障碍兜底 —— 这一段必须随 token 一起走,不许删
445
474
  ========================================================================= */
@@ -85,10 +85,30 @@ hover 只改 **`transform`** 和 **`box-shadow`**,这两个属性不触发回
85
85
  | `.card--linked` | 整卡可点 | 内部必须有真实 `<a>`/`<button>` |
86
86
  | `.card--selected` | 选中态 | inset shadow,不是改 border |
87
87
  | `.card--compact` | 紧凑 | 两档够了,第三档说明密度没规划 | —— demo 未演示,是留给复用者的钩子
88
- | `.card--row` | 横向 | 560px 以下自动回退纵向 |
88
+ | `.card--row` | 横向 | 560px 以下回退纵向;槽位标了 `data-cq` 时按**槽宽**判断(见下) |
89
89
 
90
90
  ---
91
91
 
92
+ ## 横向卡片看槽宽,不看屏幕
93
+
94
+ `.card--row` 有两套断点,按有没有容器祖先分流:
95
+
96
+ ```html
97
+ <!-- 侧栏里:1440px 的屏幕上依然是竖排,因为槽只有 320px。
98
+ 宽度由你的布局决定;这一层就是组件眼里的"槽"。 -->
99
+ <aside data-cq>
100
+ <div class="card card--row">…</div>
101
+ </aside>
102
+ ```
103
+
104
+ - **没有 `data-cq`** ⇒ `@media (max-width: 560px)` 说了算,与本版之前完全一致。
105
+ - **有 `data-cq`** ⇒ 以**槽宽**为准(35rem = 560px):槽窄则竖排、槽宽则横排,
106
+ 连"视口很窄但槽很宽"那种情况也以槽为准。
107
+ - 为什么必须显式标记:`container-type` 会让元素成为 `position: fixed` 后代的包含块。
108
+ 组件自己加会把抽屉/浮层关进卡片里 —— 这个决定只能由页面作者做。
109
+
110
+ 判据:`container-query-check.js`(含"剥掉 `@container` 布局不塌"的降级验证)。
111
+
92
112
  ## 🔴 整卡可点:正确做法 vs 错误做法
93
113
 
94
114
  **错误**:给 `<div>` 加 `onclick`。
@@ -209,6 +209,43 @@
209
209
  }
210
210
  }
211
211
 
212
+ /* ---------------------------------------------------------------- 按槽宽(0.16.0)
213
+ 🔴 上面那条是**视口**断点,它在一种真实布局里是错的:
214
+ 1440px 的页面上,右侧 320px 栏里放一张 `.card--row` ——
215
+ 视口很宽,于是 `@media` 不触发,卡片保持横排:
216
+ 160px 的图 + 160px 的文字,标题换行三行。
217
+ 症状不来自组件,来自"拿视口宽度替槽宽度做了决定"。
218
+
219
+ ⇒ 槽位被标成 `[data-cq]`(见 01-tokens/tokens.css 的「容器查询的槽位标记」)时,以**槽宽**为准。
220
+ 两条都写,是因为"窄槽 + 宽视口"和"宽槽 + 窄视口"是两个方向:
221
+ 槽窄 ⇒ 竖排;槽宽 ⇒ 横排(后者覆盖上面那条视口断点)。
222
+ ⚠️ 容器查询不匹配时**不会**撤销任何规则 —— 所以"退回去"必须显式写一条。
223
+ 这也是为什么不能只写 `@container (max-width)` 一条。
224
+
225
+ ⚠️ 没有 `[data-cq]` 祖先时这两条整段不生效(实测:`@container` 不会退回视口),
226
+ 行为与 0.15.0 完全一致 —— 这条改动对未标记的页面是**零影响**。
227
+ ---------------------------------------------------------------- */
228
+ @container (max-width: 35rem) {
229
+ .card--row { flex-direction: column; }
230
+ .card--row .card__media {
231
+ width: 100%;
232
+ aspect-ratio: 16 / 9;
233
+ border-radius: calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;
234
+ }
235
+ }
236
+ @container (min-width: 35rem) {
237
+ .card--row {
238
+ flex-direction: row;
239
+ align-items: stretch;
240
+ }
241
+ .card--row .card__media {
242
+ flex: none;
243
+ width: 160px;
244
+ aspect-ratio: auto;
245
+ border-radius: calc(var(--r-md) - 1px) 0 0 calc(var(--r-md) - 1px);
246
+ }
247
+ }
248
+
212
249
  /* ---------------------------------------------------------------- 无障碍兜底
213
250
  card 的 hover 位移属于"小幅位移",reduced-motion 下应当去掉抬起,
214
251
  只保留阴影变化 —— 阴影不属于前庭敏感类动效。
@@ -10,6 +10,7 @@
10
10
  | `Esc` | 关闭,并把焦点**还给按钮** |
11
11
  | `Tab` | 关闭菜单(焦点移到下一个元素)|
12
12
  | `Enter` / `Space` | 激活当前项 |
13
+ | 字母 / 数字(**可选**) | 跳到名字以该前缀开头的项(APG 标为 `(Optional)`;见下) |
13
14
 
14
15
  ## ⚠️ 另有两条容易漏的
15
16
 
@@ -66,12 +67,27 @@ menu.setAttribute('role', 'menu');
66
67
  `data-state` / `data-value`,**不用 class 切换** —— 这是全库 INVARIANT I-8
67
68
  (状态必须活在 DOM 属性上),也是门禁会查的东西。
68
69
 
69
- ## 成熟度:beta(两条行为债,如实列出)
70
+ ## 成熟度:stable(行为零欠账)
70
71
 
71
- | 债 | 现在的后果 |
72
+ 两条历史债都收编了,记账在 `ai/behaviors.json`:
73
+
74
+ | 曾经 | 现在 |
72
75
  |---|---|
73
- | `focus-return:manual` | 关闭后归位是手写的,不是统一机制 |
74
- | `typeahead:gap` | **没有类型搜索** —— APG 的 Menu Button 不硬性要求,但列表长时体验缺失 |
76
+ | `focus-return:manual` | 0.8.6 收编:`open()` 里 `save()`、`close()` 里 `restore()`,替掉手写的 `btn.focus()` |
77
+ | `typeahead:gap` | 0.15.0 收编:打字跳项走 typeahead 核(见下) |
78
+
79
+ ### 打字跳项(APG 的 `(Optional)` 项)
80
+
81
+ 打一个字母 ⇒ 跳到**下一个**以它开头的项;快速连打 ⇒ 前缀累积;连打同一个字母 ⇒
82
+ 在匹配项之间循环;不匹配 ⇒ **什么都不做**(APG 不要求"没找到"有反馈)。
83
+
84
+ ⚠️ **它天然只对拉丁字母标签有效**:中文标签靠输入法,而 IME 组字时 `keydown`
85
+ 给的是 `Process` / `Unidentified`,拿不到字符。所以判据用一张英文夹具测
86
+ (`dropdown-check` ⑦),demo 的中文菜单不受影响、也不会因此变慢。
87
+
88
+ ⚠️ 匹配前会**剥掉开头的装饰符号**:带勾选的项文本是「✓ 按日期」,
89
+ 不剥的话打「按」永远匹配不到。这和 table 列头把 `↕` 念出来是同一类问题 ——
90
+ 装饰符号不该混进"名字"里。
75
91
 
76
92
  ## 反例
77
93
 
@@ -223,6 +223,72 @@
223
223
  };
224
224
  /* ==== BEHAVIOR INJECT END: dismissable ==== */
225
225
 
226
+ /* ==== BEHAVIOR INJECT BEGIN: typeahead ==== */
227
+ var flTypeahead = function (opts) {
228
+ var opt = opts || {};
229
+ var items = opt.items || function () { return []; };
230
+ var textOf = opt.textOf || function (x) { return String((x && x.textContent) || ''); };
231
+ var usable = opt.usable || function () { return true; };
232
+ var timeout = opt.timeout || 500;
233
+ var onHit = opt.onHit || function () {};
234
+ var buf = '';
235
+ var timer = null;
236
+ var last = -1;
237
+
238
+ function stop() {
239
+ if (timer) { clearTimeout(timer); timer = null; }
240
+ }
241
+
242
+ /* 连打同一字符 ⇒ 只按首字符匹配(理由见文件头 ③④)*/
243
+ function repeated(s) {
244
+ if (s.length < 2) return false;
245
+ for (var i = 1; i < s.length; i++) {
246
+ if (s.charAt(i) !== s.charAt(0)) return false;
247
+ }
248
+ return true;
249
+ }
250
+
251
+ function find(needle) {
252
+ var l = items() || [];
253
+ var n = l.length;
254
+ if (!n) return -1;
255
+ for (var k = 0; k < n; k++) {
256
+ var i = (last + 1 + k) % n; /* ① 从当前项之后开始 ⇒ 会绕回 */
257
+ if (!usable(l[i])) continue; /* ⑤ */
258
+ if (textOf(l[i]).toLowerCase().indexOf(needle) === 0) return i;
259
+ }
260
+ return -1; /* ⑥ */
261
+ }
262
+
263
+ function type(ch) {
264
+ if (!ch) return -1;
265
+ buf += String(ch).toLowerCase(); /* ② */
266
+ stop();
267
+ timer = setTimeout(clear, timeout); /* ④ */
268
+ var needle = repeated(buf) ? buf.charAt(0) : buf; /* ③ */
269
+ var i = find(needle);
270
+ if (i >= 0) {
271
+ last = i;
272
+ onHit(i, (items() || [])[i], buf);
273
+ }
274
+ return i;
275
+ }
276
+
277
+ /* 只清缓冲,不清 last(理由见文件头)*/
278
+ function clear() {
279
+ buf = '';
280
+ stop();
281
+ }
282
+
283
+ return {
284
+ type: type,
285
+ clear: clear,
286
+ buffer: function () { return buf; },
287
+ destroy: function () { stop(); },
288
+ };
289
+ };
290
+ /* ==== BEHAVIOR INJECT END: typeahead ==== */
291
+
226
292
  /* ---- 旧环境兼容:Element.closest 在老 WebView 上不存在 ----
227
293
  直接调用会在真机上抛 `is not a function`(本地完全正常,
228
294
  只在老环境炸 —— 属静默失效)。这里做模块内兜底,
@@ -315,6 +381,8 @@
315
381
  焦点留在按钮上,菜单项永远进不去(真机用 NVDA + Edge 实测过)。
316
382
  APG 的菜单按钮也是这样:打开即把焦点交给第一项。 */
317
383
  focusAt(0);
384
+ /* 每次打开都从空缓冲开始:上次关菜单前打了一半的前缀不该留到这次 */
385
+ typeahead.clear();
318
386
  flEmit(root, 'fl-open', null);
319
387
  if (opt.onOpen) opt.onOpen();
320
388
  }
@@ -324,6 +392,7 @@
324
392
  menu.hidden = true;
325
393
  btn.setAttribute('aria-expanded', 'false');
326
394
  active = -1;
395
+ typeahead.clear();
327
396
  if (restoreFocus !== false) fr.restore();
328
397
  flEmit(root, 'fl-close', null);
329
398
  if (opt.onClose) opt.onClose();
@@ -359,6 +428,22 @@
359
428
  onMove: function (i) { focusAt(i); },
360
429
  });
361
430
 
431
+ /* ⑥ 打字跳项 —— APG Menu 的 `(Optional)` 项,走 typeahead 核。
432
+ 匹配**可见文本**,但先剥掉开头的装饰符号:带勾选的项文本是
433
+ "✓ 按日期",不剥的话打"按"永远匹配不到(同 table 列头 ↕ 那一类问题)。
434
+ ⚠️ 中文标签实际上打不出来:IME 走 keydown 时 key 是 Process / Unidentified。
435
+ 这条是给拉丁字母标签的菜单用的 —— APG 的 typeahead 本来就是这个前提,
436
+ 所以判据用一张英文夹具页测(见 dropdown-check ⑥)。 */
437
+ var typeahead = flTypeahead({
438
+ items: function () { return itemsOf(menu); },
439
+ textOf: function (el) {
440
+ var s = String((el && el.textContent) || '').replace(/\s+/g, ' ').trim();
441
+ return s.replace(/^[^0-9A-Za-z\u4e00-\u9fa5]+/, '');
442
+ },
443
+ usable: function (el) { return el.getAttribute('aria-disabled') !== 'true'; },
444
+ onHit: function (i) { focusAt(i); },
445
+ });
446
+
362
447
  /* ---------- 按钮 ---------- */
363
448
  /* 🔴 修:原来先 `setAttribute('aria-controls', menu.id || '')`
364
449
  **再**给 menu.id ⇒ aria-controls 永远拿到空串。
@@ -416,6 +501,17 @@
416
501
  留在原地会**关两次**(同一元素上的两个监听器,stopPropagation 挡不住
417
502
  同级的另一个监听器,那是 stopImmediatePropagation 的活)⇒ onClose 被调两遍。 */
418
503
 
504
+ /* ③b 可打印字符 ⇒ 跳到名字以它开头的项(APG 的 (Optional) 项)
505
+ · 空格排除:它的语义是"激活当前项"(下面 ④)
506
+ · 带 Ctrl / Meta / Alt 排除:那是浏览器快捷键
507
+ · isComposing 时排除:中文输入法正在组字,不能抢 */
508
+ if (k.length === 1 && k !== ' ' &&
509
+ !e.ctrlKey && !e.metaKey && !e.altKey && !e.isComposing) {
510
+ e.preventDefault();
511
+ typeahead.type(k);
512
+ return;
513
+ }
514
+
419
515
  /* ③ Tab 关闭(不拦截,让焦点自然走到下一个)*/
420
516
  if (k === 'Tab') { close(false); return; }
421
517
 
@@ -440,7 +536,10 @@
440
536
  return {
441
537
  open: open,
442
538
  close: close,
443
- destroy: function () { roving.destroy(); dismiss.destroy(); menu.hidden = true; },
539
+ destroy: function () {
540
+ roving.destroy(); dismiss.destroy(); typeahead.destroy();
541
+ menu.hidden = true;
542
+ },
444
543
  };
445
544
  }
446
545
 
@@ -60,6 +60,24 @@ analysis-report.css ← 本示例的局部样式
60
60
  3. 辅助栏 `position: sticky` 必须配 `align-self: start`,
61
61
  否则 sticky 在 Grid 里不生效(默认 stretch 会撑满整列高度)。
62
62
 
63
+ ### 分屏 / 嵌入:断点看槽宽
64
+
65
+ 上面那条断点对**分屏**是错的:1440px 的屏幕上把报告放进 700px 的阅读栏,
66
+ 视口 ≥1024 ⇒ 硬排 `1fr + 15rem`,正文每行只剩七八个字。
67
+
68
+ ```html
69
+ <!-- 700px 的阅读栏;宽度由你的布局决定,这一层就是"槽" -->
70
+ <div data-cq>
71
+ <div class="report">…</div>
72
+ </div>
73
+ ```
74
+
75
+ 槽位标了 `[data-cq]` ⇒ 以槽宽为准:槽 < 64rem 时回到单栏(`aside` 同时退回
76
+ `position: static`,否则空侧栏会跟着滚)。没有标记 ⇒ 行为与本版之前一致。
77
+
78
+ ⚠️ `data-cq` 不要加在承载抽屉/全屏浮层的祖先上(`container-type` 会成为
79
+ `position: fixed` 的包含块)。
80
+
63
81
  ## 已知限制
64
82
 
65
83
  - 断点写不了 `var()` —— 媒体查询里的 `64rem` 与令牌 `--bp-lg`
@@ -58,6 +58,45 @@
58
58
  }
59
59
  }
60
60
 
61
+ /* ---------------------------------------------------------------- 按槽宽(0.16.0)
62
+ 🔴 上面那条断点对"分屏 / 嵌入"是错的:
63
+ 1440px 的屏幕上,报告被放进右侧 700px 的阅读栏(列表在左)——
64
+ 视口 ≥1024,于是硬排成 `1fr + 15rem`:正文 460px、侧栏 240px,
65
+ 正文每行只剩七八个字,而侧栏空空荡荡。
66
+
67
+ ⇒ 槽位标了 `[data-cq]`(见 tokens.css 的「容器查询的槽位标记」)时,以**槽宽**为准。两条方向都写:
68
+ 窄槽 ⇒ 回到单栏(撤销上面的并排)
69
+ 宽槽 ⇒ 并排(覆盖"窄视口"那条)
70
+ ⚠️ 容器查询不匹配**不会**撤销规则,所以"退回去"必须显式写 ——
71
+ 只写 `@container (min-width)` 一条,窄槽在宽视口上仍然是并排的。
72
+
73
+ ⚠️ 没有 `[data-cq]` 祖先时整段不生效,未标记的页面行为不变。
74
+ ---------------------------------------------------------------- */
75
+ @container (max-width: 63.99rem) {
76
+ .report__body {
77
+ grid-template-columns: minmax(0, 1fr);
78
+ gap: var(--sp-6);
79
+ }
80
+ .report__aside {
81
+ position: static;
82
+ max-height: none;
83
+ overflow: visible;
84
+ }
85
+ }
86
+ @container (min-width: 64rem) {
87
+ .report__body {
88
+ grid-template-columns: minmax(0, 1fr) 15rem;
89
+ gap: var(--sp-7);
90
+ }
91
+ .report__aside {
92
+ position: sticky;
93
+ top: var(--sp-4);
94
+ align-self: start;
95
+ max-height: calc(100vh - var(--sp-8));
96
+ overflow: auto;
97
+ }
98
+ }
99
+
61
100
  /* ---------- 正文节奏 ---------- */
62
101
  .report__main > section + section { margin-top: var(--sp-6); }
63
102
  .report__main h2 { margin-top: 0; }
package/CHANGELOG.md CHANGED
@@ -16,7 +16,129 @@
16
16
 
17
17
  (无)
18
18
 
19
- ## [0.14.0] - 2026-10-30
19
+ ## [0.16.0] - 2026-10-31
20
+
21
+ ### 布局 · 断点看**槽宽**,不看视口(`[data-cq]` + `@container`)
22
+
23
+ 全库 44 个 CSS 文件里 `@container` 曾经是 **0 处**,所有断点都是 `@media`。
24
+ 有一类布局因此是错的,而且**在库自己的 demo 里永远测不出来**:
25
+
26
+ - 1440px 页面上,右侧 320px 栏里放 `.card--row` ⇒ 视口断点不触发 ⇒
27
+ 卡片保持横排:160px 图 + 160px 文字,标题折三行;
28
+ - 同一个页面上,报告被放进 700px 阅读栏 ⇒ 视口 ≥1024 ⇒ 硬排 `1fr + 15rem`,
29
+ 正文每行七八个字。
30
+
31
+ 这是**参考系错了**:视口宽度不等于组件所在槽位的宽度。
32
+
33
+ 做法是显式标记 + 两个方向都写:
34
+
35
+ ```html
36
+ <aside class="rail" data-cq> <!-- 这一层就是组件眼里的"窗口" -->
37
+ <div class="card card--row">…</div>
38
+ </aside>
39
+ ```
40
+
41
+ - `[data-cq] { container-type: inline-size }` 放在 **`tokens.css`**(唯一必须引入的文件),
42
+ 不是 `page.css` —— 否则"只拿一个组件"的页面会拿到一份永不生效的 `@container`;
43
+ - **不自动给组件加 `container-type`**:它会带上 `contain: layout`,
44
+ 让元素成为 `position: fixed` 后代的包含块 ⇒ 抽屉/全屏浮层会被关进那个盒子;
45
+ - **没有 `data-cq` 时整条不生效**(实测:没有容器祖先时 `@container` **不会**退回视口),
46
+ 未标记页面的渲染与 0.15.0 完全一致;
47
+ - **两个方向都写**:`@container (max-width)` 只能"变窄",撤销不了一条已生效的
48
+ `@media`(不匹配不会撤销任何规则),所以"标记过的槽以槽为准"还得写 `min-width` 那条。
49
+
50
+ 判据 `container-query-check.js` 10 条,覆盖 card 与 report 的
51
+ 「槽窄 / 槽宽 / 未标记 / 视口回退 / 容器盖过视口」五种情形,外加一条
52
+ **降级证明**:把 `@container` 整块从 CSS 文本里剥掉再跑 —— 布局必须不再按槽切换、
53
+ 且不塌(`cardH>0`、图片仍 160px)。老引擎(Chromium 97)看到的就是剥掉后的那一份。
54
+
55
+ ### 判据 · 文档事实漏了两种写法,英文 README 因此落后两个版本
56
+
57
+ `doc-facts` 只认中文句式「共 N 个(stable A / beta B)」与「N 个组件」,
58
+ 而英文 README 写的是 `**35** components (31 stable / 4 beta)`:
59
+
60
+ | | 文档 | 实际 |
61
+ |---|---|---|
62
+ | 组件数 | 35(31 stable / 4 beta) | **37(37 stable / 0 beta)** |
63
+ | primitives 条数 | 13 | **15** |
64
+ | 表里缺的行 | — | `metric`、`delta` |
65
+
66
+ 两个版本(0.14.0、0.15.0)里它每轮都通过,因为门禁**读不到那种写法**。
67
+ 现在补两条判据:英文的 `N components (X stable / Y beta)`(两种语序都认)、
68
+ 分档标题 `### primitives (N)`;自检各加一条反向用例,另加一条正向基线
69
+ (写对了必须绿)。**"清单里少了什么"比"数字错"更难发现** —— 所以分档条数也进了门禁。
70
+
71
+ ### 更正 · 上一轮盘点里把 `@layer` 排成 P1,是错的
72
+
73
+ `docs/CONVENTIONS.md` 第七节与 `docs/LAYER-BASELINE.md` 早有结论:7 行真机实测里
74
+ Android 14 · 百度浏览器(Chromium 97)**缺 `@layer`**,而 `@layer { … }` 不支持时是
75
+ **整块丢弃** ⇒ 那一层样式直接消失。切层在这个库里不是改进,是会让部分浏览器变白屏。
76
+ 记进 `internal/ERRORS.md` E167:**"0 处"只说明现状,不说明原因;有门禁守着的"没有",
77
+ 几乎都是决定。**
78
+
79
+ ### 体积
80
+
81
+ 分两个口径写,因为这两个数差 **16 倍**,只看一个会得出相反结论:
82
+
83
+ | 口径 | 变化 |
84
+ |---|---|
85
+ | **发布载荷**(`dist/*.css` gzip) | card **+35 B** · report **+51 B** · tokens **+36 B**(合计 **+122 B**) |
86
+ | **源码**(含注释,`size-baseline` 量的就是这个) | 全库 gzip 259.7 → 261.5 KB(**+0.71%**);card +16.9%、report +17.4% |
87
+
88
+ 差异全在注释:`dist/` 剥注释,源码保留 —— 而"为什么两个方向都要写"这件事
89
+ 必须留在源码里,否则下一个人会删掉一半。基线按显式重录记下(`--record`)。
90
+
91
+ ## [0.15.0] - 2026-10-31
92
+
93
+ ### 行为 · dropdown 的「打字跳项」收编 typeahead 核(**最后一个 beta 升 stable**)
94
+
95
+ `ai/behaviors.json` 里最后一条真缺口是 `dropdown` 的 `typeahead:gap`:
96
+ APG Menu 把"按字符跳到名字以它开头的项"列为 `(Optional)`,本库确实没做。
97
+ 这一轮补上,走 `typeahead` 核(不新写一份)。
98
+
99
+ 接线时补了两件核不管的事:
100
+
101
+ - **打开/关闭都清缓冲**:上次关菜单前打了一半的前缀不该留到下次;
102
+ - **匹配前剥掉开头的装饰符号**:带勾选的项文本是「✓ 按日期」,
103
+ 不剥的话打「按」永远匹配不到。这和 table 列头把 `↕` 念出来是同一类问题 ——
104
+ 装饰符号混进了"名字"里。
105
+
106
+ ⚠️ 一个必须写明的限制:**中文标签打不出来**。IME 组字时 `keydown` 给的是
107
+ `Process` / `Unidentified`,拿不到字符。所以这条能力天然只对拉丁字母标签有效 ——
108
+ 这也是 APG 把它标成 Optional 的现实原因。判据因此用一张**英文夹具**测
109
+ (只换标签、跑的是同一份 `Dropdown.create`),demo 的中文菜单不受影响。
110
+
111
+ 判据 6 条,每条各有独立突变体(删分支 / 去掉同字母特判 / 不累积前缀 /
112
+ 不剥装饰符号 / 没找到就跳首项 —— 每次只有对应的那条红):
113
+
114
+ | 判据 | 断言 | 只被哪个突变体打红 |
115
+ |---|---|---|
116
+ | ⑦ 首字母 | 打 `p` ⇒ Paste | 删掉打字分支 |
117
+ | ⑦b 同字母循环 | `c` `c` ⇒ Copy → Cut | 去掉 `repeated()` 特判 |
118
+ | ⑦c 前缀累积 | `s` `e` ⇒ Select all | 把 `buf +=` 改成 `buf =` |
119
+ | ⑦d 不匹配不动 | 打 `z` 焦点不变 | `return -1` 改成 `return 0` |
120
+ | ⑦e 关闭态 | 关着打字不开菜单 | 删掉打字分支 |
121
+ | ⑦f 装饰符号 | 打 `c` 命中「✓ Copy」 | 去掉剥符号那一步 |
122
+
123
+ ### 判据 · 全库 stable 之后,ladder 怎么才算没瞎
124
+
125
+ 37 个组件全 stable,`maturity-gate` ③("不得全落同一档")按时报红。
126
+ 门禁自己写了两条路:加严判据,或显式声明台阶退役。这轮走了**第三条**,
127
+ 因为前两条都不诚实:为了"留几个 beta"而加严是拿数据凑判据,直接删掉 ③ 又是放走量具。
128
+
129
+ ⇒ ③ 改成:全库 stable 时先做**正向控制** —— 给一个真实组件临时塞一条合成债务,
130
+ 重算必须降成 beta;撤掉必须回到 stable。通过了才放行,并在输出里说明
131
+ "阶梯退而不休"。**分支里唯一的证据就是"判据仍然会红"**,不是一个允许全绿的开关。
132
+
133
+ 反向控制也跟着换了:原来的突变体是"把矩阵洗白"(全改成 core)——
134
+ 全库达标后这已经是**合法状态**,突变消失了。改成"**把判据改松**"
135
+ (让 `recompute` 忽略债务、一律返回 stable)⇒ 正向控制抓得到 ⇒ ③ 红,且只红这一条。
136
+
137
+ ### 体积
138
+
139
+ `dropdown.js` 多背一份 typeahead 核(gzip **6.4 → 7.6 KB / +18.9%**)。
140
+ 全库 gzip 258.5 → 259.7 KB(**+0.46%**)。基线按显式重录记下(`size-baseline.py --record`)。
141
+
20
142
 
21
143
  ### 行为 · drawer / overlay 的「关得掉」收编进核(beta → stable)
22
144
 
package/CONTRIBUTING.md CHANGED
@@ -8,7 +8,7 @@ npm run gate # 全量:含浏览器类
8
8
  npm run gate:fast # 只跑静态层,不用装浏览器
9
9
  ```
10
10
 
11
- 全量是 **214 道门禁**(4 路并发实测约 9 分钟 / 退出码 0;单道耗时累加 1691 秒,
11
+ 全量是 **215 道门禁**(4 路并发实测约 9 分钟 / 退出码 0;单道耗时累加 1691 秒,
12
12
  组件层要起浏览器,还要逐张比 96 张视觉截图。0.11.15 起默认 4 路并发)。
13
13
  只想确认没改坏静态契约的话用 `gate:fast`,几秒到几分钟。
14
14
 
package/README.md CHANGED
@@ -171,9 +171,9 @@ Three mistakes the template above already avoids:
171
171
 
172
172
  ## Components
173
173
 
174
- **35** components (31 stable / 4 beta). Each directory holds a `README.md` (why it is built this way) and a `demo.html` (markup you can copy).
174
+ **37** components (stable 37 / beta 0). Each directory holds a `README.md` (why it is built this way) and a `demo.html` (markup you can copy).
175
175
 
176
- ### primitives (13)
176
+ ### primitives (15)
177
177
 
178
178
  | Component | Root class | Purpose |
179
179
  |---|---|---|
@@ -183,7 +183,9 @@ Three mistakes the template above already avoids:
183
183
  | `choice` | `choice` | One control for radio, checkbox and native-select appearance |
184
184
  | `combobox` | `combo` | Searchable input that can hold multiple tags |
185
185
  | `date-range` | `drange` | Start/end date range with preset shortcuts |
186
+ | `delta` | `delta` | Change value: direction and sign, **describes direction only, never judges good or bad** |
186
187
  | `input` | `field` | Text field plus its label, hint and error |
188
+ | `metric` | `metric` | Metric block: one label, one value, one line of context; missing data is a first-class state |
187
189
  | `popover` | `popover` | Floating panel anchored to its trigger |
188
190
  | `progress` | `progress` | Progress bar and ring |
189
191
  | `select` | `select` | Custom dropdown (not the native element) |
package/README.zh-CN.md CHANGED
@@ -83,7 +83,7 @@ npm install github:owain323/frontend-lib
83
83
 
84
84
  ## 组件一览
85
85
 
86
- 共 **37** 个(stable 36 / beta 1)。每个组件目录下有 `README.md`(为什么这么做)和 `demo.html`(能直接抄的结构)。
86
+ 共 **37** 个(stable 37 · 全部通过四条判据)。每个组件目录下有 `README.md`(为什么这么做)和 `demo.html`(能直接抄的结构)。
87
87
 
88
88
  ### primitives · 基础件(15)
89
89
 
package/START-HERE.md CHANGED
@@ -37,7 +37,7 @@
37
37
  | 按钮 | `02-primitives/button/button.css` | 357 | — | 几乎总要 |
38
38
  | 输入框 | `02-primitives/input/input.css` | 239 | — | 有表单就要 |
39
39
  | 标签 / 徽章 | `02-primitives/badge/badge.css` | 141 | — | 有状态标记就要("已披露""停牌") |
40
- | 卡片 | `02-primitives/card/card.css` | 246 | — | 有分组内容就要 |
40
+ | 卡片 | `02-primitives/card/card.css` | 283 | — | 有分组内容就要 |
41
41
  | 单选 / 多选 | `02-primitives/choice/choice.css` | 395 | — | 单选 / 多选就要 |
42
42
  | 下拉选择 | `02-primitives/select/select.css` | 151 | **必需 541** (`select.js`) | 原生 select 的外观或交互不够用就要 |
43
43
  | 可搜索选择 | `02-primitives/combobox/combobox.css` | 223 | **必需 477** (`combobox.js`) | 选项多到要搜索,或要选多个并留成标签就要 |
@@ -49,7 +49,7 @@
49
49
  | 日期区间 | `02-primitives/date-range/date-range.css` | 206 | **必需 301** (`date-range.js`) | 要选起止日期就要 |
50
50
  | 标签页 | `03-patterns/tabs/tabs.css` | 172 | **必需 316** (`tabs.js`) | 同层级内容切换就要 |
51
51
  | 折叠面板 | `03-patterns/accordion/accordion.css` | 453 | **必需 389** (`accordion.js`) | 长表单分段就要 |
52
- | 动作菜单(beta) | `03-patterns/dropdown/dropdown.css` | 135 | **必需 448** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
52
+ | 动作菜单 | `03-patterns/dropdown/dropdown.css` | 135 | **必需 547** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
53
53
  | 文字提示 | `03-patterns/tooltip/tooltip.css` | 89 | **必需 280** (`tooltip.js`) | 图标按钮或缩写需要一句话解释就要 |
54
54
  | 分页 | `03-patterns/pagination/pagination.css` | 195 | **必需 401** (`pagination.js`) | 数据多到要翻页就要 |
55
55
  | 树形 | `03-patterns/tree/tree.css` | 122 | **必需 464** (`tree.js`) | 层级数据(文件 / 组织 / 分类)要展开就要 |
@@ -63,7 +63,7 @@
63
63
  | delta | `02-primitives/delta/delta.css` | 113 | — | |
64
64
  | metric | `02-primitives/metric/metric.css` | 157 | — | |
65
65
  | reveal | `03-patterns/reveal/reveal.css` | 93 | 可选 175 (`reveal.js`,入场动效,纯增强;没有它内容直接可见) | |
66
- | analysis-report | `04-recipes/analysis-report/analysis-report.css` | 187 | — | |
66
+ | analysis-report | `04-recipes/analysis-report/analysis-report.css` | 226 | — | |
67
67
  | detail-page | `04-recipes/detail-page/detail-page.css` | 190 | — | |
68
68
  | report | `04-recipes/report/report.css` | 364 | — | |
69
69
  | table | `04-recipes/table/table.css` | 387 | — | |
package/SUPPORT.md CHANGED
@@ -94,6 +94,10 @@ grep 一条都数不到),还有 `skip "…"`(这台机器跑不了的门
94
94
  3. **`conic-gradient`**(环形进度)—— Safari 15.4+ 才支持
95
95
  4. **滚动条样式** —— `::-webkit-scrollbar` 与 Firefox 的 `scrollbar-width` 是两套语法,组件里都写了
96
96
  5. **iOS 输入框的 `font-size < 16px` 会触发缩放** —— 输入类组件已避免
97
+ 6. **`@container`**(0.16.0 起使用,`[data-cq]` 标记的槽位)—— 与 `@layer` 同类:
98
+ 不支持时**整块丢弃**。区别在于**基础样式不在块里**,所以降级后与标记前完全一致;
99
+ `container-query-check.js` 每次运行都会把 `@container` 剥掉再跑一遍,证明布局不塌。
100
+ Chromium 97 不会白屏,只会"按视口断点"(即旧行为)。
97
101
 
98
102
  ## 已知限制(已验证存在,不是"待验证")
99
103
 
@@ -114,3 +118,6 @@ grep 一条都数不到),还有 `skip "…"`(这台机器跑不了的门
114
118
  3. popover 的碰撞翻转(上面"已知限制"第 1 条)
115
119
 
116
120
  ⚠️ 在补齐之前,请勿把「移动 WebView 支持」当作已验证的能力对外承诺。
121
+
122
+ > 2026-10-31 记:前两项**卡在设备上** —— 手上没有可用的安卓机,也没有 iOS 设备,
123
+ > 云真机需要账号。这条不是"忘了做",是"缺外部条件",所以不写进版本承诺。
package/ai/START-HERE.md CHANGED
@@ -30,7 +30,7 @@
30
30
  > 它的值直接从 `ai/components.json` 与目录实际内容读出来 —— **不信上面的说法,信这个**。
31
31
 
32
32
  <!-- ==== AI-INVENTORY-BEGIN ==== -->
33
- - 组件 **37** 个(`beta` 1 · `stable` 36)
33
+ - 组件 **37** 个(`stable` 37)
34
34
  - `04-recipes/`(页面级示例):`analysis-report`(组合型研究报告:正文 + 辅助栏并排,窄屏退回单栏) · `data-showcase`(四种数据表达方式对照:我到底该用哪个) · `detail-page`(实体详情页骨架:字段齐全 / 部分缺失 / 无数据,三态同构(DOM 不变)) · `longform`(单栏长文排版与阅读节奏) · `report`(报告版式系统 v1:共享基础 + 三种版式取舍(杂志 / 科研技术 / 金融研究)) · `table`(专业数据表格:列级格式与单位,三种用途预设)
35
35
  - `09-assets/`(图表与可视化):`bar`(柱状图,含图表规范的裁剪铁律) · `echarts-adapter`(把令牌喂给 ECharts,按需引入;本库不含 ECharts) · `model-viewer`(惰性加载的 3D 模型查看器) · `scientific-plot`(二维科学绘图:坐标轴 / 误差棒 / 置信区间带 / 对数轴) · `sparkline`(迷你趋势线,没有坐标轴的走势提示)
36
36
  <!-- ==== AI-INVENTORY-END ==== -->
package/ai/behaviors.json CHANGED
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "budget": {
31
31
  "manual": 0,
32
- "gap": 1
32
+ "gap": 0
33
33
  },
34
34
  "matrix": {
35
35
  "dismissable": {
@@ -316,8 +316,8 @@
316
316
  "没有候选项集合"
317
317
  ],
318
318
  "dropdown": [
319
- "gap",
320
- "APG 菜单支持'按首字母跳到菜单项'(可选增强),本库未实现;要做就走 typeahead 核"
319
+ "core",
320
+ "0.15.0 收编:APG Menu 的 `(Optional)` 项「按字符跳项」走 typeahead 核。删掉自写的匹配逻辑(本来也没有),接线时补了两件事:① 匹配前剥掉开头的装饰符号(带勾选的项文本是「✓ 按日期」,不剥就永远匹配不到,同 table 列头 ↕ 那类问题);② open/close 都清缓冲。判据 dropdown-check ⑦(首字母)/ ⑦b(同字母循环)/ ⑦c(前缀累积)/ ⑦d(不匹配不动)/ ⑦e(关着时打字不开菜单)/ ⑦f(装饰符号不进匹配文本),五条各有独立突变体。"
321
321
  ],
322
322
  "flip": [
323
323
  "n/a",
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
3
  "generatedBy": "05-audit/gen-ai-contract.py",
4
- "libraryVersion": "0.14.0",
4
+ "libraryVersion": "0.16.0",
5
5
  "$comment": "机器可读的组件契约。slots = BEM 元素名(在本库里元素名就是插槽名),states.enum = 组件会在 DOM 上写出的 data-* 取值,states.legacyClass = 仍未收敛到 data 属性的历史状态类(INVARIANT I-8 未达标)。",
6
6
  "maturityCriteria": {
7
7
  "alpha": "缺 demo.html 或缺 05-audit 下的专属门禁 ⇒ 还没被机器验证过,不要用",
8
8
  "beta": "有 demo + 专属门禁,但**下面至少有一条没过**:① 状态仍用 .is-* class 表达(违反 INVARIANT I-8);② 该收编到微行为核的行为还是**手写副本**(manual)或**根本没做**(gap) ⇒ 改一处不会全库跟着变(I-14 的病)",
9
9
  "stable": "以上四条全过:有 demo + 专属门禁 + 状态全在 data-* 属性上 + 行为零欠账",
10
10
  "$note": "成熟度是【计算出来的】,不是拍的。手写把某个组件改成 stable 而它还有 manual / gap ⇒ 门禁会红。",
11
- "$whyFourth": "🔴 为什么第四条判据不能省:用前三条时多数组件会**全是 stable** ⇒ ladder 没有任何鉴别力(对标 Primer 六档阶梯,每档都有硬判据)。补上「行为欠账」这一条后:**stable 36 / beta 1**(本行由生成器算出,不要手改)。",
12
- "$strictImpact": "⚠️ strict 档位只放行 stable ⇒ 1 个组件在 strict 下不可用:dropdown。放宽 strict 会让「未收编的手写行为」进入生产路径。(本行由生成器算出,不要手改)"
11
+ "$whyFourth": "🔴 为什么第四条判据不能省:用前三条时多数组件会**全是 stable** ⇒ ladder 没有任何鉴别力(对标 Primer 六档阶梯,每档都有硬判据)。补上「行为欠账」这一条后:**stable 37 / beta 0**(本行由生成器算出,不要手改)。全库达标时 ladder 靠**正向控制**证明还有鉴别力(maturity-gate ③:塞一条合成债务必须降 beta),不是靠\"总得留几个 beta\"。",
12
+ "$strictImpact": "✅ strict 档位放行全部 37 个组件 —— 没有任何组件带着未收编的手写行为或未做的行为(本行由生成器算出,不要手改)"
13
13
  },
14
14
  "summary": {
15
15
  "total": 37,
16
16
  "byMaturity": {
17
- "stable": 36,
18
- "beta": 1
17
+ "stable": 37
19
18
  },
20
19
  "byTier": {
21
20
  "primitive": 15,
@@ -399,14 +398,12 @@
399
398
  "tier": "pattern",
400
399
  "purpose": "下拉菜单",
401
400
  "rootClass": "dd",
402
- "maturity": "beta",
401
+ "maturity": "stable",
403
402
  "maturityReasons": {
404
403
  "hasDemo": true,
405
404
  "hasDedicatedGate": true,
406
405
  "statesOnDomAttributes": true,
407
- "behaviorDebt": [
408
- "typeahead:gap"
409
- ]
406
+ "behaviorDebt": []
410
407
  },
411
408
  "gates": [
412
409
  "dropdown-check.js"
@@ -1 +1 @@
1
- :root {--raw-paper:#f6f7f8;--raw-surface:#ffffff;--raw-surface-sunken:#f1f3f5;--raw-surface-raised:#ffffff;--raw-scrollbar-thumb:#7A808A;--raw-scrollbar-thumb-hov:#6E747E;--raw-text-primary:#15181c;--raw-text-secondary:#4f555e;--raw-text-tertiary:#606770;--raw-text-on-accent:#ffffff;--raw-text-on-solid:#ffffff;--raw-skeleton-bg:#e9ecef;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.72);--raw-border-control:#868b94;--raw-border-decor:#e2e5e9;--raw-border-decor-str:#c8cdd4;--raw-danger:#c0392b;--raw-danger-hover:#8f1e17;--raw-scrim:rgba(21, 24, 28, 0.4);--raw-success:#2f6b3a;--raw-warning:#8a5a00;--raw-info:#1d5f8a;--raw-danger-bg:#fdeeec;--raw-success-bg:#eaf2e6;--raw-warning-bg:#fbf1dd;--raw-info-bg:#e8f1f8;--raw-dir-up:#c0392b;--raw-dir-down:#2f6b3a;--raw-accent:#1b4d8f;--raw-switch-on:#1b4d8f;--raw-accent-hover:#163a6d;--raw-accent-soft:#e6eef7;--raw-tooltip-bg:#15181c;--raw-tooltip-fg:#f6f7f8;--raw-tooltip-border:#15181c;--paper:var(--raw-paper);--surface:var(--raw-surface);--surface-sunken:var(--raw-surface-sunken);--surface-raised:var(--raw-surface-raised);--scrollbar-size:10px;--scrollbar-size-thin:6px;--scrollbar-thumb:var(--raw-scrollbar-thumb);--scrollbar-thumb-hov:var(--raw-scrollbar-thumb-hov);--scrollbar-radius:5px;--text-primary:var(--raw-text-primary);--text-secondary:var(--raw-text-secondary);--text-tertiary:var(--raw-text-tertiary);--text-on-accent:var(--raw-text-on-accent);--text-on-solid:var(--raw-text-on-solid);--skeleton-bg:var(--raw-skeleton-bg);--skeleton-shimmer:var(--raw-skeleton-shimmer);--skeleton-h:1em;--skeleton-size:40px;--skeleton-duration:1.4s;--ease-out:cubic-bezier(0.23, 1, 0.32, 1);--progress-h:6px;--progress-dur:240ms;--r-full:999px;--ring-size:64px;--ring-thickness:8px;--progress:0%;--progress-num:0;--z-popover:60;--popover-min-w:200px;--popover-max-w:320px;--popover-p:var(--sp-3);--popover-gap:8px;--popover-dur:140ms;--border-control:var(--raw-border-control);--border-decor:var(--raw-border-decor);--border-decor-str:var(--raw-border-decor-str);--danger:var(--raw-danger);--danger-hover:var(--raw-danger-hover);--scrim:var(--raw-scrim);--success:var(--raw-success);--warning:var(--raw-warning);--info:var(--raw-info);--danger-bg:var(--raw-danger-bg);--success-bg:var(--raw-success-bg);--warning-bg:var(--raw-warning-bg);--info-bg:var(--raw-info-bg);--dir-up:var(--raw-dir-up);--dir-down:var(--raw-dir-down);--accent:var(--raw-accent);--switch-on:var(--raw-switch-on);--accent-hover:var(--raw-accent-hover);--accent-soft:var(--raw-accent-soft);--tooltip-bg:var(--raw-tooltip-bg);--tooltip-fg:var(--raw-tooltip-fg);--tooltip-border:var(--raw-tooltip-border);--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:clamp(1rem, 0.95rem + 0.3vw, 1.25rem);--sp-5:clamp(1.25rem, 1.15rem + 0.7vw, 1.75rem);--sp-6:clamp(1.5rem, 1.3rem + 1.2vw, 2.25rem);--sp-7:clamp(2rem, 1.7rem + 1.8vw, 3.5rem);--sp-8:clamp(2.5rem, 2.1rem + 2.4vw, 4.5rem);--bp-sm:30rem;--bp-md:48rem;--bp-lg:64rem;--bp-xl:80rem;--fs-xs:0.75rem;--fs-sm:0.875rem;--fs-base:clamp(1rem, 0.95rem + 0.3vw, 1.125rem);--fs-md:clamp(1.125rem, 1.05rem + 0.5vw, 1.3125rem);--fs-lg:clamp(1.3125rem, 1.2rem + 0.7vw, 1.5rem);--fs-xl:clamp(1.5rem, 1.3rem + 1.2vw, 1.875rem);--fs-2xl:clamp(1.75rem, 1.45rem + 1.8vw, 2.5rem);--lh-tight:1.25;--lh-base:1.7;--lh-loose:1.85;--measure-page:52rem;--tracking-title:-0.02em;--r-sm:6px;--r-xs:3px;--r-md:10px;--r-lg:14px;--r-pill:999px;--shadow-1:0 1px 2px rgba(31, 29, 25, 0.05), 0 1px 3px rgba(31, 29, 25, 0.06);--shadow-2:0 2px 4px rgba(31, 29, 25, 0.05), 0 6px 16px rgba(31, 29, 25, 0.08);--dur-fast:120ms;--dur-base:180ms;--dur-slow:280ms;--ease-out:cubic-bezier(0.23, 1, 0.32, 1);--ease-in-out:cubic-bezier(0.77, 0, 0.175, 1);--ease-drawer:cubic-bezier(0.32, 0.72, 0, 1);--lift-sm:1px;--lift-md:2px;--lift-lg:8px;--z-base:0;--z-sticky:10;--z-header:20;--z-raised:5;--z-overlay:30;--z-modal:40;--z-toast:50;--sz-touch:44px;--sz-touch-sm:32px;}@media (prefers-reduced-motion:reduce) {*, *::before, *::after {animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important;}}:focus-visible {outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm);}:root {--measure-prose:34em;--measure-title:20em;--measure-lede:32em;--measure-pull:30em;--measure-aside:32em;}:root:lang(en), :root:lang(en-US), :root:lang(en-GB), :root:lang(fr), :root:lang(de), :root:lang(es), :root:lang(pt), :root:lang(ru), :root:lang(el), :root:lang(tr), :root:lang(vi) {--measure-prose:68ch;--measure-title:34ch;--measure-lede:62ch;--measure-pull:58ch;--measure-aside:56ch;}:root:lang(ar) {--measure-prose:60ch;--measure-title:30ch;--measure-lede:56ch;--measure-pull:52ch;--measure-aside:50ch;}:root:lang(hi), :root:lang(th), :root:lang(km), :root:lang(my) {--measure-prose:56ch;--measure-title:28ch;--measure-lede:52ch;--measure-pull:48ch;--measure-aside:46ch;}:root:lang(zh), :root:lang(zh-CN), :root:lang(zh-TW), :root:lang(ja), :root:lang(ko) {--measure-prose:34em;--measure-title:20em;--measure-lede:32em;--measure-pull:30em;--measure-aside:32em;}@media (prefers-color-scheme:dark) {:root:not([data-theme="light"]) {--raw-paper:#14171A;--raw-surface:#1C2024;--raw-surface-raised:#232830;--raw-surface-sunken:#1A1D21;--raw-scrollbar-thumb:#6E747E;--raw-scrollbar-thumb-hov:#7D828A;--raw-border-decor:#2A3038;--raw-border-decor-str:#3A424C;--raw-border-control:#8A9099;--raw-text-primary:#E4E7EA;--raw-text-secondary:#AEB6C0;--raw-text-tertiary:#9BA3AD;--raw-text-on-accent:#0D1114;--raw-text-on-solid:#0D1114;--raw-skeleton-bg:#2E343C;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.06);--raw-danger:#E07A6E;--raw-danger-hover:#F09A90;--raw-danger-bg:#341D1A;--raw-success:#6FAA7A;--raw-success-bg:#16241A;--raw-dir-up:#E07A6E;--raw-dir-down:#6FAA7A;--raw-warning:#C99A3F;--raw-warning-bg:#242A31;--raw-info:#5B9BC4;--raw-info-bg:#13242F;--raw-accent:#7AA9DE;--raw-switch-on:#3E6EA8;--raw-accent-hover:#8FB8E6;--raw-accent-soft:#1C2C3D;--raw-tooltip-bg:#232830;--raw-tooltip-fg:#E4E7EA;--raw-tooltip-border:#2A3038;--raw-scrim:rgba(0, 0, 0, 0.62);}}[data-theme="light"] {--raw-accent:#1b4d8f;--raw-accent-hover:#163a6d;--raw-accent-soft:#e6eef7;--raw-border-control:#868b94;--raw-border-decor:#e2e5e9;--raw-border-decor-str:#c8cdd4;--raw-danger:#c0392b;--raw-danger-bg:#fdeeec;--raw-danger-hover:#8f1e17;--raw-dir-down:#2f6b3a;--raw-dir-up:#c0392b;--raw-info:#1d5f8a;--raw-info-bg:#e8f1f8;--raw-paper:#f6f7f8;--raw-scrim:rgba(21, 24, 28, 0.4);--raw-scrollbar-thumb:#7A808A;--raw-scrollbar-thumb-hov:#6E747E;--raw-skeleton-bg:#e9ecef;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.72);--raw-success:#2f6b3a;--raw-success-bg:#eaf2e6;--raw-surface:#ffffff;--raw-surface-raised:#ffffff;--raw-surface-sunken:#f1f3f5;--raw-switch-on:#1b4d8f;--raw-text-on-accent:#ffffff;--raw-text-on-solid:#ffffff;--raw-text-primary:#15181c;--raw-text-secondary:#4f555e;--raw-text-tertiary:#606770;--raw-tooltip-bg:#15181c;--raw-tooltip-border:#15181c;--raw-tooltip-fg:#f6f7f8;--raw-warning:#8a5a00;--raw-warning-bg:#fbf1dd;--accent:var(--raw-accent);--accent-hover:var(--raw-accent-hover);--accent-soft:var(--raw-accent-soft);--border-control:var(--raw-border-control);--border-decor:var(--raw-border-decor);--border-decor-str:var(--raw-border-decor-str);--danger:var(--raw-danger);--danger-bg:var(--raw-danger-bg);--danger-hover:var(--raw-danger-hover);--dir-down:var(--raw-dir-down);--dir-up:var(--raw-dir-up);--info:var(--raw-info);--info-bg:var(--raw-info-bg);--paper:var(--raw-paper);--scrim:var(--raw-scrim);--scrollbar-thumb:var(--raw-scrollbar-thumb);--scrollbar-thumb-hov:var(--raw-scrollbar-thumb-hov);--skeleton-bg:var(--raw-skeleton-bg);--skeleton-shimmer:var(--raw-skeleton-shimmer);--success:var(--raw-success);--success-bg:var(--raw-success-bg);--surface:var(--raw-surface);--surface-raised:var(--raw-surface-raised);--surface-sunken:var(--raw-surface-sunken);--switch-on:var(--raw-switch-on);--text-on-accent:var(--raw-text-on-accent);--text-on-solid:var(--raw-text-on-solid);--text-primary:var(--raw-text-primary);--text-secondary:var(--raw-text-secondary);--text-tertiary:var(--raw-text-tertiary);--tooltip-bg:var(--raw-tooltip-bg);--tooltip-border:var(--raw-tooltip-border);--tooltip-fg:var(--raw-tooltip-fg);--warning:var(--raw-warning);--warning-bg:var(--raw-warning-bg);}:root[data-theme="dark"], [data-theme="dark"] {--raw-accent:#7AA9DE;--raw-accent-hover:#8FB8E6;--raw-accent-soft:#1C2C3D;--raw-border-control:#8A9099;--raw-border-decor:#2A3038;--raw-border-decor-str:#3A424C;--raw-danger:#E07A6E;--raw-danger-bg:#341D1A;--raw-danger-hover:#F09A90;--raw-dir-down:#6FAA7A;--raw-dir-up:#E07A6E;--raw-info:#5B9BC4;--raw-info-bg:#13242F;--raw-paper:#14171A;--raw-scrim:rgba(0, 0, 0, 0.62);--raw-scrollbar-thumb:#6E747E;--raw-scrollbar-thumb-hov:#7D828A;--raw-skeleton-bg:#2E343C;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.06);--raw-success:#6FAA7A;--raw-success-bg:#16241A;--raw-surface:#1C2024;--raw-surface-raised:#232830;--raw-surface-sunken:#1A1D21;--raw-switch-on:#3E6EA8;--raw-text-on-accent:#0D1114;--raw-text-on-solid:#0D1114;--raw-text-primary:#E4E7EA;--raw-text-secondary:#AEB6C0;--raw-text-tertiary:#9BA3AD;--raw-tooltip-bg:#232830;--raw-tooltip-border:#2A3038;--raw-tooltip-fg:#E4E7EA;--raw-warning:#C99A3F;--raw-warning-bg:#242A31;--accent:var(--raw-accent);--accent-hover:var(--raw-accent-hover);--accent-soft:var(--raw-accent-soft);--border-control:var(--raw-border-control);--border-decor:var(--raw-border-decor);--border-decor-str:var(--raw-border-decor-str);--danger:var(--raw-danger);--danger-bg:var(--raw-danger-bg);--danger-hover:var(--raw-danger-hover);--dir-down:var(--raw-dir-down);--dir-up:var(--raw-dir-up);--info:var(--raw-info);--info-bg:var(--raw-info-bg);--paper:var(--raw-paper);--scrim:var(--raw-scrim);--scrollbar-thumb:var(--raw-scrollbar-thumb);--scrollbar-thumb-hov:var(--raw-scrollbar-thumb-hov);--skeleton-bg:var(--raw-skeleton-bg);--skeleton-shimmer:var(--raw-skeleton-shimmer);--success:var(--raw-success);--success-bg:var(--raw-success-bg);--surface:var(--raw-surface);--surface-raised:var(--raw-surface-raised);--surface-sunken:var(--raw-surface-sunken);--switch-on:var(--raw-switch-on);--text-on-accent:var(--raw-text-on-accent);--text-on-solid:var(--raw-text-on-solid);--text-primary:var(--raw-text-primary);--text-secondary:var(--raw-text-secondary);--text-tertiary:var(--raw-text-tertiary);--tooltip-bg:var(--raw-tooltip-bg);--tooltip-border:var(--raw-tooltip-border);--tooltip-fg:var(--raw-tooltip-fg);--warning:var(--raw-warning);--warning-bg:var(--raw-warning-bg);}:root {--sans:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Source Han Sans SC', 'Noto Sans CJK SC', 'Geeza Pro', 'Segoe UI Historic', sans-serif;--mono:ui-monospace, 'SF Mono', 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono', Menlo, Consolas, 'Sarasa Mono SC', 'Microsoft YaHei Mono', monospace;--serif:'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Songti SC', 'SimSun', 'Noto Serif CJK SC', 'Source Han Serif SC', serif;--tracking-normal:0;}:root:lang(ar), :root:lang(fa), :root:lang(he), :root:lang(ur) {--tracking-normal:0.01em;}a, button, label, summary, input[type="checkbox"], input[type="radio"], input[type="button"], input[type="submit"], [role="button"], [role="switch"], [role="tab"], [role="option"], .choice, .switch, .btn, .accordion__trigger, .tabs__tab, .card, .list__row {-webkit-tap-highlight-color:transparent;}.choice:active, .switch:active, .btn:active, .list__row:active, .card:active {opacity:0.82;}@media (hover:hover) and (pointer:fine) {.choice:active, .switch:active, .btn:active, .list__row:active, .card:active {opacity:1;}}
1
+ :root {--raw-paper:#f6f7f8;--raw-surface:#ffffff;--raw-surface-sunken:#f1f3f5;--raw-surface-raised:#ffffff;--raw-scrollbar-thumb:#7A808A;--raw-scrollbar-thumb-hov:#6E747E;--raw-text-primary:#15181c;--raw-text-secondary:#4f555e;--raw-text-tertiary:#606770;--raw-text-on-accent:#ffffff;--raw-text-on-solid:#ffffff;--raw-skeleton-bg:#e9ecef;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.72);--raw-border-control:#868b94;--raw-border-decor:#e2e5e9;--raw-border-decor-str:#c8cdd4;--raw-danger:#c0392b;--raw-danger-hover:#8f1e17;--raw-scrim:rgba(21, 24, 28, 0.4);--raw-success:#2f6b3a;--raw-warning:#8a5a00;--raw-info:#1d5f8a;--raw-danger-bg:#fdeeec;--raw-success-bg:#eaf2e6;--raw-warning-bg:#fbf1dd;--raw-info-bg:#e8f1f8;--raw-dir-up:#c0392b;--raw-dir-down:#2f6b3a;--raw-accent:#1b4d8f;--raw-switch-on:#1b4d8f;--raw-accent-hover:#163a6d;--raw-accent-soft:#e6eef7;--raw-tooltip-bg:#15181c;--raw-tooltip-fg:#f6f7f8;--raw-tooltip-border:#15181c;--paper:var(--raw-paper);--surface:var(--raw-surface);--surface-sunken:var(--raw-surface-sunken);--surface-raised:var(--raw-surface-raised);--scrollbar-size:10px;--scrollbar-size-thin:6px;--scrollbar-thumb:var(--raw-scrollbar-thumb);--scrollbar-thumb-hov:var(--raw-scrollbar-thumb-hov);--scrollbar-radius:5px;--text-primary:var(--raw-text-primary);--text-secondary:var(--raw-text-secondary);--text-tertiary:var(--raw-text-tertiary);--text-on-accent:var(--raw-text-on-accent);--text-on-solid:var(--raw-text-on-solid);--skeleton-bg:var(--raw-skeleton-bg);--skeleton-shimmer:var(--raw-skeleton-shimmer);--skeleton-h:1em;--skeleton-size:40px;--skeleton-duration:1.4s;--ease-out:cubic-bezier(0.23, 1, 0.32, 1);--progress-h:6px;--progress-dur:240ms;--r-full:999px;--ring-size:64px;--ring-thickness:8px;--progress:0%;--progress-num:0;--z-popover:60;--popover-min-w:200px;--popover-max-w:320px;--popover-p:var(--sp-3);--popover-gap:8px;--popover-dur:140ms;--border-control:var(--raw-border-control);--border-decor:var(--raw-border-decor);--border-decor-str:var(--raw-border-decor-str);--danger:var(--raw-danger);--danger-hover:var(--raw-danger-hover);--scrim:var(--raw-scrim);--success:var(--raw-success);--warning:var(--raw-warning);--info:var(--raw-info);--danger-bg:var(--raw-danger-bg);--success-bg:var(--raw-success-bg);--warning-bg:var(--raw-warning-bg);--info-bg:var(--raw-info-bg);--dir-up:var(--raw-dir-up);--dir-down:var(--raw-dir-down);--accent:var(--raw-accent);--switch-on:var(--raw-switch-on);--accent-hover:var(--raw-accent-hover);--accent-soft:var(--raw-accent-soft);--tooltip-bg:var(--raw-tooltip-bg);--tooltip-fg:var(--raw-tooltip-fg);--tooltip-border:var(--raw-tooltip-border);--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:clamp(1rem, 0.95rem + 0.3vw, 1.25rem);--sp-5:clamp(1.25rem, 1.15rem + 0.7vw, 1.75rem);--sp-6:clamp(1.5rem, 1.3rem + 1.2vw, 2.25rem);--sp-7:clamp(2rem, 1.7rem + 1.8vw, 3.5rem);--sp-8:clamp(2.5rem, 2.1rem + 2.4vw, 4.5rem);--bp-sm:30rem;--bp-md:48rem;--bp-lg:64rem;--bp-xl:80rem;--fs-xs:0.75rem;--fs-sm:0.875rem;--fs-base:clamp(1rem, 0.95rem + 0.3vw, 1.125rem);--fs-md:clamp(1.125rem, 1.05rem + 0.5vw, 1.3125rem);--fs-lg:clamp(1.3125rem, 1.2rem + 0.7vw, 1.5rem);--fs-xl:clamp(1.5rem, 1.3rem + 1.2vw, 1.875rem);--fs-2xl:clamp(1.75rem, 1.45rem + 1.8vw, 2.5rem);--lh-tight:1.25;--lh-base:1.7;--lh-loose:1.85;--measure-page:52rem;--tracking-title:-0.02em;--r-sm:6px;--r-xs:3px;--r-md:10px;--r-lg:14px;--r-pill:999px;--shadow-1:0 1px 2px rgba(31, 29, 25, 0.05), 0 1px 3px rgba(31, 29, 25, 0.06);--shadow-2:0 2px 4px rgba(31, 29, 25, 0.05), 0 6px 16px rgba(31, 29, 25, 0.08);--dur-fast:120ms;--dur-base:180ms;--dur-slow:280ms;--ease-out:cubic-bezier(0.23, 1, 0.32, 1);--ease-in-out:cubic-bezier(0.77, 0, 0.175, 1);--ease-drawer:cubic-bezier(0.32, 0.72, 0, 1);--lift-sm:1px;--lift-md:2px;--lift-lg:8px;--z-base:0;--z-sticky:10;--z-header:20;--z-raised:5;--z-overlay:30;--z-modal:40;--z-toast:50;--sz-touch:44px;--sz-touch-sm:32px;}[data-cq] {container-type:inline-size;}@media (prefers-reduced-motion:reduce) {*, *::before, *::after {animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important;}}:focus-visible {outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm);}:root {--measure-prose:34em;--measure-title:20em;--measure-lede:32em;--measure-pull:30em;--measure-aside:32em;}:root:lang(en), :root:lang(en-US), :root:lang(en-GB), :root:lang(fr), :root:lang(de), :root:lang(es), :root:lang(pt), :root:lang(ru), :root:lang(el), :root:lang(tr), :root:lang(vi) {--measure-prose:68ch;--measure-title:34ch;--measure-lede:62ch;--measure-pull:58ch;--measure-aside:56ch;}:root:lang(ar) {--measure-prose:60ch;--measure-title:30ch;--measure-lede:56ch;--measure-pull:52ch;--measure-aside:50ch;}:root:lang(hi), :root:lang(th), :root:lang(km), :root:lang(my) {--measure-prose:56ch;--measure-title:28ch;--measure-lede:52ch;--measure-pull:48ch;--measure-aside:46ch;}:root:lang(zh), :root:lang(zh-CN), :root:lang(zh-TW), :root:lang(ja), :root:lang(ko) {--measure-prose:34em;--measure-title:20em;--measure-lede:32em;--measure-pull:30em;--measure-aside:32em;}@media (prefers-color-scheme:dark) {:root:not([data-theme="light"]) {--raw-paper:#14171A;--raw-surface:#1C2024;--raw-surface-raised:#232830;--raw-surface-sunken:#1A1D21;--raw-scrollbar-thumb:#6E747E;--raw-scrollbar-thumb-hov:#7D828A;--raw-border-decor:#2A3038;--raw-border-decor-str:#3A424C;--raw-border-control:#8A9099;--raw-text-primary:#E4E7EA;--raw-text-secondary:#AEB6C0;--raw-text-tertiary:#9BA3AD;--raw-text-on-accent:#0D1114;--raw-text-on-solid:#0D1114;--raw-skeleton-bg:#2E343C;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.06);--raw-danger:#E07A6E;--raw-danger-hover:#F09A90;--raw-danger-bg:#341D1A;--raw-success:#6FAA7A;--raw-success-bg:#16241A;--raw-dir-up:#E07A6E;--raw-dir-down:#6FAA7A;--raw-warning:#C99A3F;--raw-warning-bg:#242A31;--raw-info:#5B9BC4;--raw-info-bg:#13242F;--raw-accent:#7AA9DE;--raw-switch-on:#3E6EA8;--raw-accent-hover:#8FB8E6;--raw-accent-soft:#1C2C3D;--raw-tooltip-bg:#232830;--raw-tooltip-fg:#E4E7EA;--raw-tooltip-border:#2A3038;--raw-scrim:rgba(0, 0, 0, 0.62);}}[data-theme="light"] {--raw-accent:#1b4d8f;--raw-accent-hover:#163a6d;--raw-accent-soft:#e6eef7;--raw-border-control:#868b94;--raw-border-decor:#e2e5e9;--raw-border-decor-str:#c8cdd4;--raw-danger:#c0392b;--raw-danger-bg:#fdeeec;--raw-danger-hover:#8f1e17;--raw-dir-down:#2f6b3a;--raw-dir-up:#c0392b;--raw-info:#1d5f8a;--raw-info-bg:#e8f1f8;--raw-paper:#f6f7f8;--raw-scrim:rgba(21, 24, 28, 0.4);--raw-scrollbar-thumb:#7A808A;--raw-scrollbar-thumb-hov:#6E747E;--raw-skeleton-bg:#e9ecef;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.72);--raw-success:#2f6b3a;--raw-success-bg:#eaf2e6;--raw-surface:#ffffff;--raw-surface-raised:#ffffff;--raw-surface-sunken:#f1f3f5;--raw-switch-on:#1b4d8f;--raw-text-on-accent:#ffffff;--raw-text-on-solid:#ffffff;--raw-text-primary:#15181c;--raw-text-secondary:#4f555e;--raw-text-tertiary:#606770;--raw-tooltip-bg:#15181c;--raw-tooltip-border:#15181c;--raw-tooltip-fg:#f6f7f8;--raw-warning:#8a5a00;--raw-warning-bg:#fbf1dd;--accent:var(--raw-accent);--accent-hover:var(--raw-accent-hover);--accent-soft:var(--raw-accent-soft);--border-control:var(--raw-border-control);--border-decor:var(--raw-border-decor);--border-decor-str:var(--raw-border-decor-str);--danger:var(--raw-danger);--danger-bg:var(--raw-danger-bg);--danger-hover:var(--raw-danger-hover);--dir-down:var(--raw-dir-down);--dir-up:var(--raw-dir-up);--info:var(--raw-info);--info-bg:var(--raw-info-bg);--paper:var(--raw-paper);--scrim:var(--raw-scrim);--scrollbar-thumb:var(--raw-scrollbar-thumb);--scrollbar-thumb-hov:var(--raw-scrollbar-thumb-hov);--skeleton-bg:var(--raw-skeleton-bg);--skeleton-shimmer:var(--raw-skeleton-shimmer);--success:var(--raw-success);--success-bg:var(--raw-success-bg);--surface:var(--raw-surface);--surface-raised:var(--raw-surface-raised);--surface-sunken:var(--raw-surface-sunken);--switch-on:var(--raw-switch-on);--text-on-accent:var(--raw-text-on-accent);--text-on-solid:var(--raw-text-on-solid);--text-primary:var(--raw-text-primary);--text-secondary:var(--raw-text-secondary);--text-tertiary:var(--raw-text-tertiary);--tooltip-bg:var(--raw-tooltip-bg);--tooltip-border:var(--raw-tooltip-border);--tooltip-fg:var(--raw-tooltip-fg);--warning:var(--raw-warning);--warning-bg:var(--raw-warning-bg);}:root[data-theme="dark"], [data-theme="dark"] {--raw-accent:#7AA9DE;--raw-accent-hover:#8FB8E6;--raw-accent-soft:#1C2C3D;--raw-border-control:#8A9099;--raw-border-decor:#2A3038;--raw-border-decor-str:#3A424C;--raw-danger:#E07A6E;--raw-danger-bg:#341D1A;--raw-danger-hover:#F09A90;--raw-dir-down:#6FAA7A;--raw-dir-up:#E07A6E;--raw-info:#5B9BC4;--raw-info-bg:#13242F;--raw-paper:#14171A;--raw-scrim:rgba(0, 0, 0, 0.62);--raw-scrollbar-thumb:#6E747E;--raw-scrollbar-thumb-hov:#7D828A;--raw-skeleton-bg:#2E343C;--raw-skeleton-shimmer:rgba(255, 255, 255, 0.06);--raw-success:#6FAA7A;--raw-success-bg:#16241A;--raw-surface:#1C2024;--raw-surface-raised:#232830;--raw-surface-sunken:#1A1D21;--raw-switch-on:#3E6EA8;--raw-text-on-accent:#0D1114;--raw-text-on-solid:#0D1114;--raw-text-primary:#E4E7EA;--raw-text-secondary:#AEB6C0;--raw-text-tertiary:#9BA3AD;--raw-tooltip-bg:#232830;--raw-tooltip-border:#2A3038;--raw-tooltip-fg:#E4E7EA;--raw-warning:#C99A3F;--raw-warning-bg:#242A31;--accent:var(--raw-accent);--accent-hover:var(--raw-accent-hover);--accent-soft:var(--raw-accent-soft);--border-control:var(--raw-border-control);--border-decor:var(--raw-border-decor);--border-decor-str:var(--raw-border-decor-str);--danger:var(--raw-danger);--danger-bg:var(--raw-danger-bg);--danger-hover:var(--raw-danger-hover);--dir-down:var(--raw-dir-down);--dir-up:var(--raw-dir-up);--info:var(--raw-info);--info-bg:var(--raw-info-bg);--paper:var(--raw-paper);--scrim:var(--raw-scrim);--scrollbar-thumb:var(--raw-scrollbar-thumb);--scrollbar-thumb-hov:var(--raw-scrollbar-thumb-hov);--skeleton-bg:var(--raw-skeleton-bg);--skeleton-shimmer:var(--raw-skeleton-shimmer);--success:var(--raw-success);--success-bg:var(--raw-success-bg);--surface:var(--raw-surface);--surface-raised:var(--raw-surface-raised);--surface-sunken:var(--raw-surface-sunken);--switch-on:var(--raw-switch-on);--text-on-accent:var(--raw-text-on-accent);--text-on-solid:var(--raw-text-on-solid);--text-primary:var(--raw-text-primary);--text-secondary:var(--raw-text-secondary);--text-tertiary:var(--raw-text-tertiary);--tooltip-bg:var(--raw-tooltip-bg);--tooltip-border:var(--raw-tooltip-border);--tooltip-fg:var(--raw-tooltip-fg);--warning:var(--raw-warning);--warning-bg:var(--raw-warning-bg);}:root {--sans:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Source Han Sans SC', 'Noto Sans CJK SC', 'Geeza Pro', 'Segoe UI Historic', sans-serif;--mono:ui-monospace, 'SF Mono', 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono', Menlo, Consolas, 'Sarasa Mono SC', 'Microsoft YaHei Mono', monospace;--serif:'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Songti SC', 'SimSun', 'Noto Serif CJK SC', 'Source Han Serif SC', serif;--tracking-normal:0;}:root:lang(ar), :root:lang(fa), :root:lang(he), :root:lang(ur) {--tracking-normal:0.01em;}a, button, label, summary, input[type="checkbox"], input[type="radio"], input[type="button"], input[type="submit"], [role="button"], [role="switch"], [role="tab"], [role="option"], .choice, .switch, .btn, .accordion__trigger, .tabs__tab, .card, .list__row {-webkit-tap-highlight-color:transparent;}.choice:active, .switch:active, .btn:active, .list__row:active, .card:active {opacity:0.82;}@media (hover:hover) and (pointer:fine) {.choice:active, .switch:active, .btn:active, .list__row:active, .card:active {opacity:1;}}
@@ -1 +1 @@
1
- .card {position:relative;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);transition:transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);}.card__media {aspect-ratio:16 / 9;overflow:hidden;border-radius:calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;background:var(--surface-sunken);}.card__media img {width:100%;height:100%;object-fit:cover;display:block;}.card__media + .card__body {border-radius:0;}.card__body {padding:var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-2);}.card__title {margin:0;font-size:var(--fs-md);font-weight:500;line-height:var(--lh-tight);color:var(--text-primary);}.card__desc {margin:0;font-size:var(--fs-sm);color:var(--text-secondary);line-height:var(--lh-base);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}.card__footer {padding:var(--sp-3) var(--sp-5);border-top:1px solid var(--border-decor);display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);font-size:var(--fs-xs);color:var(--text-tertiary);}.card--flat {border-color:transparent;background:var(--surface);}.card--elevated {border-color:transparent;box-shadow:var(--shadow-1);}.card--interactive {cursor:pointer;}@media (hover:hover) and (pointer:fine) {.card--interactive:hover {transform:translateY(calc(-1 * var(--lift-md)));box-shadow:var(--shadow-2);}}.card--interactive:active {transform:translateY(0);box-shadow:var(--shadow-1);}.card--linked {position:relative;}.card__link {color:inherit;text-decoration:none;}.card__link::after {content:"";position:absolute;inset:0;border-radius:var(--r-md);z-index:var(--z-base);}.card__body :is(button, a, input, select, textarea, [role="button"], [role="checkbox"], [role="switch"], [tabindex]):not(.card__link) {position:relative;z-index:var(--z-raised);}.card--linked:focus-within {outline:2px solid var(--accent);outline-offset:2px;}.card__link:focus-visible {outline:none;}.card--selected {box-shadow:inset 0 0 0 2px var(--accent);}.card--compact .card__body {padding:var(--sp-4);gap:var(--sp-1);}.card--compact .card__title {font-size:var(--fs-base);}.card--row {flex-direction:row;align-items:stretch;}.card--row .card__media {flex:none;width:160px;aspect-ratio:auto;border-radius:calc(var(--r-md) - 1px) 0 0 calc(var(--r-md) - 1px);}@media (max-width:560px) {.card--row {flex-direction:column;}.card--row .card__media {width:100%;aspect-ratio:16 / 9;border-radius:calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;}}@media (prefers-reduced-motion:reduce) {@media (hover:hover) and (pointer:fine) {.card--interactive:hover, .card--interactive:active {transform:none;}}}
1
+ .card {position:relative;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);transition:transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);}.card__media {aspect-ratio:16 / 9;overflow:hidden;border-radius:calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;background:var(--surface-sunken);}.card__media img {width:100%;height:100%;object-fit:cover;display:block;}.card__media + .card__body {border-radius:0;}.card__body {padding:var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-2);}.card__title {margin:0;font-size:var(--fs-md);font-weight:500;line-height:var(--lh-tight);color:var(--text-primary);}.card__desc {margin:0;font-size:var(--fs-sm);color:var(--text-secondary);line-height:var(--lh-base);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}.card__footer {padding:var(--sp-3) var(--sp-5);border-top:1px solid var(--border-decor);display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);font-size:var(--fs-xs);color:var(--text-tertiary);}.card--flat {border-color:transparent;background:var(--surface);}.card--elevated {border-color:transparent;box-shadow:var(--shadow-1);}.card--interactive {cursor:pointer;}@media (hover:hover) and (pointer:fine) {.card--interactive:hover {transform:translateY(calc(-1 * var(--lift-md)));box-shadow:var(--shadow-2);}}.card--interactive:active {transform:translateY(0);box-shadow:var(--shadow-1);}.card--linked {position:relative;}.card__link {color:inherit;text-decoration:none;}.card__link::after {content:"";position:absolute;inset:0;border-radius:var(--r-md);z-index:var(--z-base);}.card__body :is(button, a, input, select, textarea, [role="button"], [role="checkbox"], [role="switch"], [tabindex]):not(.card__link) {position:relative;z-index:var(--z-raised);}.card--linked:focus-within {outline:2px solid var(--accent);outline-offset:2px;}.card__link:focus-visible {outline:none;}.card--selected {box-shadow:inset 0 0 0 2px var(--accent);}.card--compact .card__body {padding:var(--sp-4);gap:var(--sp-1);}.card--compact .card__title {font-size:var(--fs-base);}.card--row {flex-direction:row;align-items:stretch;}.card--row .card__media {flex:none;width:160px;aspect-ratio:auto;border-radius:calc(var(--r-md) - 1px) 0 0 calc(var(--r-md) - 1px);}@media (max-width:560px) {.card--row {flex-direction:column;}.card--row .card__media {width:100%;aspect-ratio:16 / 9;border-radius:calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;}}@container (max-width:35rem) {.card--row {flex-direction:column;}.card--row .card__media {width:100%;aspect-ratio:16 / 9;border-radius:calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;}}@container (min-width:35rem) {.card--row {flex-direction:row;align-items:stretch;}.card--row .card__media {flex:none;width:160px;aspect-ratio:auto;border-radius:calc(var(--r-md) - 1px) 0 0 calc(var(--r-md) - 1px);}}@media (prefers-reduced-motion:reduce) {@media (hover:hover) and (pointer:fine) {.card--interactive:hover, .card--interactive:active {transform:none;}}}
@@ -196,6 +196,68 @@ if (k >= 0) shared.stack.splice(k, 1);
196
196
  };
197
197
  };
198
198
 
199
+ var flTypeahead = function (opts) {
200
+ var opt = opts || {};
201
+ var items = opt.items || function () { return []; };
202
+ var textOf = opt.textOf || function (x) { return String((x && x.textContent) || ''); };
203
+ var usable = opt.usable || function () { return true; };
204
+ var timeout = opt.timeout || 500;
205
+ var onHit = opt.onHit || function () {};
206
+ var buf = '';
207
+ var timer = null;
208
+ var last = -1;
209
+
210
+ function stop() {
211
+ if (timer) { clearTimeout(timer); timer = null; }
212
+ }
213
+
214
+ function repeated(s) {
215
+ if (s.length < 2) return false;
216
+ for (var i = 1; i < s.length; i++) {
217
+ if (s.charAt(i) !== s.charAt(0)) return false;
218
+ }
219
+ return true;
220
+ }
221
+
222
+ function find(needle) {
223
+ var l = items() || [];
224
+ var n = l.length;
225
+ if (!n) return -1;
226
+ for (var k = 0; k < n; k++) {
227
+ var i = (last + 1 + k) % n;
228
+ if (!usable(l[i])) continue;
229
+ if (textOf(l[i]).toLowerCase().indexOf(needle) === 0) return i;
230
+ }
231
+ return -1;
232
+ }
233
+
234
+ function type(ch) {
235
+ if (!ch) return -1;
236
+ buf += String(ch).toLowerCase();
237
+ stop();
238
+ timer = setTimeout(clear, timeout);
239
+ var needle = repeated(buf) ? buf.charAt(0) : buf;
240
+ var i = find(needle);
241
+ if (i >= 0) {
242
+ last = i;
243
+ onHit(i, (items() || [])[i], buf);
244
+ }
245
+ return i;
246
+ }
247
+
248
+ function clear() {
249
+ buf = '';
250
+ stop();
251
+ }
252
+
253
+ return {
254
+ type: type,
255
+ clear: clear,
256
+ buffer: function () { return buf; },
257
+ destroy: function () { stop(); },
258
+ };
259
+ };
260
+
199
261
  function closest(el, sel) {
200
262
  if (!el) return null;
201
263
  if (el.closest) return el.closest(sel);
@@ -269,6 +331,8 @@ btn.setAttribute('aria-expanded', 'true');
269
331
  active = -1;
270
332
 
271
333
  focusAt(0);
334
+
335
+ typeahead.clear();
272
336
  flEmit(root, 'fl-open', null);
273
337
  if (opt.onOpen) opt.onOpen();
274
338
  }
@@ -278,6 +342,7 @@ function close(restoreFocus) {
278
342
  menu.hidden = true;
279
343
  btn.setAttribute('aria-expanded', 'false');
280
344
  active = -1;
345
+ typeahead.clear();
281
346
  if (restoreFocus !== false) fr.restore();
282
347
  flEmit(root, 'fl-close', null);
283
348
  if (opt.onClose) opt.onClose();
@@ -307,6 +372,16 @@ tabindex: false,
307
372
  onMove: function (i) { focusAt(i); },
308
373
  });
309
374
 
375
+ var typeahead = flTypeahead({
376
+ items: function () { return itemsOf(menu); },
377
+ textOf: function (el) {
378
+ var s = String((el && el.textContent) || '').replace(/\s+/g, ' ').trim();
379
+ return s.replace(/^[^0-9A-Za-z\u4e00-\u9fa5]+/, '');
380
+ },
381
+ usable: function (el) { return el.getAttribute('aria-disabled') !== 'true'; },
382
+ onHit: function (i) { focusAt(i); },
383
+ });
384
+
310
385
  if (!menu.id) menu.id = 'dd-menu-' + (new Date()).getTime();
311
386
  btn.setAttribute('aria-haspopup', 'true');
312
387
  btn.setAttribute('aria-expanded', 'false');
@@ -341,6 +416,13 @@ return;
341
416
 
342
417
  if (closest(e.target, '[data-dd-menu]') !== menu) return;
343
418
 
419
+ if (k.length === 1 && k !== ' ' &&
420
+ !e.ctrlKey && !e.metaKey && !e.altKey && !e.isComposing) {
421
+ e.preventDefault();
422
+ typeahead.type(k);
423
+ return;
424
+ }
425
+
344
426
  if (k === 'Tab') { close(false); return; }
345
427
 
346
428
  if (k === 'Enter' || k === ' ' || k === 'Spacebar') {
@@ -360,7 +442,10 @@ onDismiss: function (e, reason) { close(reason === 'esc'); },
360
442
  return {
361
443
  open: open,
362
444
  close: close,
363
- destroy: function () { roving.destroy(); dismiss.destroy(); menu.hidden = true; },
445
+ destroy: function () {
446
+ roving.destroy(); dismiss.destroy(); typeahead.destroy();
447
+ menu.hidden = true;
448
+ },
364
449
  };
365
450
  }
366
451
 
@@ -1 +1 @@
1
- .report {max-width:calc(var(--measure-page) + 16rem);}.report__kicker {margin:0 0 var(--sp-2);font-size:var(--fs-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--text-tertiary);}.report__meta {display:flex;flex-wrap:wrap;gap:var(--sp-1) var(--sp-4);margin:var(--sp-4) 0 0;padding-top:var(--sp-3);border-top:1px solid var(--border-decor);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__meta b {font-weight:500;color:var(--text-secondary);}.report__body {display:grid;grid-template-columns:minmax(0, 1fr);gap:var(--sp-6);margin-top:var(--sp-6);}@media (min-width:64rem) {.report__body {grid-template-columns:minmax(0, 1fr) 15rem;gap:var(--sp-7);}.report__aside {position:sticky;top:var(--sp-4);align-self:start;max-height:calc(100vh - var(--sp-8));overflow:auto;}}.report__main > section + section {margin-top:var(--sp-6);}.report__main h2 {margin-top:0;}.report__main p {margin:var(--sp-3) 0;}.report__main ul, .report__main ol {margin:var(--sp-3) 0;padding-inline-start:var(--sp-5);}.report__main li + li {margin-top:var(--sp-1);}.kf {margin:var(--sp-4) 0;padding:var(--sp-4);background:var(--info-bg);border-radius:var(--r-md);}.kf__title {margin:0 0 var(--sp-2);font-size:var(--fs-sm);font-weight:600;color:var(--text-secondary);}.kf ol {margin:0;padding-inline-start:var(--sp-5);}.kf li {margin-top:var(--sp-2);}.kf strong {color:var(--text-primary);}.report__quote {margin:var(--sp-4) 0;padding:var(--sp-3) var(--sp-4);border-inline-start:3px solid var(--border-decor);color:var(--text-secondary);font-style:italic;}.report__quote cite {display:block;margin-top:var(--sp-2);font-size:var(--fs-xs);font-style:normal;color:var(--text-tertiary);}.report__code {margin:var(--sp-4) 0;padding:var(--sp-3) var(--sp-4);background:var(--surface-sunken);border:1px solid var(--border-decor);border-radius:var(--r-md);overflow-x:auto;font-family:var(--mono);font-size:var(--fs-xs);line-height:var(--lh-loose);color:var(--text-secondary);white-space:pre;}.report__aside .card + .card {margin-top:var(--sp-3);}.report__aside .card__title {font-size:var(--fs-xs);letter-spacing:.04em;text-transform:uppercase;color:var(--text-tertiary);}.report__aside dl {margin:var(--sp-2) 0 0;font-size:var(--fs-sm);}.report__aside dt {margin-top:var(--sp-2);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__aside dd {margin:0;color:var(--text-primary);}.report__aside p {margin:0;font-size:var(--fs-sm);color:var(--text-secondary);}.report__fig {margin:var(--sp-5) 0;padding:var(--sp-3);background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);overflow-x:auto;}.report__fig figcaption {margin-top:var(--sp-2);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__foot {margin-top:var(--sp-8);padding-top:var(--sp-4);border-top:1px solid var(--border-decor);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__foot ol {padding-inline-start:var(--sp-5);}.report__foot li + li {margin-top:var(--sp-1);}@media print {.report__aside {position:static;max-height:none;overflow:visible;}.report__body {grid-template-columns:1fr;}}
1
+ .report {max-width:calc(var(--measure-page) + 16rem);}.report__kicker {margin:0 0 var(--sp-2);font-size:var(--fs-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--text-tertiary);}.report__meta {display:flex;flex-wrap:wrap;gap:var(--sp-1) var(--sp-4);margin:var(--sp-4) 0 0;padding-top:var(--sp-3);border-top:1px solid var(--border-decor);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__meta b {font-weight:500;color:var(--text-secondary);}.report__body {display:grid;grid-template-columns:minmax(0, 1fr);gap:var(--sp-6);margin-top:var(--sp-6);}@media (min-width:64rem) {.report__body {grid-template-columns:minmax(0, 1fr) 15rem;gap:var(--sp-7);}.report__aside {position:sticky;top:var(--sp-4);align-self:start;max-height:calc(100vh - var(--sp-8));overflow:auto;}}@container (max-width:63.99rem) {.report__body {grid-template-columns:minmax(0, 1fr);gap:var(--sp-6);}.report__aside {position:static;max-height:none;overflow:visible;}}@container (min-width:64rem) {.report__body {grid-template-columns:minmax(0, 1fr) 15rem;gap:var(--sp-7);}.report__aside {position:sticky;top:var(--sp-4);align-self:start;max-height:calc(100vh - var(--sp-8));overflow:auto;}}.report__main > section + section {margin-top:var(--sp-6);}.report__main h2 {margin-top:0;}.report__main p {margin:var(--sp-3) 0;}.report__main ul, .report__main ol {margin:var(--sp-3) 0;padding-inline-start:var(--sp-5);}.report__main li + li {margin-top:var(--sp-1);}.kf {margin:var(--sp-4) 0;padding:var(--sp-4);background:var(--info-bg);border-radius:var(--r-md);}.kf__title {margin:0 0 var(--sp-2);font-size:var(--fs-sm);font-weight:600;color:var(--text-secondary);}.kf ol {margin:0;padding-inline-start:var(--sp-5);}.kf li {margin-top:var(--sp-2);}.kf strong {color:var(--text-primary);}.report__quote {margin:var(--sp-4) 0;padding:var(--sp-3) var(--sp-4);border-inline-start:3px solid var(--border-decor);color:var(--text-secondary);font-style:italic;}.report__quote cite {display:block;margin-top:var(--sp-2);font-size:var(--fs-xs);font-style:normal;color:var(--text-tertiary);}.report__code {margin:var(--sp-4) 0;padding:var(--sp-3) var(--sp-4);background:var(--surface-sunken);border:1px solid var(--border-decor);border-radius:var(--r-md);overflow-x:auto;font-family:var(--mono);font-size:var(--fs-xs);line-height:var(--lh-loose);color:var(--text-secondary);white-space:pre;}.report__aside .card + .card {margin-top:var(--sp-3);}.report__aside .card__title {font-size:var(--fs-xs);letter-spacing:.04em;text-transform:uppercase;color:var(--text-tertiary);}.report__aside dl {margin:var(--sp-2) 0 0;font-size:var(--fs-sm);}.report__aside dt {margin-top:var(--sp-2);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__aside dd {margin:0;color:var(--text-primary);}.report__aside p {margin:0;font-size:var(--fs-sm);color:var(--text-secondary);}.report__fig {margin:var(--sp-5) 0;padding:var(--sp-3);background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);overflow-x:auto;}.report__fig figcaption {margin-top:var(--sp-2);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__foot {margin-top:var(--sp-8);padding-top:var(--sp-4);border-top:1px solid var(--border-decor);font-size:var(--fs-xs);color:var(--text-tertiary);}.report__foot ol {padding-inline-start:var(--sp-5);}.report__foot li + li {margin-top:var(--sp-1);}@media print {.report__aside {position:static;max-height:none;overflow:visible;}.report__body {grid-template-columns:1fr;}}
package/dist/COSTS.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  实测两笔账,一笔不省:
11
11
 
12
- - `tokens` 源码版 gzip **19.6 KB** → min 版 **2.7 KB**(省 86%)——
12
+ - `tokens` 源码版 gzip **20.4 KB** → min 版 **2.7 KB**(省 87%)——
13
13
  源码多的那部分**全是注释**(全库 CSS 有 67% 是注释,字节口径),而注释是给维护者的,不是给浏览器的。
14
14
  - tokens 的 min 版 2.7 KB **比任何一个组件自己都贵** ⇒
15
15
  "只用一个组件"的真实起步价就是它。不公开这张表,等于让人盲买。
@@ -18,40 +18,40 @@
18
18
 
19
19
  | 档 | 组件 | tokens(必付) | CSS | JS | **合计** | 源码版合计 | 差 |
20
20
  |---|---|---:|---:|---:|---:|---:|---:|
21
- | primitive | `badge` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 22.4 KB | −19.2 KB |
22
- | primitive | `button` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 26.0 KB | −22.4 KB |
23
- | primitive | `card` | 2.7 KB | 0.9 KB | — | **3.6 KB** | 23.1 KB | −19.5 KB |
24
- | primitive | `choice` | 2.7 KB | 1.1 KB | — | **3.8 KB** | 25.0 KB | −21.2 KB |
25
- | primitive | `combobox` | 2.7 KB | 1.0 KB | 5.4 KB | **9.0 KB** | 29.2 KB | −20.1 KB |
26
- | primitive | `date-range` | 2.7 KB | 0.8 KB | 2.5 KB | **6.1 KB** | 26.1 KB | −20.1 KB |
27
- | primitive | `delta` | 2.7 KB | 0.3 KB | — | **3.0 KB** | 22.4 KB | −19.4 KB |
28
- | primitive | `input` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 23.2 KB | −19.5 KB |
29
- | primitive | `metric` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 22.8 KB | −19.7 KB |
30
- | primitive | `popover` | 2.7 KB | 0.8 KB | 2.2 KB | **5.7 KB** | 26.9 KB | −21.2 KB |
31
- | primitive | `progress` | 2.7 KB | 0.7 KB | — | **3.4 KB** | 22.5 KB | −19.1 KB |
32
- | primitive | `select` | 2.7 KB | 0.8 KB | 3.5 KB | **7.0 KB** | 29.2 KB | −22.2 KB |
33
- | primitive | `separator` | 2.7 KB | 0.2 KB | — | **2.9 KB** | 20.9 KB | −18.0 KB |
34
- | primitive | `skeleton` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 21.1 KB | −17.9 KB |
35
- | primitive | `switch` | 2.7 KB | 0.8 KB | — | **3.4 KB** | 26.8 KB | −23.3 KB |
36
- | pattern | `accordion` | 2.7 KB | 1.3 KB | 1.8 KB | **5.8 KB** | 34.1 KB | −28.3 KB |
37
- | pattern | `content` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 24.2 KB | −20.5 KB |
38
- | pattern | `drawer` | 2.7 KB | 0.8 KB | 2.4 KB | **5.9 KB** | 26.1 KB | −20.2 KB |
39
- | pattern | `dropdown` | 2.7 KB | 0.8 KB | 2.9 KB | **6.4 KB** | 27.9 KB | −21.5 KB |
40
- | pattern | `form-validation` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 24.1 KB | −20.7 KB |
41
- | pattern | `list` | 2.7 KB | 0.6 KB | — | **3.3 KB** | 22.0 KB | −18.7 KB |
42
- | pattern | `nav` | 2.7 KB | 1.2 KB | 2.6 KB | **6.6 KB** | 30.7 KB | −24.1 KB |
43
- | pattern | `overlay` | 2.7 KB | 1.1 KB | 3.3 KB | **7.1 KB** | 34.5 KB | −27.4 KB |
44
- | pattern | `pagination` | 2.7 KB | 0.8 KB | 2.6 KB | **6.0 KB** | 30.1 KB | −24.1 KB |
45
- | pattern | `reveal` | 2.7 KB | 0.3 KB | 1.2 KB | **4.2 KB** | 24.2 KB | −20.0 KB |
46
- | pattern | `states` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 23.6 KB | −20.1 KB |
47
- | pattern | `tabs` | 2.7 KB | 0.7 KB | 2.0 KB | **5.4 KB** | 26.8 KB | −21.4 KB |
48
- | pattern | `tooltip` | 2.7 KB | 0.5 KB | 1.9 KB | **5.1 KB** | 26.0 KB | −20.9 KB |
49
- | pattern | `tree` | 2.7 KB | 0.6 KB | 2.9 KB | **6.2 KB** | 27.8 KB | −21.6 KB |
21
+ | primitive | `badge` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 23.1 KB | −19.9 KB |
22
+ | primitive | `button` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 26.8 KB | −23.1 KB |
23
+ | primitive | `card` | 2.7 KB | 0.9 KB | — | **3.7 KB** | 24.5 KB | −20.8 KB |
24
+ | primitive | `choice` | 2.7 KB | 1.1 KB | — | **3.8 KB** | 25.8 KB | −22.0 KB |
25
+ | primitive | `combobox` | 2.7 KB | 1.0 KB | 5.4 KB | **9.1 KB** | 29.9 KB | −20.9 KB |
26
+ | primitive | `date-range` | 2.7 KB | 0.8 KB | 2.5 KB | **6.1 KB** | 26.9 KB | −20.8 KB |
27
+ | primitive | `delta` | 2.7 KB | 0.3 KB | — | **3.0 KB** | 23.1 KB | −20.2 KB |
28
+ | primitive | `input` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 24.0 KB | −20.3 KB |
29
+ | primitive | `metric` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 23.6 KB | −20.4 KB |
30
+ | primitive | `popover` | 2.7 KB | 0.8 KB | 2.2 KB | **5.7 KB** | 27.7 KB | −22.0 KB |
31
+ | primitive | `progress` | 2.7 KB | 0.7 KB | — | **3.4 KB** | 23.3 KB | −19.9 KB |
32
+ | primitive | `select` | 2.7 KB | 0.8 KB | 3.5 KB | **7.1 KB** | 30.0 KB | −22.9 KB |
33
+ | primitive | `separator` | 2.7 KB | 0.2 KB | — | **3.0 KB** | 21.7 KB | −18.7 KB |
34
+ | primitive | `skeleton` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 21.8 KB | −18.6 KB |
35
+ | primitive | `switch` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 27.5 KB | −24.1 KB |
36
+ | pattern | `accordion` | 2.7 KB | 1.3 KB | 1.8 KB | **5.8 KB** | 34.9 KB | −29.1 KB |
37
+ | pattern | `content` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 25.0 KB | −21.2 KB |
38
+ | pattern | `drawer` | 2.7 KB | 0.8 KB | 2.4 KB | **5.9 KB** | 26.9 KB | −21.0 KB |
39
+ | pattern | `dropdown` | 2.7 KB | 0.8 KB | 3.4 KB | **6.9 KB** | 29.8 KB | −22.9 KB |
40
+ | pattern | `form-validation` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 24.9 KB | −21.4 KB |
41
+ | pattern | `list` | 2.7 KB | 0.6 KB | — | **3.3 KB** | 22.8 KB | −19.5 KB |
42
+ | pattern | `nav` | 2.7 KB | 1.2 KB | 2.6 KB | **6.6 KB** | 31.5 KB | −24.9 KB |
43
+ | pattern | `overlay` | 2.7 KB | 1.1 KB | 3.3 KB | **7.1 KB** | 35.3 KB | −28.1 KB |
44
+ | pattern | `pagination` | 2.7 KB | 0.8 KB | 2.6 KB | **6.1 KB** | 30.9 KB | −24.9 KB |
45
+ | pattern | `reveal` | 2.7 KB | 0.3 KB | 1.2 KB | **4.2 KB** | 24.9 KB | −20.7 KB |
46
+ | pattern | `states` | 2.7 KB | 0.8 KB | — | **3.6 KB** | 24.4 KB | −20.9 KB |
47
+ | pattern | `tabs` | 2.7 KB | 0.7 KB | 2.0 KB | **5.4 KB** | 27.6 KB | −22.2 KB |
48
+ | pattern | `tooltip` | 2.7 KB | 0.5 KB | 1.9 KB | **5.1 KB** | 26.8 KB | −21.7 KB |
49
+ | pattern | `tree` | 2.7 KB | 0.6 KB | 2.9 KB | **6.3 KB** | 28.6 KB | −22.3 KB |
50
50
 
51
51
  ## 三、全库
52
52
 
53
- - 全部组件都用上(min,含 tokens 一份):**62.1 KB**
54
- - 源码版(带注释)全库 gzip:304.3 KB
53
+ - 全部组件都用上(min,含 tokens 一份):**62.7 KB**
54
+ - 源码版(带注释)全库 gzip:307.3 KB
55
55
 
56
56
  > ⚠️ 源码版那个数字**不是**要付的钱:它包含我们的注释(全库 CSS 有 67% 是注释,字节口径)。
57
57
  > 它的用途只有一个 —— 让人看清"dist 到底省了多少"。
@@ -82,10 +82,10 @@ python3 -m http.server 8000 --bind 127.0.0.1
82
82
 
83
83
  > **为什么后三行是补的**:`accordion` / `drawer` / `dropdown` 当时都是 beta 组件,
84
84
  > 而它们升 stable 的判据之一就是「行为零欠账」——人工读屏是这件事**唯一的用户侧证据**。
85
- > (0.14.0 把 drawer / overlay 的 dismissable 收编进核之后,两者已升 stable;
86
- > accordion 的 `roving:gap` 经核对 APG 原文后判定为**判据写错**(APG Accordion 没有
87
- > 方向键),已改为 `n/a`。`ai/components.json` 当前记 **1 个 beta**:dropdown
88
- > —— 它的 `typeahead:gap` 是真的:APG Menu 把"按字符跳项"列为 Optional,本库没做。)
85
+ > (0.14.0 把 drawer / overlay 的 dismissable 收编进核;accordion 的 `roving:gap`
86
+ > 经核对 APG 原文后判定为**判据写错**(APG Accordion 没有方向键),改为 `n/a`;
87
+ > 0.15.0 补上 dropdown 的 typeahead。`ai/components.json` 当前 **37 个全 stable**,
88
+ > 阶梯靠正向控制证明仍能区分有债的组件。)
89
89
  > 只覆盖 `overlay` 一个浮层,等于对其他浮层的真实表现没有任何记录。
90
90
 
91
91
  **本轮跑出来并已修掉的一条真缺陷**:`table` 的列头按钮被念成「公司 · `↕` · 按钮」——
@@ -5,7 +5,7 @@
5
5
 
6
6
  | 项目 | 值 |
7
7
  |---|---|
8
- | 声明适用版本 | agentcss `0.14.0` |
8
+ | 声明适用版本 | agentcss `0.16.0` |
9
9
  | 声明日期 | 2026-10-30 |
10
10
  | 复核节奏 | 每次发版前重跑(`npm run gate` 内含全部证据) |
11
11
  | 范围 | 本库交付的 CSS / JS、全部 `demo.html`(组件页 + 图表页 + 组合页)、以及文档站生成的页面 |
@@ -207,7 +207,34 @@
207
207
  `0` 与「四舍五入到 0」(如精度 1 下的 `0.0004`)都不加 `+` / `−`
208
208
  —— 「+0.0%」等于把持平念成上涨。会计列与市场列是同一条规则。
209
209
 
210
- ## 十一 · 每条规则由什么挡住
210
+ ## 十一 · 断点看槽宽,不看视口
211
+
212
+ `@media` 回答的是"**屏幕**多宽",而组件关心的是"**我**多宽"。这两件事在
213
+ 侧栏、分屏、嵌入卡片里不等,而且 demo 页永远暴露不出来。
214
+
215
+ ```html
216
+ <div class="rail" data-cq> <!-- 这一层就是组件眼里的"窗口" -->
217
+ <div class="card card--row">…</div>
218
+ </div>
219
+ ```
220
+
221
+ - **`data-cq` 是显式选择**:加了它,里面的组件按槽宽切换;不加,行为与本版之前**完全一致**
222
+ (实测:没有容器祖先时 `@container` 不会退回视口,整条不生效)。
223
+ - **不自动给组件加 `container-type`**:它会带上 `contain: layout`,
224
+ 让元素变成 `position: fixed` 后代的包含块 —— 抽屉、全屏浮层会被关进那个盒子。
225
+ 所以 `data-cq` 不许加在承载抽屉/全屏浮层的祖先上。
226
+ - **两个方向都要写**:`@container (max-width)` 只能"变窄",
227
+ 无法撤销一条已经生效的 `@media`(不匹配不会撤销任何规则)。
228
+ 要表达"标记过的槽以槽为准",`min-width` 那一条也得写。
229
+ - **基础样式不许住进 `@container`**:老引擎(Chromium 97)丢弃整块,
230
+ 写进去等于那些浏览器上组件直接没有样式。判据会把 `@container` 剥掉再跑一遍,
231
+ 布局必须不塌。
232
+ - 不需要 `@supports at-rule(@container)`:不支持时整块被丢弃,检测只会带来双写。
233
+
234
+ 挡住它的:`container-query-check.js`(10 条:两组件 × 槽窄/槽宽/未标记 ×
235
+ 两个方向 + 剥块降级)。
236
+
237
+ ## 十二 · 每条规则由什么挡住
211
238
 
212
239
  规则只有落到判据上才会被遵守。对应关系如下(文件都在 `05-audit/`):
213
240
 
@@ -225,6 +252,7 @@
225
252
  | 键盘可达(Tab 顺序、Esc 关闭) | `keyboard-nav.js` |
226
253
  | 减少动效策略双向一致 | `motion.py --reduced-audit` |
227
254
  | 逻辑属性全覆盖 | `rtl-check.js` |
255
+ | 断点看槽宽(`data-cq`)、未标记页面不变、剥掉 `@container` 不塌 | `container-query-check.js` |
228
256
  | `@layer` 计数与 `!important` 分类 | `cascade-layer-gate.py` |
229
257
  | 触控目标 44px | `a11y.py`(静态) + 行为脚本(运行时) |
230
258
  | 页面骨架只有一份(改令牌能牵动全部页面) | `skeleton-lever-check.js` |
package/index.html CHANGED
@@ -94,7 +94,7 @@ li a:active { background: var(--surface-sunken); }
94
94
  </p>
95
95
  </main>
96
96
  <footer class="wrap">
97
- <p class="note">0.14.0 · 零第三方运行时依赖</p>
97
+ <p class="note">0.16.0 · 零第三方运行时依赖</p>
98
98
  </footer>
99
99
  </body>
100
100
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcss",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "移动 Web 组件库:CSS + 可选行为脚本,零第三方运行时依赖。样式用 bundler 引入,交互行为用 script 标签引入。",
5
5
  "keywords": [
6
6
  "css",