agentcss 0.25.1 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/02-primitives/button/README.md +7 -0
  2. package/02-primitives/button/demo.html +52 -0
  3. package/02-primitives/card/README.md +6 -1
  4. package/02-primitives/card/demo.html +13 -0
  5. package/02-primitives/choice/choice.css +10 -0
  6. package/02-primitives/choice/demo.html +38 -0
  7. package/02-primitives/date-range/README.md +1 -1
  8. package/02-primitives/date-range/demo.html +41 -1
  9. package/02-primitives/input/README.md +3 -2
  10. package/02-primitives/input/demo.html +42 -0
  11. package/03-patterns/accordion/demo.html +113 -0
  12. package/03-patterns/content/demo.html +17 -0
  13. package/03-patterns/drawer/README.md +11 -1
  14. package/03-patterns/drawer/drawer.css +22 -6
  15. package/03-patterns/drawer/drawer.js +16 -1
  16. package/03-patterns/dropdown/README.md +10 -8
  17. package/03-patterns/dropdown/demo.html +5 -0
  18. package/03-patterns/dropdown/dropdown.js +8 -0
  19. package/03-patterns/form-validation/demo.html +20 -0
  20. package/03-patterns/list/demo.html +10 -0
  21. package/03-patterns/overlay/overlay.js +14 -1
  22. package/03-patterns/pagination/demo.html +23 -0
  23. package/03-patterns/states/demo.html +39 -1
  24. package/03-patterns/tabs/demo.html +34 -0
  25. package/03-patterns/tooltip/tooltip.js +8 -4
  26. package/04-recipes/app-shell/demo.html +27 -0
  27. package/04-recipes/table/demo.html +34 -0
  28. package/09-assets/bar/bar.js +8 -4
  29. package/09-assets/bar/demo.html +27 -0
  30. package/CHANGELOG.md +66 -1
  31. package/START-HERE.md +6 -6
  32. package/ai/components.json +6 -3
  33. package/dist/02-primitives/choice/choice.css +1 -1
  34. package/dist/03-patterns/drawer/drawer.css +1 -1
  35. package/dist/03-patterns/drawer/drawer.js +10 -1
  36. package/dist/03-patterns/dropdown/dropdown.js +4 -0
  37. package/dist/03-patterns/overlay/overlay.js +7 -1
  38. package/dist/03-patterns/tooltip/tooltip.js +3 -3
  39. package/dist/09-assets/bar/bar.js +4 -3
  40. package/dist/COSTS.md +7 -7
  41. package/docs/ACCESSIBILITY.md +1 -1
  42. package/index.html +1 -1
  43. package/llms-full.txt +3 -3
  44. package/package.json +1 -1
@@ -359,6 +359,14 @@
359
359
  li.className = li.className ? (li.className + ' dd__item') : 'dd__item';
360
360
  }
361
361
  if (li.hasAttribute('data-dd-sep')) {
362
+ /* 🔴 和上面的 dd__item 同一个道理:分隔线那 1px 的高度与背景
363
+ 全在 `.dd__sep` 这条规则里。作者只写 `data-dd-sep` 而不写类名时,
364
+ 量到的渲染高度是 **0px**(实测),两组菜单项直接贴着 ——
365
+ 看上去像"漏了一条分隔线",而且没有任何报错。
366
+ ⇒ 由组件补类,不指望每个使用者记得写。 */
367
+ if (String(li.className).indexOf('dd__sep') < 0) {
368
+ li.className = li.className ? (li.className + ' dd__sep') : 'dd__sep';
369
+ }
362
370
  li.setAttribute('role', 'separator');
363
371
  li.setAttribute('aria-hidden', 'true');
364
372
  } else if (!li.hasAttribute('role')) {
@@ -21,6 +21,8 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
21
21
  .good { background: var(--surface-sunken); }
22
22
  .bad h3 { color: var(--danger); }
23
23
  .good h3 { color: var(--success); }
24
+ /* 校验中示例只放一个字段,别撑满整行 —— 撑满了会让人以为"整页都在查" */
25
+ .checking-box { max-width: 460px; }
24
26
  .btn { display:inline-flex; align-items:center; justify-content:center; height:44px;
25
27
  padding:0 var(--sp-5); border:1px solid transparent; border-radius:var(--r-sm);
26
28
  font-size:var(--fs-sm); font-weight:500; cursor:pointer; font-family:inherit;
@@ -91,6 +93,24 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
91
93
  而且把逻辑硬塞进样式层不可测。控制权交给 JS 加 <code>data-dirty</code> 属性。
92
94
  </div>
93
95
 
96
+ <h3 class="rule-bottom">第三种状态:异步校验中(<code>field--checking</code>)</h3>
97
+ <div class="box checking-box">
98
+ <div class="field field--checking">
99
+ <label class="field__label" for="uname">用户名</label>
100
+ <input class="field__input" id="uname" value="data_team" autocomplete="username"
101
+ aria-describedby="uname-checking">
102
+ <span class="field__checking-hint" id="uname-checking">正在检查是否可用…</span>
103
+ </div>
104
+ <p class="small" style="margin-top:var(--sp-3)">
105
+ 🔴 <strong>校验中绝不能把输入框置灰或禁用</strong> —— 用户会以为"不让打字了",
106
+ 于是停在那里等,而其实他完全可以接着改。
107
+ 正确做法只有一条:<strong>保持可输入</strong>,旁边给一个小的 pending 指示。
108
+ <br>这个指示器是<strong>原地旋转</strong>(12px),不是全屏 spinner:
109
+ 它只说明"这一项在查",不代表"整个页面在忙"。
110
+ <code>prefers-reduced-motion</code> 下放慢到 1.2s 但不停(停了会被当成卡死)。
111
+ </p>
112
+ </div>
113
+
94
114
  <h2 class="rule-bottom">二、完整表单:提交后出错误汇总</h2>
95
115
  <p class="small">什么都不填,直接点提交。焦点会移到汇总上,汇总里每条可点击跳转。</p>
96
116
 
@@ -47,6 +47,9 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
47
47
  </div>
48
48
 
49
49
  <ul class="list" id="list" role="list"></ul>
50
+ <!-- 列表末尾的一行小字:说明"这个列表本身"(条数、排序、口径),
51
+ 不是空状态。空状态用 states 的 .state--empty(见下面)。 -->
52
+ <p class="list__note" id="note" aria-live="polite">共 0 条</p>
50
53
 
51
54
  <!-- 空状态:删光后显示,容器位置不变所以不跳动 -->
52
55
  <div id="empty" hidden>
@@ -165,6 +168,8 @@ var EXTRA = [
165
168
 
166
169
  var list = document.getElementById('list');
167
170
  var emptyBox = document.getElementById('empty');
171
+ var note = document.getElementById('note');
172
+ var mode = '插入顺序';
168
173
  var data = SEED.slice();
169
174
  var seq = 100;
170
175
 
@@ -210,6 +215,7 @@ function paint() {
210
215
  for (var i = 0; i < data.length; i++) { html += rowHtml(data[i]); }
211
216
  list.innerHTML = html;
212
217
  emptyBox.hidden = data.length > 0;
218
+ note.textContent = '共 ' + data.length + ' 条 · ' + mode;
213
219
  }
214
220
 
215
221
  function repaint(mutate) {
@@ -219,6 +225,7 @@ function repaint(mutate) {
219
225
  for (var i = 0; i < data.length; i++) { html += rowHtml(data[i]); }
220
226
  list.innerHTML = html;
221
227
  emptyBox.hidden = data.length > 0;
228
+ note.textContent = '共 ' + data.length + ' 条 · ' + mode;
222
229
  });
223
230
  }
224
231
 
@@ -254,12 +261,14 @@ document.getElementById('add').onclick = function () {
254
261
  };
255
262
 
256
263
  document.getElementById('sort').onclick = function () {
264
+ mode = '按记录数降序';
257
265
  repaint(function () {
258
266
  data.sort(function (a, b) { return b.rec - a.rec; });
259
267
  });
260
268
  };
261
269
 
262
270
  document.getElementById('reverse').onclick = function () {
271
+ mode = '按名称排序';
263
272
  repaint(function () {
264
273
  data.sort(function (a, b) { return a.name.localeCompare(b.name, 'zh-Hans-CN'); });
265
274
  });
@@ -270,6 +279,7 @@ document.getElementById('clear').onclick = function () {
270
279
  };
271
280
 
272
281
  document.getElementById('reload').onclick = function () {
282
+ mode = '插入顺序';
273
283
  data = SEED.slice();
274
284
  for (var i = 0; i < data.length; i++) { data[i].id = 'r' + (++seq); }
275
285
  paint();
@@ -252,6 +252,19 @@
252
252
  🔴 绝不抢焦点。role="status" 会被屏幕阅读器自动播报,
253
253
  focus() 会打断用户当前正在做的事。
254
254
  */
255
+ /* 变体 ⇒ 类名。🔴 两个理由,都不是"为了好看":
256
+ ① **白名单**:`toast({variant:'warn'})` 原本会拼出 `toast--warn`,
257
+ 没有任何 CSS 命中 ⇒ 一条本该是黄色的警示条渲染成普通白条,
258
+ 不报错、控制台干净。现在未知值退回无变体(至少不假装有语义)。
259
+ ② **写完整字面量**:`'toast--' + variant` 在源码里搜不到
260
+ `toast--success`,覆盖率门禁认不出"谁在用这个类"
261
+ (实测 success / warning 因此长期躺在"只被 README 认领"名单里)。 */
262
+ var TOAST_VARIANT = {
263
+ success: 'toast--success',
264
+ error: 'toast--error',
265
+ warning: 'toast--warning'
266
+ };
267
+
255
268
  function toast(opts) {
256
269
  opts = opts || {};
257
270
 
@@ -266,7 +279,7 @@
266
279
  }
267
280
 
268
281
  var el = document.createElement('div');
269
- el.className = 'toast' + (opts.variant ? ' toast--' + opts.variant : '');
282
+ el.className = 'toast' + (TOAST_VARIANT[opts.variant] ? ' ' + TOAST_VARIANT[opts.variant] : '');
270
283
 
271
284
  var body = document.createElement('div');
272
285
  body.className = 'toast__body';
@@ -121,6 +121,23 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
121
121
 
122
122
 
123
123
 
124
+ <h2 class="rule-top">7 · 表格页脚里的精简分页(不占一行位置)</h2>
125
+ <p class="note">
126
+ 数据表底部通常只有一两行的空间:<b>状态文字可以省,播报区不能省</b>。
127
+ <br>做法是把 <code>data-pg-live</code> 挂在一个 <code>.pagination__sr</code> 上 ——
128
+ <b>视觉上不占位,读屏照常播报</b>(用 <code>clip</code> 裁掉,不是 <code>display:none</code>;
129
+ 后者会把整块移出无障碍树,播报就没了)。
130
+ <br>⚠️ 省略 <code>data-pg-status</code> 是允许的(JS 里有判空);
131
+ 省略 <code>data-pg-live</code> 则等于"翻页了但不告诉读屏"。
132
+ </p>
133
+ <div class="demo-box">
134
+ <nav class="pagination" aria-label="明细表分页"
135
+ data-pagination data-total="248" data-page-size="20" data-page="7" id="pg2">
136
+ <ul class="pagination__list" data-pg-list></ul>
137
+ <p class="pagination__sr" data-pg-live aria-live="polite"></p>
138
+ </nav>
139
+ </div>
140
+
124
141
  <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
125
142
  <div class="cmp">
126
143
  <div class="cmp__pane cmp__bad" data-bad="li.pagination__item|count|>7">
@@ -200,6 +217,12 @@ pg.onChange = function (page) {
200
217
  }
201
218
  };
202
219
  load(pg.page);
220
+
221
+ /* 精简分页(页脚用):没有 data-pg-status,播报区挂在 .pagination__sr 上 */
222
+ var pg2 = Pagination.create(document.getElementById('pg2'));
223
+ pg2.onChange = function (page) {
224
+ document.getElementById('pg2').setAttribute('data-page', String(page));
225
+ };
203
226
  </script>
204
227
  </body>
205
228
  </html>
@@ -56,6 +56,12 @@ p.lead { color: var(--text-secondary); }
56
56
  }
57
57
  .lbl { font-size: var(--fs-xs); color: var(--text-tertiary);
58
58
  margin-bottom: var(--sp-2); display: block; }
59
+ /* 列表项骨架:头像 + 两行 —— 结构与真实行一致(.row 的 padding 与 gap 对齐) */
60
+ .sk-list { border: 1px solid var(--border-decor); border-radius: var(--r-sm);
61
+ padding: var(--sp-2) 0; max-width: 420px; }
62
+ .sk-row { display: flex; align-items: center; gap: var(--sp-3);
63
+ padding: var(--sp-3) var(--sp-4); }
64
+ .sk-lines { flex: 1; min-width: 0; }
59
65
  </style>
60
66
  </head>
61
67
  <body class="page">
@@ -140,6 +146,31 @@ p.lead { color: var(--text-secondary); }
140
146
 
141
147
  <hr class="rule">
142
148
 
149
+ <h2>列表项的骨架:头像占位 + 两行文字</h2>
150
+ <p>
151
+ <code>skeleton--avatar</code> 是 40×40 的全圆占位,专门配 <code>skeleton--text</code>
152
+ 做「头像 + 姓名 + 副标题」这种列表项。宽度节奏要跟真内容一致,
153
+ 否则内容到位时头像会把文字挤走 —— 那正是骨架屏要避免的事。
154
+ </p>
155
+ <div class="sk-list" role="status" aria-live="polite" aria-label="正在加载公司列表">
156
+ <div class="sk-row">
157
+ <div class="skeleton skeleton--avatar" aria-hidden="true"></div>
158
+ <div class="sk-lines">
159
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
160
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
161
+ </div>
162
+ </div>
163
+ <div class="sk-row">
164
+ <div class="skeleton skeleton--avatar" aria-hidden="true"></div>
165
+ <div class="sk-lines">
166
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
167
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <hr class="rule">
173
+
143
174
  <h2>300ms:不显示比闪一下好</h2>
144
175
  <p>
145
176
  请求在 300ms 内返回时<strong>不要显示加载态</strong>。骨架屏闪一下比不显示更糟 ——
@@ -218,7 +249,14 @@ var VIEWS = {
218
249
  '<div class="state__action">' +
219
250
  '<button class="btn btn--sm btn--primary" type="button">重试</button>' +
220
251
  '<button class="btn btn--sm btn--secondary" type="button">复制诊断信息</button>' +
221
- '</div></div>';
252
+ '</div>' +
253
+ /* 技术细节放这里、并且折叠起来:主文案说"我该怎么办",
254
+ 细节留给要把它贴给技术支持的人。默认摊开会把所有人都吓一遍。 */
255
+ '<details class="state__more">' +
256
+ '<summary>诊断信息</summary>' +
257
+ '<div class="state__detail">sqlite3 · SELECT … FROM financial_facts WHERE ws=19 · err: SQLITE_BUSY (database is locked) · 12,480 ms · trace 8f2c41a7</div>' +
258
+ '</details>' +
259
+ '</div>';
222
260
  }
223
261
  };
224
262
 
@@ -13,6 +13,10 @@
13
13
  <style>
14
14
  .log { font-family: var(--mono); font-size: var(--fs-xs);
15
15
  color: var(--text-secondary); min-height: 1.5em; }
16
+ /* 键帽:用真正的 <kbd> 元素,不是 span+class(后者会被 refs 判成"未定义的类") */
17
+ kbd { display: inline-block; padding: 2px 7px; border: 1px solid var(--border-control);
18
+ border-bottom-width: 2px; border-radius: 5px; background: var(--surface);
19
+ font-family: var(--mono); font-size: 11px; color: var(--text-primary); }
16
20
  </style>
17
21
  </head>
18
22
  <body class="page">
@@ -86,6 +90,36 @@
86
90
  </div>
87
91
  </div>
88
92
 
93
+ <h2 class="rule-top">纵向(设置页侧栏那种)</h2>
94
+ <p class="note">
95
+ 加 <code>.tabs--vertical</code>,并把 tablist 的
96
+ <code>aria-orientation</code> 改成 <code>vertical</code> ——
97
+ <strong>两件事必须同时做</strong>:只改 CSS 的话,键盘仍然是
98
+ <code>←</code> <code>→</code> 切换,而屏幕阅读器被告知"横向",
99
+ 于是它提示用户按左右键;用户按上下键,没反应。
100
+ </p>
101
+ <div class="tabs tabs--vertical" data-tabs>
102
+ <div class="tabs__list" role="tablist" aria-orientation="vertical" aria-label="账户设置">
103
+ <button class="tabs__tab" role="tab" id="v1" aria-controls="vp1"
104
+ aria-selected="true">基本资料</button>
105
+ <button class="tabs__tab" role="tab" id="v2" aria-controls="vp2"
106
+ aria-selected="false">安全</button>
107
+ <button class="tabs__tab" role="tab" id="v3" aria-controls="vp3"
108
+ aria-selected="false">通知</button>
109
+ </div>
110
+ <div class="tabs__panel" role="tabpanel" id="vp1" aria-labelledby="v1">
111
+ <p>头像、显示名、所属工作区。</p>
112
+ <p><button class="btn btn--md btn--secondary">修改头像(可 Tab 到)</button></p>
113
+ </div>
114
+ <div class="tabs__panel" role="tabpanel" id="vp2" aria-labelledby="v2" hidden>
115
+ <p>两步验证、登录设备、API 令牌。</p>
116
+ </div>
117
+ <div class="tabs__panel" role="tabpanel" id="vp3" aria-labelledby="v3" hidden>
118
+ <p>周报邮件、异常告警的接收范围。</p>
119
+ </div>
120
+ </div>
121
+ <p class="log">试 <kbd>↓</kbd> <kbd>↑</kbd>:选中线应该在<strong>文字右侧</strong>(RTL 下换到左侧)。</p>
122
+
89
123
  <h2 class="rule-top">键盘契约(APG / Radix)</h2>
90
124
  <div class="box">
91
125
  <p>
@@ -157,10 +157,14 @@ var flDismissable = function (opts) {
157
157
  return r;
158
158
  }
159
159
 
160
- /* 逻辑方位 ⇒ 类名后缀。CSS 里只有 --inset-inline-start / --inset-inline-end */
160
+ /* 逻辑方位 ⇒ 完整类名。CSS 里只有 --inset-inline-start / --inset-inline-end。
161
+ 🔴 为什么写整串而不是 `'tooltip--' + CLS[pos]`:拼出来的名字
162
+ 在源码里 grep 不到,改 CSS 时无法反查谁在用;覆盖率门禁也只能看到
163
+ 一段前缀,于是四个方位长期被记成"只被 README 认领"
164
+ —— 而它们其实每次 hover 都在渲染。 */
161
165
  var CLS = {
162
- top: 'top', bottom: 'bottom',
163
- start: 'inset-inline-start', end: 'inset-inline-end',
166
+ top: 'tooltip--top', bottom: 'tooltip--bottom',
167
+ start: 'tooltip--inset-inline-start', end: 'tooltip--inset-inline-end',
164
168
  };
165
169
 
166
170
  /**
@@ -195,7 +199,7 @@ var flDismissable = function (opts) {
195
199
  var id = 'tt' + (++uid);
196
200
  var tip = document.createElement('div');
197
201
  tip.id = id;
198
- tip.className = 'tooltip tooltip--' + CLS[pos];
202
+ tip.className = 'tooltip ' + CLS[pos];
199
203
  tip.setAttribute('role', 'tooltip');
200
204
  tip.textContent = text;
201
205
 
@@ -316,6 +316,33 @@
316
316
  <p class="note">只做纯图标栏、不接抽屉的话,第 4 节那种写法(图标 + <code>aria-label</code>)
317
317
  也成立;但只要你会用 <code>data-shell-nav</code>,就把文字留在 DOM 里 ——
318
318
  一份标记同时满足两种形态。</p>
319
+ <h2>8 · 平面外壳(<code>shell--flat</code>):融进纸面,不画第二层框</h2>
320
+ <div class="demo" style="padding:0">
321
+ <div class="shell shell--flat shell-demo--short">
322
+ <header class="shell__topbar">
323
+ <h2 class="shell__title">内嵌在报告里的外壳</h2>
324
+ </header>
325
+ <nav class="shell__side" aria-label="主导航(平面外壳)">
326
+ <ul class="shell__side-group">
327
+ <li><a class="shell__link" href="#" aria-current="page">概览</a></li>
328
+ <li><a class="shell__link" href="#">明细</a></li>
329
+ </ul>
330
+ </nav>
331
+ <div class="shell__main">
332
+ <div class="shell__content">
333
+ <div class="ph">外壳底色改回纸色(--paper)</div>
334
+ <div class="ph">主区边线透明 ⇒ 不与外层的卡片边框重影</div>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ <p class="note">
340
+ 什么时候用:外壳<strong>本身已经在卡片 / 弹窗 / 报告版式里</strong>。
341
+ 这时它再自带一层底色和左边线,就会和外层边框形成"双重描边" ——
342
+ 两条相距 1px 的线,看上去像渲染错位。
343
+ <br>什么时候别用:外壳是整页容器。那时它需要自己的边界,
344
+ <code>--flat</code> 会让侧栏和主区糊成一片。
345
+ </p>
319
346
 
320
347
  </main>
321
348
  <!-- shell-nav.js 依赖 Drawer(抽屉的行为层)。顺序不能反:
@@ -122,6 +122,40 @@
122
122
 
123
123
 
124
124
 
125
+ <h2 class="rule-top">7 · 缺失值与紧凑密度(对账表这种"一屏二十行"的场合)</h2>
126
+ <p class="note">
127
+ <code>table--compact</code> 把字号降到 <code>--fs-xs</code>、内边距收到
128
+ <code>--sp-1 --sp-2</code> —— <strong>只给数字表、对账表用</strong>,
129
+ 正文表格用了会读不动。
130
+ <br><code>cell--na</code> 是缺失值的统一写法:灰色、居中、不留空。
131
+ <strong>空格子和 0 是两件事</strong>:空格子读者分不清"没有数据"还是"漏了",
132
+ 0 会被当成真实数值参与心算。
133
+ </p>
134
+ <div class="table table--compact">
135
+ <table>
136
+ <caption class="table__cap">表 G · 银行对账明细(示例,含缺失值)</caption>
137
+ <thead>
138
+ <tr>
139
+ <th scope="col">科目</th>
140
+ <th scope="col" class="num">期初</th>
141
+ <th scope="col" class="num">本期发生</th>
142
+ <th scope="col" class="num">期末</th>
143
+ </tr>
144
+ </thead>
145
+ <tbody>
146
+ <tr><th scope="row">银行存款</th><td class="num">1,204,300</td><td class="num">318,200</td><td class="num">1,522,500</td></tr>
147
+ <tr><th scope="row">应收账款</th><td class="num">884,120</td><td class="cell--na">—</td><td class="num">884,120</td></tr>
148
+ <tr><th scope="row">其他应收款</th><td class="cell--na">N/A</td><td class="num">12,400</td><td class="num">12,400</td></tr>
149
+ <tr><th scope="row">预付账款</th><td class="num">46,700</td><td class="num">3,100</td><td class="num">49,800</td></tr>
150
+ </tbody>
151
+ </table>
152
+ </div>
153
+ <p class="table__foot">
154
+ 两处缺失值用了两种写法(<code>—</code> 与 <code>N/A</code>):
155
+ 会计表里 <code>—</code> 表示"本期无发生",<code>N/A</code> 表示"未取到数"。
156
+ 混用会让人以为两者是同一件事 —— <strong>选一种并在表注里说明</strong>。
157
+ </p>
158
+
125
159
  <h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
126
160
  <div class="cmp">
127
161
  <div class="cmp__pane cmp__bad" data-bad="td.num--pos|glyphs|=5">
@@ -23,8 +23,12 @@
23
23
  var NS = 'http://www.w3.org/2000/svg';
24
24
  var uid = 0;
25
25
  /* 分类色 5 个(bar.css 里的 .bar__s0..s4),与 scientific-plot 的 ramp 对齐。
26
- 超过 5 个系列请由调用方给 `series[i].cls` 自定义 —— 颜色循环会造成误读。 */
27
- var SERIES_STYLES = 5;
26
+ 超过 5 个系列请由调用方给 `series[i].cls` 自定义 —— 颜色循环会造成误读。
27
+ 🔴 写成**完整类名数组**而不是 `'bar__s' + n`:
28
+ 拼串在源码里搜不到 `bar__s4`,改配色时无法反查谁在用;
29
+ 也让 .bar__s4 一直躺在覆盖率门禁的"只被 README 认领"名单里。 */
30
+ var SERIES_CLS = ['bar__s0', 'bar__s1', 'bar__s2', 'bar__s3', 'bar__s4'];
31
+ var SERIES_STYLES = SERIES_CLS.length;
28
32
 
29
33
  /* ==================================================================== *
30
34
  * 铁律二的核心:哪些点值得标数字
@@ -193,7 +197,7 @@
193
197
  yy = padT + plotH - vt;
194
198
 
195
199
  rect = el('rect', {
196
- 'class': 'bar__bar bar__s' + (s % SERIES_STYLES) +
200
+ 'class': 'bar__bar ' + SERIES_CLS[s % SERIES_STYLES] +
197
201
  (series[s].cls ? ' ' + series[s].cls : ''),
198
202
  x: x, y: yy, width: barW, height: vt - vb,
199
203
  });
@@ -277,7 +281,7 @@
277
281
  var li = document.createElement('li');
278
282
  li.className = 'bar__legend-item';
279
283
  var sw = document.createElement('span');
280
- sw.className = 'bar__legend-swatch bar__s' + (si % SERIES_STYLES) +
284
+ sw.className = 'bar__legend-swatch ' + SERIES_CLS[si % SERIES_STYLES] +
281
285
  (se.cls ? ' ' + se.cls : '');
282
286
  /* 🔴 用 textContent,不用 innerHTML —— 系列名来自调用方的数据 */
283
287
  li.appendChild(sw);
@@ -82,6 +82,16 @@
82
82
  <button class="btn" id="try">不给 max,看会发生什么</button>
83
83
  <div id="err"></div>
84
84
 
85
+ <h2>5 · 五个系列 = 五档分类色;第 6 个必须自带 <code>cls</code></h2>
86
+ <p class="note">
87
+ <code>bar.css</code> 只提供 <code>.bar__s0</code>–<code>.bar__s4</code> 五档分类色
88
+ (与 <code>scientific-plot</code> 的 ramp 对齐)。超过五个系列时,本库
89
+ <b>不会循环取色</b>——循环会让两个不同的系列拿到同一个颜色,读者会以为它们是同一类。
90
+ <br>⇒ 第 6 个系列起,请由调用方给 <code>series[i].cls</code>。
91
+ 下面这张图正好用满五档:五根柱子五种颜色,都能在基线截图里逐色核对。
92
+ </p>
93
+ <div class="bar" id="b5"></div>
94
+
85
95
  </main>
86
96
 
87
97
  <script src="bar.js"></script>
@@ -156,6 +166,23 @@
156
166
  ariaLabel: '每日 09:00 前的事项数,含阈值线 1',
157
167
  });
158
168
 
169
+ /* 5 · 五系列 = 五档分类色(.bar__s0..s4 全部真的渲染出来) */
170
+ Bar.draw({
171
+ host: document.getElementById('b5'),
172
+ mode: 'group',
173
+ max: 9,
174
+ height: 190,
175
+ labels: LBL.slice(0, 6),
176
+ series: [
177
+ { name: '受理', values: [3, 4, 5, 2, 6, 4] },
178
+ { name: '审核', values: [2, 3, 4, 5, 3, 6] },
179
+ { name: '放款', values: [4, 2, 3, 4, 5, 3] },
180
+ { name: '对账', values: [5, 5, 2, 3, 4, 2] },
181
+ { name: '归档', values: [1, 6, 6, 1, 2, 5] },
182
+ ],
183
+ ariaLabel: '六天五个环节的并列分组柱状图,五档分类色',
184
+ });
185
+
159
186
  /* 4 · 演示"量程必填" */
160
187
  document.getElementById('try').addEventListener('click', function () {
161
188
  var box = document.getElementById('err');
package/CHANGELOG.md CHANGED
@@ -16,9 +16,74 @@
16
16
 
17
17
  (无)
18
18
 
19
+ ## [0.25.2] - 2026-11-03
20
+
21
+ **修订版:把覆盖率门禁里 41 个「只被 README 认领」的类全部变成真的渲染。
22
+ 量它们的过程本身成了一次体检 —— 查出 4 个静默缺陷(两个方位摆错、分隔线高度 0、
23
+ 说明落在标签右侧),外加 3 条判据在误报。不改类名、不改令牌、不改 API。**
24
+
25
+ ### 清零:41 个未渲染类 → 0
26
+
27
+ `css-coverage-baseline.json` 的棘轮从 41 条降到 **0**(`unrendered: []`)。
28
+ 三类分开处理,不是一把梭往 demo 里塞空 div:
29
+
30
+ | 类型 | 数量 | 做法 |
31
+ |---|---|---|
32
+ | JS 拼接生成、**其实一直在渲染** | 10 | `tooltip--top/bottom/inset-inline-*`、`drawer--left/right/top/bottom`、`toast--success/warning` |
33
+ | 纯 CSS、demo 里一次没用过 | 30 | 逐个补真实用例(徽标/卡片/表格/抽屉/提示…) |
34
+ | README 里的兼容别名 | 1 | `rep__lede`:加判据要求它与 `rep__standfirst` 计算样式**逐项相同** |
35
+
36
+ 第一类是判据自己的盲区:`'tooltip--' + CLS[pos]` 这种拼串在源码里 grep 不到
37
+ `tooltip--inset-inline-start`,于是"渲染了但没人认领"。修法不是放宽判据,
38
+ 而是**改成完整字面量映射**(`{ top: 'tooltip--top', … }`)——顺带把
39
+ `toast({variant:'warn'})`(拼出不存在的类 ⇒ 警示条渲染成白条)也收成了白名单,
40
+ `{place:'tp'}`(拼出 `drawer--tp` ⇒ 静默贴右)现在退回默认值。
41
+
42
+ ### 修复:四个「渲染出来才发现」的缺陷
43
+
44
+ | 缺陷 | 实测症状 | 修法 |
45
+ |---|---|---|
46
+ | 抽屉的左右/上下**根本不动** | 1280×800 下四个方位量矩形:`--left` 的 left=860(该 0)、`--bottom` 的 top=0(该 380)。两个方向摆错,控制台干净、契约全绿 | 容器只留主轴对齐,落位交给各方向的 `margin-inline-end:auto` / `align-self`。四个方位补了逐条判据 |
47
+ | 菜单分隔线**高度 0** | 只写 `data-dd-sep` 不写类名时,量到的高度是 0px,两组菜单项直接贴着,像漏了一条线 | 由 JS 补 `.dd__sep`(和它早就在做的 `dd__item` 同一个理由:组件级类名不该要求使用者手抄) |
48
+ | `.choice__hint` 落在标签**右侧** | README 写"标签下方",实际是第三个 flex item,被 `flex:1` 的 label 挤到最右边 | `.choice` 加 `flex-wrap`,hint 独占一行并缩进到与文字左对齐(20px 勾选框 + 8px gap) |
49
+ | `.drange--disabled` **只管一半** | 文档写"整体禁用",CSS 只把预设与清除置灰;日期输入照样能改 | 文档改成"视觉禁用",demo 补正确组合(类负责观感、原生 `disabled` 负责行为),并说明只做一半的后果 |
50
+
51
+ ### 修复:三条判据在**误报**(比漏报更伤信任)
52
+
53
+ - **form-validation「错误文案具体」**:原来扫的是"所有 `aria-describedby` 指向的文本",
54
+ 于是帮助文字与"校验中"提示也被当错误文案审;判据里还有一条
55
+ `t.length <= 最长含糊词长度 + 2`(=9)—— 等于"凡是 ≤9 字都算含糊",与内容无关。
56
+ 实测把一个 8 字的正常提示判成"太含糊"。现在只审 `.field__error` / `role=alert`
57
+ / `aria-invalid` 指向的文案,且只判"太短(<4 字)"与"只由含糊词构成(≤6 字)"。
58
+ 反向控制:把文案换成「输入有误」⇒ 立刻转红。
59
+ - **report「宽通道」**:参照物取成了 `.rep__sec`(列容器,544px),而行长只施加在
60
+ 内容块上(420px)⇒ "宽通道"永远看着没打开。改成探针式:同一篇正文里插一个块,
61
+ 先量(被行长压住)、加类再量(放开),两个值必须拉开。
62
+ 顺带记下:`--wide` 只在 **finance** 版有意义(magazine 的 42em 比列还宽)。
63
+ - **coverage-gate 的"未渲染"口径**:它不是"没渲染",是"文本里没提到"。
64
+ 文档里补了一句,免得后人把绿当成"视觉回归看得到它"。
65
+
66
+ ### 新增判据(10 条)
67
+
68
+ | 契约 | 新增 |
69
+ |---|---|
70
+ | `drawer-check` | 四个方位各量一次"矩形贴哪条边"(此前只测 `right`) |
71
+ | `tabs-contract` | 纵向变体:`flex-direction:column`、面板在右侧、`↓`/`↑` 真的切换(此前纵向只有 CSS,没有判据) |
72
+ | `report-check` | `rep__lede` 与主名计算样式逐项相同;宽通道探针(with/without 两次测量) |
73
+
74
+ ### 视觉基线
75
+
76
+ 16 页有变化(浅色 16 / 暗色 16 / RTL 8,都是新增区块),`--update` 重录后
77
+ **109 / 109 张逐字节一致**;Linux 基线同批在 CI 上重录(`release.yml` 的
78
+ `shoot_baseline`)。`size-baseline` 重录:drawer.css +34.5%(1.5 KB 全是注释 ——
79
+ 这段"两个方位摆错"的现场记录值得留着)、drawer.js +8.5%、choice.css +5.3%,
80
+ 其余 65 个文件不变。
81
+
82
+ ---
83
+
19
84
  ## [0.25.1] - 2026-11-03
20
85
 
21
- **修订版:修 `agentcss pdf` 在**长文档**上的一处误判 —— 50 页的文档会被报
86
+ **修订版:修 `agentcss pdf` 在「长文档」上的一处误判 —— 50 页的文档会被报
22
87
  「无法判定预算」(退出码 4),哪怕预算够、文件也没坏。不改类名、不改令牌、不改 API。**
23
88
 
24
89
  ### 修复:长文档的页树根 `Count` 被读成了叶节点的数
package/START-HERE.md CHANGED
@@ -38,7 +38,7 @@
38
38
  | 输入框 | `02-primitives/input/input.css` | 253 | — | 有表单就要 |
39
39
  | 标签 / 徽章 | `02-primitives/badge/badge.css` | 141 | — | 有状态标记就要("已披露""停牌") |
40
40
  | 卡片 | `02-primitives/card/card.css` | 283 | — | 有分组内容就要 |
41
- | 单选 / 多选 | `02-primitives/choice/choice.css` | 424 | — | 单选 / 多选就要 |
41
+ | 单选 / 多选 | `02-primitives/choice/choice.css` | 434 | — | 单选 / 多选就要 |
42
42
  | 下拉选择 | `02-primitives/select/select.css` | 151 | **必需 631** (`select.js`) | 原生 select 的外观或交互不够用就要 |
43
43
  | 可搜索选择 | `02-primitives/combobox/combobox.css` | 223 | **必需 477** (`combobox.js`) | 选项多到要搜索,或要选多个并留成标签就要 |
44
44
  | 开关 | `02-primitives/switch/switch.css` | 429 | — | 有"立即生效的设置"就要 |
@@ -49,12 +49,12 @@
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
- | 动作菜单 | `03-patterns/dropdown/dropdown.css` | 135 | **必需 547** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
53
- | 文字提示 | `03-patterns/tooltip/tooltip.css` | 89 | **必需 280** (`tooltip.js`) | 图标按钮或缩写需要一句话解释就要 |
52
+ | 动作菜单 | `03-patterns/dropdown/dropdown.css` | 135 | **必需 555** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
53
+ | 文字提示 | `03-patterns/tooltip/tooltip.css` | 89 | **必需 284** (`tooltip.js`) | 图标按钮或缩写需要一句话解释就要 |
54
54
  | 分页 | `03-patterns/pagination/pagination.css` | 195 | **必需 401** (`pagination.js`) | 数据多到要翻页就要 |
55
55
  | 树形 | `03-patterns/tree/tree.css` | 122 | **必需 464** (`tree.js`) | 层级数据(文件 / 组织 / 分类)要展开就要 |
56
- | 侧边抽屉 | `03-patterns/drawer/drawer.css` | 129 | **必需 361** (`drawer.js`) | 要从侧边滑出面板就要 |
57
- | 弹窗 / 吐司 | `03-patterns/overlay/overlay.css` | 338 | **必需 684** (`overlay.js`) | 需要弹层(吐司 / 模态)就要 |
56
+ | 侧边抽屉 | `03-patterns/drawer/drawer.css` | 145 | **必需 376** (`drawer.js`) | 要从侧边滑出面板就要 |
57
+ | 弹窗 / 吐司 | `03-patterns/overlay/overlay.css` | 338 | **必需 697** (`overlay.js`) | 需要弹层(吐司 / 模态)就要 |
58
58
  | 状态(空 / 加载 / 错 / 成功) | `03-patterns/states/states.css` | 269 | — | **凡是会异步取数就要** |
59
59
  | 表单校验 | `03-patterns/form-validation/form-validation.css` | 274 | — | 提交前要校验就要 |
60
60
  | 列表增删 | `03-patterns/list/list.css` | 162 | 可选 201 (`flip.js`,FLIP 增删动画,纯增强) | 列表会动态增删就要 |
@@ -70,7 +70,7 @@
70
70
  | detail-page | `04-recipes/detail-page/detail-page.css` | 190 | — | |
71
71
  | report | `04-recipes/report/report.css` | 369 | — | |
72
72
  | table | `04-recipes/table/table.css` | 409 | — | |
73
- | bar | `09-assets/bar/bar.css` | 181 | **必需 320** (`bar.js`) | |
73
+ | bar | `09-assets/bar/bar.css` | 181 | **必需 324** (`bar.js`) | |
74
74
  | model-viewer | `09-assets/model-viewer/model-viewer.css` | 143 | **必需 423** (`model-viewer.js`) | |
75
75
  | scientific-plot | `09-assets/scientific-plot/scientific-plot.css` | 141 | **必需 603** (`scientific-plot.js`) | |
76
76
  | sparkline | `09-assets/sparkline/sparkline.css` | 158 | **必需 508** (`sparkline.js`) | |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
3
  "generatedBy": "05-audit/gen-ai-contract.py",
4
- "libraryVersion": "0.25.1",
4
+ "libraryVersion": "0.25.2",
5
5
  "$comment": "机器可读的组件契约。slots = BEM 元素名(在本库里元素名就是插槽名),states.enum = 组件会在 DOM 上写出的 data-* 取值,states.legacyClass = 仍未收敛到 data 属性的历史状态类(INVARIANT I-8 未达标)。printPolicy = 这个组件在纸上会发生什么(空数组 = 原样保留);pagination = 内容是否被分页裁过(server 不许与 expand 同时出现,否则展开的只是当前一页)。",
6
6
  "maturityCriteria": {
7
7
  "alpha": "缺 demo.html 或缺 05-audit 下的专属门禁 ⇒ 还没被机器验证过,不要用",
@@ -645,7 +645,8 @@
645
645
  "list"
646
646
  ],
647
647
  "attrs": [
648
- "hidden"
648
+ "hidden",
649
+ "live"
649
650
  ]
650
651
  },
651
652
  "usesOtherNamespaces": [],
@@ -1272,7 +1273,9 @@
1272
1273
  "roles": [],
1273
1274
  "attrs": [
1274
1275
  "busy",
1275
- "disabled"
1276
+ "disabled",
1277
+ "hidden",
1278
+ "label"
1276
1279
  ]
1277
1280
  },
1278
1281
  "usesOtherNamespaces": [],
@@ -1 +1 @@
1
- .choice {position:relative;display:flex;align-items:center;min-height:44px;cursor:pointer;user-select:none;}.choice__label {display:flex;align-items:center;gap:var(--sp-2);cursor:pointer;min-height:44px;flex:1;min-width:0;}.choice--radio .choice__label {border-radius:var(--r-sm);}.choice__input {position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer;z-index:1;}.choice__mark {position:relative;flex:none;display:grid;place-items:center;width:20px;height:20px;background:var(--surface);border:1px solid var(--border-control);transition:background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);}@media (hover:hover) and (pointer:fine) {.choice:hover .choice__mark {border-color:var(--text-tertiary);}}.choice__input:focus-visible ~ .choice__label .choice__mark {outline:2px solid var(--accent);outline-offset:2px;}.choice__mark::after {content:"";position:absolute;width:5px;height:9px;border:solid var(--text-on-accent);border-width:0 2px 2px 0;transform:rotate(45deg) scale(0);transform-origin:center;transition:transform var(--dur-fast) var(--ease-out);}.choice__input:checked ~ .choice__label .choice__mark {background:var(--accent);border-color:var(--accent);}.choice__input:checked ~ .choice__label .choice__mark::after {transform:rotate(45deg) scale(1);}.choice__input:disabled {cursor:not-allowed;}.choice__input:disabled ~ .choice__label .choice__mark, .choice__input:disabled ~ .choice__label {background:var(--surface-sunken);border-color:var(--border-decor);}.choice__input:disabled ~ .choice__label {color:var(--text-tertiary);cursor:not-allowed;}.choice__label {font-size:var(--fs-sm);line-height:var(--lh-base);color:var(--text-primary);}.choice__hint {font-size:var(--fs-xs);color:var(--text-tertiary);line-height:1.5;}.choice--checkbox .choice__mark {border-radius:5px;}.choice--checkbox .choice__input:indeterminate ~ .choice__label .choice__mark {background:var(--surface);border-color:var(--accent);}.choice--checkbox .choice__input:indeterminate ~ .choice__label .choice__mark::after {width:10px;height:2px;border:none;background:var(--accent);border-radius:1px;transform:none;}.choice--radio .choice__mark {border-radius:var(--r-pill);}.choice--radio .choice__mark::after {width:8px;height:8px;border:none;background:var(--text-on-accent);border-radius:var(--r-pill);transform:scale(0);}.choice--radio .choice__input:checked ~ .choice__label .choice__mark {background:var(--accent);border-color:var(--accent);}.choice--radio .choice__input:checked ~ .choice__label .choice__mark::after {transform:scale(1);}.choice__group {border:none;margin:0;padding:0;min-width:0;}.choice__legend {padding:0;margin-bottom:var(--sp-2);font-size:var(--fs-sm);font-weight:500;color:var(--text-primary);}.choice__options {display:flex;flex-direction:column;gap:var(--sp-1);}.select {position:relative;display:inline-flex;align-items:center;width:100%;}.select__control {width:100%;height:44px;padding:0 40px 0 var(--sp-3);font-family:inherit;font-size:16px;line-height:1.5;color:var(--text-primary);background:var(--surface);border:1px solid var(--border-control);border-radius:var(--r-sm);appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer;transition:border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);}.select__control::-ms-expand {display:none;}.select__control::-webkit-calendar-picker-indicator {opacity:0;}@media (hover:hover) and (pointer:fine) {.select__control:hover:not(:disabled) {border-color:var(--text-tertiary);}}.select__control:focus {outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);}.select__control:disabled {background:var(--surface-sunken);color:var(--text-tertiary);border-color:var(--border-decor);cursor:not-allowed;}.select__arrow {position:absolute;inset-inline-end:var(--sp-3);pointer-events:none;color:var(--text-tertiary);display:grid;place-items:center;}.select__control--auto {height:auto;min-height:44px;padding-block:var(--sp-2);}@media (prefers-reduced-motion:reduce) {.choice__mark::after {transition:none;}}@media (forced-colors:active) {.choice--radio .choice__mark::after {background:CanvasText;}.choice--checkbox .choice__input:indeterminate ~ .choice__label .choice__mark::after {background:CanvasText;}}
1
+ .choice {position:relative;display:flex;align-items:center;min-height:44px;cursor:pointer;user-select:none;flex-wrap:wrap;}.choice__label {display:flex;align-items:center;gap:var(--sp-2);cursor:pointer;min-height:44px;flex:1;min-width:0;}.choice--radio .choice__label {border-radius:var(--r-sm);}.choice__input {position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer;z-index:1;}.choice__mark {position:relative;flex:none;display:grid;place-items:center;width:20px;height:20px;background:var(--surface);border:1px solid var(--border-control);transition:background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);}@media (hover:hover) and (pointer:fine) {.choice:hover .choice__mark {border-color:var(--text-tertiary);}}.choice__input:focus-visible ~ .choice__label .choice__mark {outline:2px solid var(--accent);outline-offset:2px;}.choice__mark::after {content:"";position:absolute;width:5px;height:9px;border:solid var(--text-on-accent);border-width:0 2px 2px 0;transform:rotate(45deg) scale(0);transform-origin:center;transition:transform var(--dur-fast) var(--ease-out);}.choice__input:checked ~ .choice__label .choice__mark {background:var(--accent);border-color:var(--accent);}.choice__input:checked ~ .choice__label .choice__mark::after {transform:rotate(45deg) scale(1);}.choice__input:disabled {cursor:not-allowed;}.choice__input:disabled ~ .choice__label .choice__mark, .choice__input:disabled ~ .choice__label {background:var(--surface-sunken);border-color:var(--border-decor);}.choice__input:disabled ~ .choice__label {color:var(--text-tertiary);cursor:not-allowed;}.choice__label {font-size:var(--fs-sm);line-height:var(--lh-base);color:var(--text-primary);}.choice__hint {font-size:var(--fs-xs);color:var(--text-tertiary);line-height:1.5;flex:1 0 100%;margin-inline-start:calc(20px + var(--sp-2));}.choice--checkbox .choice__mark {border-radius:5px;}.choice--checkbox .choice__input:indeterminate ~ .choice__label .choice__mark {background:var(--surface);border-color:var(--accent);}.choice--checkbox .choice__input:indeterminate ~ .choice__label .choice__mark::after {width:10px;height:2px;border:none;background:var(--accent);border-radius:1px;transform:none;}.choice--radio .choice__mark {border-radius:var(--r-pill);}.choice--radio .choice__mark::after {width:8px;height:8px;border:none;background:var(--text-on-accent);border-radius:var(--r-pill);transform:scale(0);}.choice--radio .choice__input:checked ~ .choice__label .choice__mark {background:var(--accent);border-color:var(--accent);}.choice--radio .choice__input:checked ~ .choice__label .choice__mark::after {transform:scale(1);}.choice__group {border:none;margin:0;padding:0;min-width:0;}.choice__legend {padding:0;margin-bottom:var(--sp-2);font-size:var(--fs-sm);font-weight:500;color:var(--text-primary);}.choice__options {display:flex;flex-direction:column;gap:var(--sp-1);}.select {position:relative;display:inline-flex;align-items:center;width:100%;}.select__control {width:100%;height:44px;padding:0 40px 0 var(--sp-3);font-family:inherit;font-size:16px;line-height:1.5;color:var(--text-primary);background:var(--surface);border:1px solid var(--border-control);border-radius:var(--r-sm);appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer;transition:border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);}.select__control::-ms-expand {display:none;}.select__control::-webkit-calendar-picker-indicator {opacity:0;}@media (hover:hover) and (pointer:fine) {.select__control:hover:not(:disabled) {border-color:var(--text-tertiary);}}.select__control:focus {outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);}.select__control:disabled {background:var(--surface-sunken);color:var(--text-tertiary);border-color:var(--border-decor);cursor:not-allowed;}.select__arrow {position:absolute;inset-inline-end:var(--sp-3);pointer-events:none;color:var(--text-tertiary);display:grid;place-items:center;}.select__control--auto {height:auto;min-height:44px;padding-block:var(--sp-2);}@media (prefers-reduced-motion:reduce) {.choice__mark::after {transition:none;}}@media (forced-colors:active) {.choice--radio .choice__mark::after {background:CanvasText;}.choice--checkbox .choice__input:indeterminate ~ .choice__label .choice__mark::after {background:CanvasText;}}