@icen.ai/ui 0.5.0 → 0.8.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.
Files changed (138) hide show
  1. package/AGENTS.md +256 -0
  2. package/README.md +47 -13
  3. package/dist/base.css +31 -4
  4. package/dist/behaviors/accordion.d.mts +4 -0
  5. package/dist/behaviors/accordion.mjs +4 -3
  6. package/dist/behaviors/ai-chat.d.mts +57 -0
  7. package/dist/behaviors/ai-chat.mjs +557 -0
  8. package/dist/behaviors/ai-composer.d.mts +112 -0
  9. package/dist/behaviors/ai-composer.mjs +2032 -0
  10. package/dist/behaviors/ai-core.d.mts +151 -0
  11. package/dist/behaviors/ai-core.mjs +491 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +282 -0
  14. package/dist/behaviors/ai-panel.d.mts +98 -0
  15. package/dist/behaviors/ai-panel.mjs +1012 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1055 -0
  18. package/dist/behaviors/ai-tool.d.mts +18 -0
  19. package/dist/behaviors/ai-tool.mjs +2125 -0
  20. package/dist/behaviors/ai-tools.d.mts +81 -0
  21. package/dist/behaviors/ai-tools.mjs +1658 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +49 -32
  24. package/dist/behaviors/carousel.d.mts +4 -0
  25. package/dist/behaviors/carousel.mjs +34 -16
  26. package/dist/behaviors/charts.d.mts +250 -0
  27. package/dist/behaviors/charts.mjs +1301 -448
  28. package/dist/behaviors/command-palette.d.mts +8 -0
  29. package/dist/behaviors/command-palette.mjs +24 -12
  30. package/dist/behaviors/context-menu.d.mts +36 -0
  31. package/dist/behaviors/context-menu.mjs +6 -2
  32. package/dist/behaviors/copy.d.mts +4 -0
  33. package/dist/behaviors/copy.mjs +7 -6
  34. package/dist/behaviors/datatable.d.mts +78 -0
  35. package/dist/behaviors/datatable.mjs +54 -11
  36. package/dist/behaviors/date-picker.d.mts +4 -0
  37. package/dist/behaviors/date-picker.mjs +104 -36
  38. package/dist/behaviors/dropdown.d.mts +4 -0
  39. package/dist/behaviors/dropdown.mjs +7 -3
  40. package/dist/behaviors/input.d.mts +4 -0
  41. package/dist/behaviors/input.mjs +29 -22
  42. package/dist/behaviors/modal.d.mts +11 -0
  43. package/dist/behaviors/modal.mjs +11 -6
  44. package/dist/behaviors/nav.d.mts +7 -0
  45. package/dist/behaviors/nav.mjs +25 -9
  46. package/dist/behaviors/notification.d.mts +172 -0
  47. package/dist/behaviors/notification.mjs +243 -89
  48. package/dist/behaviors/popover.d.mts +46 -0
  49. package/dist/behaviors/popover.mjs +2 -1
  50. package/dist/behaviors/select.d.mts +4 -0
  51. package/dist/behaviors/select.mjs +131 -26
  52. package/dist/behaviors/sidebar.d.mts +4 -0
  53. package/dist/behaviors/sidebar.mjs +4 -3
  54. package/dist/behaviors/slider.d.mts +4 -0
  55. package/dist/behaviors/slider.mjs +4 -3
  56. package/dist/behaviors/split-pane.d.mts +4 -0
  57. package/dist/behaviors/split-pane.mjs +13 -13
  58. package/dist/behaviors/tabs.d.mts +8 -0
  59. package/dist/behaviors/tabs.mjs +44 -19
  60. package/dist/behaviors/tag-input.d.mts +4 -0
  61. package/dist/behaviors/tag-input.mjs +12 -3
  62. package/dist/behaviors/theme.d.mts +25 -0
  63. package/dist/behaviors/theme.mjs +3 -1
  64. package/dist/behaviors/toast.d.mts +26 -0
  65. package/dist/behaviors/toast.mjs +24 -18
  66. package/dist/behaviors/tree.d.mts +4 -0
  67. package/dist/behaviors/tree.mjs +4 -3
  68. package/dist/behaviors/upload.d.mts +4 -0
  69. package/dist/behaviors/upload.mjs +63 -26
  70. package/dist/components/ai-chat.css +1037 -0
  71. package/dist/components/ai-chat.mjs +3 -0
  72. package/dist/components/ai-composer.mjs +3 -0
  73. package/dist/components/ai-context.mjs +2 -0
  74. package/dist/components/ai-diff.css +311 -0
  75. package/dist/components/ai-diff.mjs +3 -0
  76. package/dist/components/ai-files.mjs +3 -0
  77. package/dist/components/ai-message.mjs +3 -0
  78. package/dist/components/ai-panel.css +682 -0
  79. package/dist/components/ai-reasoning.mjs +3 -0
  80. package/dist/components/ai-subagent.mjs +2 -0
  81. package/dist/components/ai-todo.mjs +2 -0
  82. package/dist/components/ai-tool-call.mjs +2 -0
  83. package/dist/components/ai-tool.css +532 -0
  84. package/dist/components/ai-usage.mjs +3 -0
  85. package/dist/components/badge.css +4 -8
  86. package/dist/components/breadcrumb.css +1 -5
  87. package/dist/components/btn.css +4 -7
  88. package/dist/components/card.css +3 -6
  89. package/dist/components/chart-bar.css +10 -0
  90. package/dist/components/chart-calendar.mjs +5 -0
  91. package/dist/components/chart-gauge.css +1 -1
  92. package/dist/components/chart-heatmap.css +40 -0
  93. package/dist/components/chart-scatter.css +24 -0
  94. package/dist/components/chart-scatter.mjs +4 -0
  95. package/dist/components/charts.css +86 -1
  96. package/dist/components/charts.mjs +1 -0
  97. package/dist/components/command-palette.css +2 -2
  98. package/dist/components/content.css +8 -9
  99. package/dist/components/copy.css +8 -8
  100. package/dist/components/datatable.css +4 -8
  101. package/dist/components/date-picker.css +3 -7
  102. package/dist/components/empty.css +0 -4
  103. package/dist/components/feedback.css +10 -17
  104. package/dist/components/form.css +13 -14
  105. package/dist/components/input.css +2 -2
  106. package/dist/components/layout.css +36 -36
  107. package/dist/components/media.css +12 -8
  108. package/dist/components/menu.css +2 -2
  109. package/dist/components/modal.css +1 -1
  110. package/dist/components/nav.css +11 -6
  111. package/dist/components/notification.css +28 -28
  112. package/dist/components/pagination.css +1 -1
  113. package/dist/components/panel.css +16 -16
  114. package/dist/components/pill.css +15 -15
  115. package/dist/components/segmented.css +5 -5
  116. package/dist/components/segmented.mjs +1 -0
  117. package/dist/components/select.css +12 -20
  118. package/dist/components/sidebar.css +1 -1
  119. package/dist/components/split-pane.css +0 -1
  120. package/dist/components/stat.css +14 -18
  121. package/dist/components/steps.css +2 -2
  122. package/dist/components/switch.css +8 -12
  123. package/dist/components/table.css +46 -47
  124. package/dist/components/tabs.css +35 -46
  125. package/dist/components/tag-input.css +4 -4
  126. package/dist/components/toast.css +16 -13
  127. package/dist/components/tooltip.css +2 -2
  128. package/dist/index.d.mts +37 -0
  129. package/dist/index.mjs +7890 -1199
  130. package/dist/registry.json +113 -4
  131. package/dist/retro-effects.css +21 -17
  132. package/dist/tokens/colors.css +4 -1
  133. package/dist/tokens/retro-effects.css +21 -17
  134. package/dist/tokens/style-profiles.css +21 -0
  135. package/dist/tokens.css +25 -1
  136. package/dist/ui.css +3168 -433
  137. package/docs/spec/ai-native.md +659 -0
  138. package/package.json +15 -5
package/dist/ui.css CHANGED
@@ -507,6 +507,8 @@
507
507
  --token-accent-glow: color-mix(in srgb, var(--token-accent) 8%, transparent);
508
508
  --token-line-dashed: color-mix(in srgb, var(--token-line-soft) 60%, transparent);
509
509
  --token-overlay-bg: color-mix(in srgb, var(--token-bg) 92%, transparent);
510
+ /* 睡莲粉 accent 上的文字色:深绿灰(白字在 #d59ab1 上对比度不达标) */
511
+ --token-on-accent: #26302b;
510
512
 
511
513
  --token-shadow-elevated: 0 1px 2px rgba(0,0,0,.18), 0 8px 22px rgba(0,0,0,.24);
512
514
  --token-shadow-overlay: 0 2px 5px rgba(0,0,0,.24), 0 14px 36px rgba(0,0,0,.32);
@@ -844,7 +846,8 @@
844
846
  --token-accent-glow: color-mix(in srgb, var(--token-accent) 6%, transparent);
845
847
  --token-line-dashed: color-mix(in srgb, var(--token-line-soft) 60%, transparent);
846
848
  --token-overlay-bg: color-mix(in srgb, var(--token-bg) 94%, transparent);
847
- --token-on-accent: #ffffff;
849
+ /* 松烟绿 accent 上的文字色:深墨(白字在 #7AA87E 上对比度不达标) */
850
+ --token-on-accent: #1a2018;
848
851
 
849
852
  --token-shadow-elevated: 0 1px 2px rgba(0,0,0,.2), 0 4px 14px rgba(0,0,0,.22);
850
853
  --token-shadow-overlay: 0 2px 4px rgba(0,0,0,.28), 0 10px 30px rgba(0,0,0,.34);
@@ -1312,13 +1315,20 @@
1312
1315
  --density-space-sm: 0.5rem;
1313
1316
  --density-space-md: 0.75rem;
1314
1317
  --density-space-lg: 1rem;
1318
+ --density-space-xl: 1.5rem;
1319
+ --density-space-2xl: 2rem;
1320
+ --density-space-3xl: 3rem;
1321
+ --density-space-4xl: 4rem;
1315
1322
  --density-input-height-sm: 28px;
1316
1323
  --density-input-height-md: 36px;
1317
1324
  --density-input-height-lg: 44px;
1325
+ --density-font-size-2xs: 10px;
1318
1326
  --density-font-size-xs: 11px;
1319
1327
  --density-font-size-sm: 12px;
1320
1328
  --density-font-size-md: 13px;
1321
1329
  --density-font-size-lg: 14px;
1330
+ --density-font-size-xl: 18px;
1331
+ --density-font-size-2xl: 22px;
1322
1332
 
1323
1333
  /* ── Motion 动效 ── */
1324
1334
  --duration-instant: 0ms;
@@ -1350,13 +1360,20 @@
1350
1360
  --density-space-sm: 0.5rem;
1351
1361
  --density-space-md: 0.75rem;
1352
1362
  --density-space-lg: 1rem;
1363
+ --density-space-xl: 1.5rem;
1364
+ --density-space-2xl: 2rem;
1365
+ --density-space-3xl: 3rem;
1366
+ --density-space-4xl: 4rem;
1353
1367
  --density-input-height-sm: 28px;
1354
1368
  --density-input-height-md: 36px;
1355
1369
  --density-input-height-lg: 44px;
1370
+ --density-font-size-2xs: 10px;
1356
1371
  --density-font-size-xs: 11px;
1357
1372
  --density-font-size-sm: 12px;
1358
1373
  --density-font-size-md: 13px;
1359
1374
  --density-font-size-lg: 14px;
1375
+ --density-font-size-xl: 18px;
1376
+ --density-font-size-2xl: 22px;
1360
1377
 
1361
1378
  /* ── Motion ── */
1362
1379
  --duration-fast: 150ms;
@@ -1385,13 +1402,20 @@
1385
1402
  --density-space-sm: 0.25rem;
1386
1403
  --density-space-md: 0.5rem;
1387
1404
  --density-space-lg: 0.75rem;
1405
+ --density-space-xl: 1rem;
1406
+ --density-space-2xl: 1.5rem;
1407
+ --density-space-3xl: 2rem;
1408
+ --density-space-4xl: 3rem;
1388
1409
  --density-input-height-sm: 24px;
1389
1410
  --density-input-height-md: 32px;
1390
1411
  --density-input-height-lg: 40px;
1412
+ --density-font-size-2xs: 9px;
1391
1413
  --density-font-size-xs: 9px;
1392
1414
  --density-font-size-sm: 10px;
1393
1415
  --density-font-size-md: 11px;
1394
1416
  --density-font-size-lg: 12px;
1417
+ --density-font-size-xl: 14px;
1418
+ --density-font-size-2xl: 16px;
1395
1419
 
1396
1420
  /* ── Motion:像素风瞬间切换 ── */
1397
1421
  --duration-fast: 0ms;
@@ -1854,10 +1878,11 @@ h1, h2, h3, h4, h5, h6 {
1854
1878
  * ════════════════════════════════════════════════════════════════════════
1855
1879
  * icen 组件契约(统一抽象)
1856
1880
  * ════════════════════════════════════════════════════════════════════════
1857
- * 所有 31 个组件遵循同一套抽象,由两层共同支撑:
1881
+ * 所有 51 个组件遵循同一套抽象,由两层共同支撑:
1858
1882
  *
1859
1883
  * ── 层 1:--icen-* 原语 token(所有组件自洽消费)
1860
- * --icen-ring / --icen-ring-soft / --icen-ring-error ← 聚焦环
1884
+ * --icen-ring / --icen-ring-soft / --icen-ring-error /
1885
+ * --icen-ring-warning / --icen-ring-inset ← 聚焦环
1861
1886
  * --icen-hover-border ← 悬停边框过渡
1862
1887
  * --icen-disabled-opacity ← 禁用透明度
1863
1888
  * --icen-press ← 按下形变
@@ -1903,6 +1928,7 @@ h1, h2, h3, h4, h5, h6 {
1903
1928
  * - 滚动条:贴 --token-scrollbar-*(retro profile 另有硬覆盖)
1904
1929
  * - 工具类:.mono / .dim / .faint / .sr-only / .skip-link
1905
1930
  * - z-index 标尺:--z-* (浮层一律 portal 到 body)
1931
+ * - 共享 keyframes:icen-pop-in(弹层进场)/ icen-spin(旋转)/ icen-skeleton-pulse(骨架脉冲)
1906
1932
  * - 交互基元 class:.pressable / .control / .field / .lift / .surface-elevated / .focus-ring / .motion
1907
1933
  * - 状态约定:[aria-disabled] / [aria-busy] / [data-loading] / [data-disabled] / [data-invalid]
1908
1934
  */
@@ -1912,7 +1938,7 @@ h1, h2, h3, h4, h5, h6 {
1912
1938
  ───────────────────────────────────────────────────────────────
1913
1939
  所有组件消费的"原语级"变量:聚焦环、悬停、按下、禁用、过渡。
1914
1940
  风格 profile(如 .style-retro)只需覆盖这里的少数变量,
1915
- 即可让 31 个组件整体换肤;组件自身无需感知风格切换。
1941
+ 即可让 51 个组件整体换肤;组件自身无需感知风格切换。
1916
1942
  ══════════════════════════════════════════════════════════════ */
1917
1943
  :root {
1918
1944
  /* ── 聚焦环(focus-visible) ── */
@@ -1920,6 +1946,9 @@ h1, h2, h3, h4, h5, h6 {
1920
1946
  --icen-ring-soft: 0 0 0 3px color-mix(in srgb, var(--token-accent) 16%, transparent);
1921
1947
  --icen-ring-error: 0 0 0 2px var(--token-bg), 0 0 0 4px color-mix(in srgb, var(--token-error) 50%, transparent);
1922
1948
  --icen-ring-success: 0 0 0 2px var(--token-bg), 0 0 0 4px color-mix(in srgb, var(--token-success) 50%, transparent);
1949
+ --icen-ring-warning: 0 0 0 2px var(--token-bg), 0 0 0 4px color-mix(in srgb, var(--token-warning) 50%, transparent);
1950
+ /* inset 聚焦环:空间受限的网格/表头单元格用内阴影聚焦(2px 内描边 + 3px 内光晕) */
1951
+ --icen-ring-inset: inset 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent), inset 0 0 0 5px color-mix(in srgb, var(--token-accent) 14%, transparent);
1923
1952
 
1924
1953
  /* ── 悬停 / 按下 / 禁用 ── */
1925
1954
  --icen-hover-border: color-mix(in srgb, var(--token-accent) 30%, var(--token-line));
@@ -2098,7 +2127,8 @@ html {
2098
2127
  banner → 全屏横幅(紧急公告,高于 toast)
2099
2128
  dialog → 模态对话框 + backdrop
2100
2129
  popover → 弹出层/下拉菜单(高于 dialog 因为常常 portal 出去)
2101
- tooltip → 工具提示(永远在最上)
2130
+ tooltip → 工具提示
2131
+ fx → 效果层(retro 扫描线/噪点等,永远在最上)
2102
2132
  ══════════════════════════════════════════════════════════════ */
2103
2133
  :root {
2104
2134
  --z-base: 0;
@@ -2110,6 +2140,27 @@ html {
2110
2140
  --z-dialog: 500;
2111
2141
  --z-popover: 620;
2112
2142
  --z-tooltip: 700;
2143
+ --z-fx: 1000;
2144
+ }
2145
+
2146
+ /* ══════════════════════════════════════════════════════════════
2147
+ 共享 keyframes(组件统一引用,不再各自重复定义)
2148
+ icen-pop-in → 弹层进场(scale .96 + translateY -4px)
2149
+ icen-spin → 旋转(spinner / 加载态)
2150
+ icen-skeleton-pulse → 骨架脉冲
2151
+ 注:menu.css / popover.css 保留自己的 scale(.95)±8px 四方向版
2152
+ (方向语义不同:按 data-side 反向位移),不并入 icen-pop-in。
2153
+ ══════════════════════════════════════════════════════════════ */
2154
+ @keyframes icen-pop-in {
2155
+ from { opacity: 0; transform: scale(0.96) translateY(-4px); }
2156
+ to { opacity: 1; transform: scale(1) translateY(0); }
2157
+ }
2158
+ @keyframes icen-spin {
2159
+ to { transform: rotate(360deg); }
2160
+ }
2161
+ @keyframes icen-skeleton-pulse {
2162
+ 0%, 100% { opacity: 1; }
2163
+ 50% { opacity: 0.5; }
2113
2164
  }
2114
2165
 
2115
2166
  /* ══════════════════════════════════════════════════════════════
@@ -2260,58 +2311,2632 @@ html {
2260
2311
  outline: none;
2261
2312
  box-shadow: var(--icen-ring);
2262
2313
  }
2263
-
2264
- /* ── .motion — 显式动画工具类(仅在 prefers-reduced-motion: no-preference 时生效) */
2265
- @media (prefers-reduced-motion: no-preference) {
2266
- .motion { transition: all var(--icen-tx-normal); }
2314
+
2315
+ /* ── .motion — 显式动画工具类(仅在 prefers-reduced-motion: no-preference 时生效) */
2316
+ @media (prefers-reduced-motion: no-preference) {
2317
+ .motion { transition: all var(--icen-tx-normal); }
2318
+ }
2319
+
2320
+ /* ══════════════════════════════════════════════════════════════
2321
+ 动效与无障碍兜底
2322
+ ══════════════════════════════════════════════════════════════ */
2323
+
2324
+ /* reduced-motion:所有动画/过渡压缩到 ~0ms(保留可访问性最小可视信号) */
2325
+ @media (prefers-reduced-motion: reduce) {
2326
+ *, *::before, *::after {
2327
+ animation-duration: 0.01ms !important;
2328
+ animation-iteration-count: 1 !important;
2329
+ transition-duration: 0.01ms !important;
2330
+ scroll-behavior: auto !important;
2331
+ }
2332
+ }
2333
+
2334
+ /* 触屏设备(无精细指针):关闭 hover 抬升、压低 pressable scale */
2335
+ @media (pointer: coarse) {
2336
+ .lift:hover { transform: none; }
2337
+ .pressable:active { transform: translateY(0) scale(0.99); }
2338
+ }
2339
+
2340
+ /* 高对比度模式(Windows High Contrast / 强制 colors) */
2341
+ @media (forced-colors: active) {
2342
+ .pressable, .control, .lift, .surface-elevated, .focus-ring:focus-visible {
2343
+ forced-color-adjust: none;
2344
+ }
2345
+ .focus-ring:focus-visible,
2346
+ .pressable:focus-visible {
2347
+ outline: 2px solid Highlight;
2348
+ outline-offset: 2px;
2349
+ }
2350
+ }
2351
+
2352
+ /* 打印:去掉阴影/背景/动效,节约墨水并避免打印截断 */
2353
+ @media print {
2354
+ *, *::before, *::after {
2355
+ background: transparent !important;
2356
+ color: #000 !important;
2357
+ box-shadow: none !important;
2358
+ text-shadow: none !important;
2359
+ }
2360
+ body { font-size: 12pt; line-height: 1.4; }
2361
+ a, a:visited { text-decoration: underline; }
2362
+ a[href]::after { content: ' (' attr(href) ')'; font-size: 90%; }
2363
+ pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
2364
+ h1, h2, h3 { page-break-after: avoid; }
2365
+ .pressable, .lift, .surface-elevated { box-shadow: none !important; transform: none !important; }
2366
+ }
2367
+
2368
+
2369
+ /* ── components/ai-chat.css ── */
2370
+ /*
2371
+ * @icen.ai/ui — Component: ai-chat(AI 原生组件族:ai-chat / ai-message / ai-reasoning / ai-composer)
2372
+ *
2373
+ * 完整契约:
2374
+ * <div class="ai-chat" data-density="normal"> ← verbose|normal|summary 三档
2375
+ * <div class="ai-chat-scroll">
2376
+ * <div class="ai-msg ai-msg--user|ai-msg--assistant|ai-msg--system|ai-msg--tool">
2377
+ * <span class="ai-msg-avatar"></span>
2378
+ * <div class="ai-msg-body">…消费方自行渲染 markdown…</div>
2379
+ * <div class="ai-msg-actions"><button data-ai-msg-action="copy|retry">…</button></div>
2380
+ * <div class="ai-msg-meta">12:04 · 1.2k tok</div>
2381
+ * </div>
2382
+ * </div>
2383
+ * <button class="ai-chat-jump" hidden>回到底部 · N 条新消息</button>
2384
+ * </div>
2385
+ *
2386
+ * <div class="ai-reasoning [.is-streaming|.is-done]">
2387
+ * <button class="ai-reasoning-head" aria-expanded="false">
2388
+ * <span class="ai-reasoning-label">思考过程</span><span class="ai-reasoning-time">3s</span>
2389
+ * </button>
2390
+ * <div class="ai-reasoning-body" hidden>…</div>
2391
+ * </div>
2392
+ *
2393
+ * <div class="ai-composer" data-ai-composer [.is-running]>
2394
+ * <div class="ai-composer-queue" hidden><span class="ai-composer-chip">
2395
+ * <span class="ai-composer-chip-text">…</span>
2396
+ * <button class="ai-composer-chip-remove" aria-label="移除">×</button>
2397
+ * </span></div>
2398
+ * <div class="ai-composer-refs" hidden><!-- @ 引用 chips,结构同上(v2 setComposerRefs) --></div>
2399
+ * <div class="ai-composer-attach" hidden><!-- 附件 chips,结构同上 --></div>
2400
+ * <div class="ai-composer-box control">
2401
+ * <textarea class="ai-composer-input" rows="1" placeholder="…"></textarea>
2402
+ * <div class="ai-composer-actions"><!-- v1 旧结构(无 toolbar 配置时保留): -->
2403
+ * <button class="ai-composer-btn" data-ai-attach aria-label="附件"></button>
2404
+ * <button class="ai-composer-send" data-ai-send aria-label="发送"></button> ← 运行态 .is-stop
2405
+ * </div>
2406
+ * <div class="ai-composer-toolbar"><!-- v2 工具条(setComposerModels/Usage 配置时 JS 装配):
2407
+ * <button class="ai-composer-model" data-ai-model-open>provider 图标/首字母章 + 模型名 + chevron</button>
2408
+ * <span class="ai-composer-spacer"></span>
2409
+ * <span class="ai-composer-usage" data-ai-usage></span> ← 用量环挂载点(renderAiUsageRing)
2410
+ * <button class="ai-composer-btn" data-ai-attach></button>
2411
+ * <button class="ai-composer-send" data-ai-send></button> ← 运行态 .is-stop
2412
+ * </div>
2413
+ * </div>
2414
+ * </div>
2415
+ * 弹层 .ai-composer-popup(portal body,JS 动态创建):模型切换 / 斜杠命令 / @ 引用
2416
+ * 三模式共用基座——.ai-composer-popup-search 搜索框、.ai-composer-popup-list(listbox)
2417
+ * > .ai-composer-popup-group > .ai-composer-popup-group-label + .ai-composer-popup-option(role=option),
2418
+ * .ai-composer-popup-empty 空态、.ai-composer-popup-foot 底部提示。
2419
+ *
2420
+ * 配套 behaviors:behaviors/ai-chat.ts(initAiChat / createAiStream)
2421
+ * + behaviors/ai-composer.ts(initAiComposer / setComposerRunning
2422
+ * + v2:setComposerModels / setComposerCommands / setComposerRefs / setComposerUsage)。
2423
+ * 状态机 7 态 .is-* 中的 streaming/done 在本文件消费;running 脉冲点等条目态见 ai-tool.css(.ai-item 基元)。
2424
+ * data-density:summary 档隐藏 meta、默认折叠 reasoning(纯 CSS);
2425
+ * tool/system 类次要消息在 summary 档压淡。verbose 与 normal 目前同视觉(透明度两档生效)。
2426
+ * 流式光标复用 tokens 的 blink keyframes(.animate-blink 同款);reasoning shimmer 时长走
2427
+ * --ai-shimmer-duration 局部变量(默认值 1.6s)。
2428
+ */
2429
+
2430
+ /* ══════════════ ai-chat(滚动容器 + 钉底) ══════════════ */
2431
+
2432
+ /* 输出容器宽度自稳定契约:即使宿主是 flex/grid 居中上下文(shrink-wrap 陷阱),
2433
+ 也不随内容多少改变宽度——流式内容只允许纵向生长。
2434
+ (用 align-self 而非 width:100%——width 在 shrink-wrap 父级下会塌成 min-content) */
2435
+ .ai-chat,
2436
+ .ai-reasoning,
2437
+ .ai-composer {
2438
+ align-self: stretch;
2439
+ }
2440
+
2441
+ .ai-chat {
2442
+ position: relative;
2443
+ display: flex;
2444
+ flex-direction: column;
2445
+ height: 100%;
2446
+ min-height: 0;
2447
+ gap: var(--density-space-sm);
2448
+ }
2449
+
2450
+ .ai-chat-scroll {
2451
+ flex: 1;
2452
+ min-height: 0;
2453
+ overflow-y: auto;
2454
+ display: flex;
2455
+ flex-direction: column;
2456
+ gap: var(--density-space-md);
2457
+ padding: var(--density-space-xs) var(--density-space-2xs, 2px);
2458
+ scroll-behavior: smooth;
2459
+ }
2460
+
2461
+ /* 「回到底部 · N 条新消息」浮动钮(用户上滚暂停跟随时出现,initAiChat 驱动) */
2462
+ .ai-chat-jump {
2463
+ position: absolute;
2464
+ left: 50%;
2465
+ bottom: var(--density-space-md);
2466
+ transform: translateX(-50%);
2467
+ z-index: var(--z-raised);
2468
+ display: inline-flex;
2469
+ align-items: center;
2470
+ gap: var(--density-space-xs);
2471
+ padding: var(--density-space-xs) var(--density-space-md);
2472
+ border: 1px solid var(--token-line-soft);
2473
+ border-radius: var(--radius-full);
2474
+ background: var(--token-surface);
2475
+ box-shadow: var(--token-shadow-card);
2476
+ color: var(--token-text-muted);
2477
+ font-family: var(--font-mono);
2478
+ font-size: var(--density-font-size-xs);
2479
+ letter-spacing: 0.02em;
2480
+ white-space: nowrap;
2481
+ cursor: pointer;
2482
+ animation: icen-pop-in var(--ai-jump-enter-duration, var(--duration-normal)) var(--ease-out);
2483
+ transition:
2484
+ color var(--icen-tx-fast),
2485
+ border-color var(--icen-tx-fast),
2486
+ box-shadow var(--icen-tx-fast);
2487
+ }
2488
+ .ai-chat-jump:hover {
2489
+ color: var(--token-accent);
2490
+ border-color: var(--icen-hover-border);
2491
+ }
2492
+ .ai-chat-jump:focus-visible {
2493
+ outline: none;
2494
+ box-shadow: var(--icen-ring);
2495
+ }
2496
+ .ai-chat-jump[hidden] {
2497
+ display: none;
2498
+ }
2499
+
2500
+ /* ══════════════ ai-message ══════════════ */
2501
+
2502
+ .ai-msg {
2503
+ position: relative;
2504
+ display: grid;
2505
+ grid-template-columns: auto 1fr;
2506
+ gap: var(--density-space-sm);
2507
+ padding: var(--density-space-sm) var(--density-space-md);
2508
+ border-radius: var(--radius-md);
2509
+ animation: icen-pop-in var(--ai-msg-enter-duration, var(--duration-normal)) var(--ease-out);
2510
+ }
2511
+
2512
+ .ai-msg-avatar {
2513
+ width: 28px;
2514
+ height: 28px;
2515
+ flex-shrink: 0;
2516
+ display: inline-flex;
2517
+ align-items: center;
2518
+ justify-content: center;
2519
+ border-radius: var(--radius-sm);
2520
+ background: var(--token-fill-2);
2521
+ color: var(--token-text-muted);
2522
+ font-family: var(--font-mono);
2523
+ font-size: var(--density-font-size-2xs);
2524
+ letter-spacing: 0.04em;
2525
+ user-select: none;
2526
+ }
2527
+ .ai-msg--user .ai-msg-avatar {
2528
+ background: color-mix(in srgb, var(--token-accent) 14%, transparent);
2529
+ color: var(--token-accent);
2530
+ }
2531
+ .ai-msg--assistant .ai-msg-avatar {
2532
+ background: color-mix(in srgb, var(--token-success) 12%, transparent);
2533
+ color: var(--token-success);
2534
+ }
2535
+ .ai-msg--system .ai-msg-avatar {
2536
+ background: color-mix(in srgb, var(--token-warning) 12%, transparent);
2537
+ color: var(--token-warning);
2538
+ }
2539
+ .ai-msg--tool .ai-msg-avatar {
2540
+ background: color-mix(in srgb, var(--token-info) 12%, transparent);
2541
+ color: var(--token-info);
2542
+ }
2543
+
2544
+ .ai-msg-body {
2545
+ min-width: 0;
2546
+ min-height: 1.6em; /* 流式从空开始时占住一行高度,不塌 */
2547
+ font-size: var(--density-font-size-md);
2548
+ line-height: 1.6;
2549
+ white-space: pre-wrap;
2550
+ word-break: break-word;
2551
+ }
2552
+
2553
+ /* 流式光标(▍闪烁,复用 tokens 的 blink keyframes,同 .animate-blink) */
2554
+ .ai-msg-body.is-streaming::after,
2555
+ .ai-reasoning-body.is-streaming::after {
2556
+ content: '▍';
2557
+ margin-left: 1px;
2558
+ color: var(--token-accent);
2559
+ animation: blink 1s step-end infinite;
2560
+ }
2561
+
2562
+ /* 消息操作:hover / focus-within 才出现(键盘可达等价) */
2563
+ .ai-msg-actions {
2564
+ position: absolute;
2565
+ top: var(--density-space-xs);
2566
+ right: var(--density-space-xs);
2567
+ display: flex;
2568
+ gap: 2px;
2569
+ padding: 2px;
2570
+ border-radius: var(--radius-sm);
2571
+ background: var(--token-surface);
2572
+ border: 1px solid var(--token-line-soft);
2573
+ opacity: 0;
2574
+ visibility: hidden;
2575
+ transform: translateY(-2px);
2576
+ transition:
2577
+ opacity var(--icen-tx-fast),
2578
+ transform var(--icen-tx-fast),
2579
+ visibility var(--icen-tx-fast);
2580
+ }
2581
+ .ai-msg:hover .ai-msg-actions,
2582
+ .ai-msg:focus-within .ai-msg-actions {
2583
+ opacity: 1;
2584
+ visibility: visible;
2585
+ transform: translateY(0);
2586
+ }
2587
+ .ai-msg-actions [data-ai-msg-action] {
2588
+ display: inline-flex;
2589
+ align-items: center;
2590
+ justify-content: center;
2591
+ width: 22px;
2592
+ height: 22px;
2593
+ padding: 0;
2594
+ border: 0;
2595
+ border-radius: var(--radius-xs);
2596
+ background: transparent;
2597
+ color: var(--token-text-muted);
2598
+ cursor: pointer;
2599
+ transition:
2600
+ color var(--icen-tx-fast),
2601
+ background-color var(--icen-tx-fast);
2602
+ }
2603
+ .ai-msg-actions [data-ai-msg-action]:hover {
2604
+ color: var(--token-accent);
2605
+ background: var(--token-fill-2);
2606
+ }
2607
+ .ai-msg-actions [data-ai-msg-action]:focus-visible {
2608
+ outline: none;
2609
+ box-shadow: var(--icen-ring);
2610
+ }
2611
+ .ai-msg-actions [data-ai-msg-action] svg {
2612
+ width: 12px;
2613
+ height: 12px;
2614
+ }
2615
+
2616
+ /* meta:时间 · token 数(summary 档隐藏) */
2617
+ .ai-msg-meta {
2618
+ margin-top: 2px;
2619
+ font-family: var(--font-mono);
2620
+ font-size: var(--density-font-size-2xs);
2621
+ color: var(--token-text-faint);
2622
+ font-variant-numeric: tabular-nums;
2623
+ }
2624
+
2625
+ /* ── 多模态消息部件(renderAiMessage,spec §10)── */
2626
+
2627
+ .ai-msg-text {
2628
+ display: block;
2629
+ }
2630
+
2631
+ /* 图片 / 音频 / 视频:宽度自稳定(不超正文列),纵向生长 */
2632
+ .ai-msg-media {
2633
+ display: block;
2634
+ max-width: min(100%, 360px);
2635
+ margin: var(--density-space-xs) 0;
2636
+ border: 1px solid var(--token-line-soft);
2637
+ border-radius: var(--radius-md);
2638
+ background: var(--token-surface);
2639
+ }
2640
+ .ai-msg-media--image {
2641
+ max-height: 320px;
2642
+ object-fit: contain;
2643
+ }
2644
+ .ai-msg-media--audio {
2645
+ width: min(100%, 300px);
2646
+ height: 40px;
2647
+ }
2648
+ .ai-msg-media--video {
2649
+ max-width: min(100%, 480px);
2650
+ max-height: 300px;
2651
+ }
2652
+
2653
+ /* 文件 chip(含可选下载链接) */
2654
+ .ai-msg-file {
2655
+ display: inline-flex;
2656
+ align-items: center;
2657
+ gap: var(--density-space-xs);
2658
+ margin: 2px var(--density-space-xs) 2px 0;
2659
+ padding: 2px var(--density-space-sm);
2660
+ border: 1px solid var(--token-line);
2661
+ border-radius: var(--radius-sm);
2662
+ background: var(--token-surface);
2663
+ font-family: var(--font-mono);
2664
+ font-size: var(--density-font-size-xs);
2665
+ color: var(--token-text);
2666
+ }
2667
+ .ai-msg-file-link {
2668
+ color: var(--token-accent);
2669
+ text-decoration: none;
2670
+ }
2671
+ .ai-msg-file-link:hover {
2672
+ text-decoration: underline;
2673
+ }
2674
+
2675
+ /* MCP ResourceLink 等资源引用 */
2676
+ .ai-msg-link {
2677
+ color: var(--token-accent);
2678
+ }
2679
+
2680
+ /* assistant 消息的模型名标签 */
2681
+ .ai-msg-model {
2682
+ display: inline-block;
2683
+ margin-left: var(--density-space-xs);
2684
+ padding: 0 6px;
2685
+ border-radius: var(--radius-xs);
2686
+ background: var(--token-fill-1);
2687
+ color: var(--token-text-faint);
2688
+ font-family: var(--font-mono);
2689
+ font-size: var(--density-font-size-2xs);
2690
+ vertical-align: middle;
2691
+ }
2692
+
2693
+ /* ── 错误变体(renderAiMessage.setError / createAiStream.fail)── */
2694
+
2695
+ .ai-msg--error .ai-msg-body {
2696
+ border-left: 2px solid var(--token-error);
2697
+ padding-left: var(--density-space-sm);
2698
+ }
2699
+ .ai-msg-error-text {
2700
+ display: block;
2701
+ margin-top: var(--density-space-xs);
2702
+ color: var(--token-error);
2703
+ font-size: var(--density-font-size-xs);
2704
+ }
2705
+
2706
+ /* ══════════════ ai-reasoning ══════════════ */
2707
+
2708
+ .ai-reasoning {
2709
+ margin: var(--density-space-xs) 0;
2710
+ padding-left: var(--density-space-md);
2711
+ border-left: 2px solid var(--token-line-soft);
2712
+ }
2713
+
2714
+ .ai-reasoning-head {
2715
+ display: flex;
2716
+ align-items: center;
2717
+ gap: var(--density-space-sm);
2718
+ width: 100%;
2719
+ padding: var(--density-space-xs) 0;
2720
+ border: 0;
2721
+ background: none;
2722
+ color: var(--token-text-muted);
2723
+ font-family: var(--font-mono);
2724
+ font-size: var(--density-font-size-sm);
2725
+ letter-spacing: 0.02em;
2726
+ cursor: pointer;
2727
+ text-align: left;
2728
+ }
2729
+ .ai-reasoning-head:hover {
2730
+ color: var(--token-text);
2731
+ }
2732
+ .ai-reasoning-head:focus-visible {
2733
+ outline: none;
2734
+ box-shadow: var(--icen-ring);
2735
+ border-radius: var(--radius-xs);
2736
+ }
2737
+
2738
+ /* 折叠指示 chevron(CSS 三角,展开时旋转) */
2739
+ .ai-reasoning-head::before {
2740
+ content: '';
2741
+ flex-shrink: 0;
2742
+ width: 6px;
2743
+ height: 6px;
2744
+ border-right: 1.5px solid currentColor;
2745
+ border-bottom: 1.5px solid currentColor;
2746
+ transform: rotate(-45deg);
2747
+ transition: transform var(--icen-tx-fast);
2748
+ }
2749
+ .ai-reasoning:has(.ai-reasoning-head[aria-expanded='true']) .ai-reasoning-head::before {
2750
+ transform: rotate(45deg);
2751
+ }
2752
+
2753
+ .ai-reasoning-label {
2754
+ overflow: hidden;
2755
+ text-overflow: ellipsis;
2756
+ white-space: nowrap;
2757
+ }
2758
+
2759
+ .ai-reasoning-time {
2760
+ margin-left: auto;
2761
+ font-size: var(--density-font-size-2xs);
2762
+ color: var(--token-text-faint);
2763
+ font-variant-numeric: tabular-nums;
2764
+ }
2765
+
2766
+ /* 流式中:耗时位换成 shimmer「正在思考…」(时长走局部变量) */
2767
+ .ai-reasoning.is-streaming .ai-reasoning-time {
2768
+ display: none;
2769
+ }
2770
+ .ai-reasoning.is-streaming .ai-reasoning-head::after {
2771
+ content: '正在思考…';
2772
+ margin-left: auto;
2773
+ font-size: var(--density-font-size-2xs);
2774
+ background: linear-gradient(
2775
+ 90deg,
2776
+ var(--token-text-faint) 25%,
2777
+ var(--token-text) 50%,
2778
+ var(--token-text-faint) 75%
2779
+ );
2780
+ background-size: 200% 100%;
2781
+ -webkit-background-clip: text;
2782
+ background-clip: text;
2783
+ -webkit-text-fill-color: transparent;
2784
+ color: transparent;
2785
+ animation: ai-shimmer var(--ai-shimmer-duration, 1.6s) linear infinite;
2786
+ }
2787
+
2788
+ .ai-reasoning-body {
2789
+ padding: var(--density-space-xs) 0 var(--density-space-sm);
2790
+ color: var(--token-text-muted);
2791
+ font-size: var(--density-font-size-sm);
2792
+ line-height: 1.6;
2793
+ white-space: pre-wrap;
2794
+ word-break: break-word;
2795
+ }
2796
+ .ai-reasoning-body[hidden] {
2797
+ display: none;
2798
+ }
2799
+
2800
+ /* ══════════════ ai-composer ══════════════ */
2801
+
2802
+ .ai-composer {
2803
+ display: flex;
2804
+ flex-direction: column;
2805
+ gap: var(--density-space-xs);
2806
+ padding: var(--density-space-sm);
2807
+ border: 1px solid var(--token-line-soft);
2808
+ border-radius: var(--radius-lg);
2809
+ background: var(--token-surface);
2810
+ box-shadow: var(--token-shadow-card);
2811
+ }
2812
+ .ai-composer:focus-within {
2813
+ border-color: var(--icen-hover-border);
2814
+ }
2815
+
2816
+ /* 排队 / 引用 / 附件 chips 行 */
2817
+ .ai-composer-queue,
2818
+ .ai-composer-refs,
2819
+ .ai-composer-attach {
2820
+ display: flex;
2821
+ flex-wrap: wrap;
2822
+ gap: var(--density-space-xs);
2823
+ }
2824
+ .ai-composer-queue[hidden],
2825
+ .ai-composer-refs[hidden],
2826
+ .ai-composer-attach[hidden] {
2827
+ display: none;
2828
+ }
2829
+
2830
+ .ai-composer-chip {
2831
+ display: inline-flex;
2832
+ align-items: center;
2833
+ gap: 4px;
2834
+ max-width: 240px;
2835
+ padding: 2px var(--density-space-xs);
2836
+ border: 1px solid var(--token-line-soft);
2837
+ border-radius: var(--radius-full);
2838
+ background: var(--token-surface-strong);
2839
+ color: var(--token-text-muted);
2840
+ font-size: var(--density-font-size-xs);
2841
+ line-height: 1.4;
2842
+ }
2843
+ .ai-composer-chip-text {
2844
+ overflow: hidden;
2845
+ text-overflow: ellipsis;
2846
+ white-space: nowrap;
2847
+ }
2848
+ .ai-composer-chip-remove {
2849
+ display: inline-flex;
2850
+ align-items: center;
2851
+ justify-content: center;
2852
+ width: 14px;
2853
+ height: 14px;
2854
+ padding: 0;
2855
+ border: 0;
2856
+ border-radius: var(--radius-full);
2857
+ background: transparent;
2858
+ color: var(--token-text-faint);
2859
+ font-size: 12px;
2860
+ line-height: 1;
2861
+ cursor: pointer;
2862
+ transition:
2863
+ color var(--icen-tx-fast),
2864
+ background-color var(--icen-tx-fast);
2865
+ }
2866
+ .ai-composer-chip-remove:hover {
2867
+ color: var(--token-error);
2868
+ background: var(--token-error-muted);
2869
+ }
2870
+ .ai-composer-chip-remove:focus-visible {
2871
+ outline: none;
2872
+ box-shadow: var(--icen-ring);
2873
+ }
2874
+
2875
+ /* 输入框(.control 基元提供 hover/focus 边框与光晕);flex-wrap 让工具条独占下一行 */
2876
+ .ai-composer-box {
2877
+ display: flex;
2878
+ flex-wrap: wrap;
2879
+ align-items: flex-end;
2880
+ gap: var(--density-space-sm);
2881
+ padding: var(--density-space-xs) var(--density-space-sm);
2882
+ border: 1px solid var(--token-line-soft);
2883
+ border-radius: var(--radius-md);
2884
+ background: var(--token-panel);
2885
+ box-shadow: var(--token-shadow-inset);
2886
+ }
2887
+
2888
+ .ai-composer-input {
2889
+ flex: 1;
2890
+ min-width: 0;
2891
+ padding: var(--density-space-xs) 0;
2892
+ border: 0;
2893
+ outline: none;
2894
+ background: transparent;
2895
+ resize: none;
2896
+ font-family: inherit;
2897
+ font-size: var(--density-font-size-md);
2898
+ line-height: 1.5;
2899
+ max-height: 12em;
2900
+ overflow-y: auto;
2901
+ }
2902
+
2903
+ /* v1 旧结构按钮行(无 toolbar 配置时保留);v2 中按钮被移入工具条后此容器由 JS 移除 */
2904
+ .ai-composer-actions {
2905
+ display: flex;
2906
+ align-items: center;
2907
+ gap: var(--density-space-xs);
2908
+ flex-shrink: 0;
2909
+ }
2910
+
2911
+ /* ── v2 工具条(composer-box 内底部,业界主流形态)── */
2912
+
2913
+ .ai-composer-toolbar {
2914
+ flex: 0 0 100%;
2915
+ display: flex;
2916
+ align-items: center;
2917
+ gap: var(--density-space-xs);
2918
+ padding-top: var(--density-space-xs);
2919
+ border-top: 1px solid var(--token-line-soft);
2920
+ }
2921
+ .ai-composer-toolbar[hidden] {
2922
+ display: none;
2923
+ }
2924
+
2925
+ .ai-composer-spacer {
2926
+ flex: 1;
2927
+ }
2928
+
2929
+ /* 模型切换钮:provider 图标/首字母章 + 模型名 + chevron */
2930
+ .ai-composer-model {
2931
+ display: inline-flex;
2932
+ align-items: center;
2933
+ gap: 4px;
2934
+ max-width: 180px;
2935
+ height: 28px;
2936
+ padding: 0 var(--density-space-xs);
2937
+ border: 1px solid transparent;
2938
+ border-radius: var(--radius-sm);
2939
+ background: transparent;
2940
+ color: var(--token-text-muted);
2941
+ font-size: var(--density-font-size-xs);
2942
+ cursor: pointer;
2943
+ transition:
2944
+ color var(--icen-tx-fast),
2945
+ background-color var(--icen-tx-fast);
2946
+ }
2947
+ .ai-composer-model:hover {
2948
+ color: var(--token-text);
2949
+ background: var(--token-fill-2);
2950
+ }
2951
+ .ai-composer-model:focus-visible {
2952
+ outline: none;
2953
+ box-shadow: var(--icen-ring);
2954
+ }
2955
+ .ai-composer-model-icon,
2956
+ .ai-composer-model-provider-icon {
2957
+ display: inline-flex;
2958
+ align-items: center;
2959
+ justify-content: center;
2960
+ flex-shrink: 0;
2961
+ }
2962
+ .ai-composer-model-provider-icon svg {
2963
+ width: 14px;
2964
+ height: 14px;
2965
+ }
2966
+ .ai-composer-model-name {
2967
+ overflow: hidden;
2968
+ text-overflow: ellipsis;
2969
+ white-space: nowrap;
2970
+ }
2971
+ .ai-composer-model-chevron {
2972
+ display: inline-flex;
2973
+ align-items: center;
2974
+ justify-content: center;
2975
+ flex-shrink: 0;
2976
+ color: var(--token-text-faint);
2977
+ transition: transform var(--icen-tx-fast);
2978
+ }
2979
+ .ai-composer-model-chevron svg {
2980
+ width: 12px;
2981
+ height: 12px;
2982
+ }
2983
+ .ai-composer-model[aria-expanded='true'] .ai-composer-model-chevron {
2984
+ transform: rotate(180deg);
2985
+ }
2986
+
2987
+ /* provider 图标缺失时的首字母圆形章(token 色) */
2988
+ .ai-composer-model-badge {
2989
+ display: inline-flex;
2990
+ align-items: center;
2991
+ justify-content: center;
2992
+ width: 16px;
2993
+ height: 16px;
2994
+ border-radius: var(--radius-full);
2995
+ background: color-mix(in srgb, var(--token-accent) 15%, transparent);
2996
+ color: var(--token-accent);
2997
+ font-size: 10px;
2998
+ font-weight: 600;
2999
+ line-height: 1;
3000
+ user-select: none;
3001
+ }
3002
+ .ai-composer-model-icon .ai-composer-model-badge {
3003
+ width: 14px;
3004
+ height: 14px;
3005
+ font-size: 9px;
3006
+ }
3007
+
3008
+ /* 用量环挂载点(环本体 .ai-usage-ring 在 ai-panel.css) */
3009
+ .ai-composer-usage {
3010
+ display: inline-flex;
3011
+ align-items: center;
3012
+ flex-shrink: 0;
3013
+ }
3014
+ .ai-composer-usage:empty {
3015
+ display: none;
3016
+ }
3017
+
3018
+ /* ── v2 弹层(portal body,模型/命令/引用三模式共用基座;定位 inline 由 behavior 写入)── */
3019
+
3020
+ .ai-composer-popup {
3021
+ position: fixed;
3022
+ z-index: var(--z-popover);
3023
+ display: flex;
3024
+ flex-direction: column;
3025
+ padding: var(--density-space-2xs);
3026
+ border: 1px solid var(--token-line-soft);
3027
+ border-radius: var(--radius-md);
3028
+ background: var(--token-card-strong);
3029
+ box-shadow: var(--token-shadow-popover, var(--token-shadow-card));
3030
+ overflow: hidden;
3031
+ animation: icen-pop-in var(--ai-composer-popup-enter-duration, var(--duration-normal)) var(--ease-out);
3032
+ }
3033
+
3034
+ /* 搜索框(模型/引用过滤) */
3035
+ .ai-composer-popup-search {
3036
+ flex-shrink: 0;
3037
+ padding: var(--density-space-2xs);
3038
+ border-bottom: 1px solid var(--token-line-soft);
3039
+ }
3040
+ .ai-composer-popup-search-input {
3041
+ width: 100%;
3042
+ padding: var(--density-space-2xs) var(--density-space-xs);
3043
+ border: 1px solid var(--token-line-soft);
3044
+ border-radius: var(--radius-sm);
3045
+ background: var(--token-panel);
3046
+ color: var(--token-text);
3047
+ font-size: var(--density-font-size-sm);
3048
+ outline: none;
3049
+ transition: border-color var(--icen-tx-fast);
3050
+ }
3051
+ .ai-composer-popup-search-input:focus {
3052
+ border-color: var(--token-accent);
3053
+ }
3054
+ .ai-composer-popup-search-input::placeholder {
3055
+ color: var(--token-text-faint);
3056
+ }
3057
+
3058
+ /* 列表(max-height inline 由 behavior 按 computePopoverLayout 写入) */
3059
+ .ai-composer-popup-list {
3060
+ flex: 1;
3061
+ min-height: 0;
3062
+ overflow-y: auto;
3063
+ overscroll-behavior: contain;
3064
+ padding: var(--density-space-2xs);
3065
+ }
3066
+
3067
+ /* 分组(provider / ref kind) */
3068
+ .ai-composer-popup-group[hidden] {
3069
+ display: none;
3070
+ }
3071
+ .ai-composer-popup-group-label {
3072
+ display: flex;
3073
+ align-items: center;
3074
+ gap: 4px;
3075
+ padding: var(--density-space-xs) var(--density-space-xs) var(--density-space-2xs);
3076
+ font-size: var(--density-font-size-2xs);
3077
+ color: var(--token-text-faint);
3078
+ letter-spacing: 0.02em;
3079
+ }
3080
+ .ai-composer-popup-group-icon {
3081
+ display: inline-flex;
3082
+ align-items: center;
3083
+ justify-content: center;
3084
+ flex-shrink: 0;
3085
+ color: var(--token-text-muted);
3086
+ }
3087
+ .ai-composer-popup-group-icon svg {
3088
+ width: 12px;
3089
+ height: 12px;
3090
+ }
3091
+ .ai-composer-popup-group-icon .ai-composer-model-badge {
3092
+ width: 12px;
3093
+ height: 12px;
3094
+ font-size: 8px;
3095
+ }
3096
+
3097
+ /* 选项行(label + meta + check) */
3098
+ .ai-composer-popup-option {
3099
+ display: flex;
3100
+ align-items: center;
3101
+ gap: var(--density-space-xs);
3102
+ width: 100%;
3103
+ padding: var(--density-space-xs);
3104
+ border: 0;
3105
+ border-radius: var(--radius-sm);
3106
+ background: transparent;
3107
+ color: var(--token-text);
3108
+ font-size: var(--density-font-size-sm);
3109
+ text-align: left;
3110
+ cursor: pointer;
3111
+ transition: background-color var(--icen-tx-fast);
3112
+ }
3113
+ .ai-composer-popup-option:hover {
3114
+ background: var(--token-fill-2);
3115
+ }
3116
+ .ai-composer-popup-option.is-focused {
3117
+ background: var(--token-fill-2);
3118
+ }
3119
+ .ai-composer-popup-option:focus-visible {
3120
+ outline: none;
3121
+ box-shadow: var(--icen-ring);
3122
+ }
3123
+ .ai-composer-popup-option-icon {
3124
+ display: inline-flex;
3125
+ align-items: center;
3126
+ justify-content: center;
3127
+ flex-shrink: 0;
3128
+ color: var(--token-text-muted);
3129
+ }
3130
+ .ai-composer-popup-option-icon svg {
3131
+ width: 14px;
3132
+ height: 14px;
3133
+ }
3134
+ .ai-composer-popup-option-icon .ai-composer-model-badge {
3135
+ width: 14px;
3136
+ height: 14px;
3137
+ font-size: 9px;
3138
+ }
3139
+ .ai-composer-popup-option-label {
3140
+ overflow: hidden;
3141
+ text-overflow: ellipsis;
3142
+ white-space: nowrap;
3143
+ }
3144
+ .ai-composer-popup-option-sub {
3145
+ margin-left: 6px;
3146
+ font-size: var(--density-font-size-2xs);
3147
+ color: var(--token-text-faint);
3148
+ }
3149
+ .ai-composer-popup-option-meta {
3150
+ margin-left: auto;
3151
+ flex-shrink: 0;
3152
+ font-family: var(--font-mono);
3153
+ font-size: var(--density-font-size-2xs);
3154
+ color: var(--token-text-faint);
3155
+ font-variant-numeric: tabular-nums;
3156
+ }
3157
+ .ai-composer-popup-option-check {
3158
+ display: none;
3159
+ flex-shrink: 0;
3160
+ color: var(--token-accent);
3161
+ }
3162
+ .ai-composer-popup-option-check svg {
3163
+ width: 12px;
3164
+ height: 12px;
3165
+ }
3166
+ .ai-composer-popup-option[aria-selected='true'] .ai-composer-popup-option-check {
3167
+ display: inline-flex;
3168
+ }
3169
+
3170
+ /* 命令行:/name <argsHint> + 描述 */
3171
+ .ai-composer-popup-cmd {
3172
+ flex-direction: column;
3173
+ align-items: flex-start;
3174
+ gap: 1px;
3175
+ }
3176
+ .ai-composer-popup-cmd-line {
3177
+ display: flex;
3178
+ align-items: baseline;
3179
+ gap: 6px;
3180
+ max-width: 100%;
3181
+ }
3182
+ .ai-composer-popup-cmd-name {
3183
+ font-family: var(--font-mono);
3184
+ color: var(--token-accent);
3185
+ }
3186
+ .ai-composer-popup-cmd-args {
3187
+ font-family: var(--font-mono);
3188
+ font-size: var(--density-font-size-2xs);
3189
+ color: var(--token-text-faint);
3190
+ }
3191
+ .ai-composer-popup-cmd-desc {
3192
+ font-size: var(--density-font-size-2xs);
3193
+ color: var(--token-text-muted);
3194
+ }
3195
+
3196
+ /* 空态 */
3197
+ .ai-composer-popup-empty {
3198
+ flex-shrink: 0;
3199
+ padding: var(--density-space-md);
3200
+ text-align: center;
3201
+ font-size: var(--density-font-size-sm);
3202
+ color: var(--token-text-faint);
3203
+ }
3204
+ .ai-composer-popup-empty[hidden] {
3205
+ display: none;
3206
+ }
3207
+
3208
+ /* 底部提示(模型弹层:切换模型会使 prompt 缓存失效;cacheRead 占比高时 warning 色) */
3209
+ .ai-composer-popup-foot {
3210
+ flex-shrink: 0;
3211
+ display: flex;
3212
+ align-items: center;
3213
+ gap: 4px;
3214
+ padding: var(--density-space-xs);
3215
+ border-top: 1px solid var(--token-line-soft);
3216
+ font-size: var(--density-font-size-2xs);
3217
+ color: var(--token-text-faint);
3218
+ }
3219
+ .ai-composer-popup-foot svg {
3220
+ width: 12px;
3221
+ height: 12px;
3222
+ flex-shrink: 0;
3223
+ }
3224
+ .ai-composer-popup-foot.is-warn {
3225
+ color: var(--token-warning);
3226
+ }
3227
+
3228
+ /* ── 工具条按钮统一 28px 方形、svg 14px 居中(v1 旧结构按钮同样收敛)── */
3229
+
3230
+ .ai-composer-btn {
3231
+ display: inline-flex;
3232
+ align-items: center;
3233
+ justify-content: center;
3234
+ width: 28px;
3235
+ height: 28px;
3236
+ padding: 0;
3237
+ border: 1px solid transparent;
3238
+ border-radius: var(--radius-sm);
3239
+ background: transparent;
3240
+ color: var(--token-text-muted);
3241
+ cursor: pointer;
3242
+ transition:
3243
+ color var(--icen-tx-fast),
3244
+ background-color var(--icen-tx-fast);
3245
+ }
3246
+ .ai-composer-btn:hover {
3247
+ color: var(--token-text);
3248
+ background: var(--token-fill-2);
3249
+ }
3250
+ .ai-composer-btn:focus-visible {
3251
+ outline: none;
3252
+ box-shadow: var(--icen-ring);
3253
+ }
3254
+ .ai-composer-btn svg {
3255
+ width: 14px;
3256
+ height: 14px;
3257
+ }
3258
+
3259
+ /* 发送钮:accent 实底;运行态 .is-stop 变停止钮(中性底 + error 色方标,停语气但不整钮铺红) */
3260
+ .ai-composer-send {
3261
+ display: inline-flex;
3262
+ align-items: center;
3263
+ justify-content: center;
3264
+ width: 28px;
3265
+ height: 28px;
3266
+ padding: 0;
3267
+ border: 1px solid transparent;
3268
+ border-radius: var(--radius-sm);
3269
+ background: var(--token-accent);
3270
+ color: var(--token-on-accent);
3271
+ cursor: pointer;
3272
+ transition:
3273
+ background-color var(--icen-tx-fast),
3274
+ color var(--icen-tx-fast),
3275
+ border-color var(--icen-tx-fast);
3276
+ }
3277
+ .ai-composer-send:hover {
3278
+ background: var(--token-accent-deep, var(--token-accent));
3279
+ }
3280
+ .ai-composer-send:focus-visible {
3281
+ outline: none;
3282
+ box-shadow: var(--icen-ring);
3283
+ }
3284
+ .ai-composer-send svg {
3285
+ width: 14px;
3286
+ height: 14px;
3287
+ }
3288
+ .ai-composer-send .ai-composer-send-icon[hidden] {
3289
+ display: none;
3290
+ }
3291
+ .ai-composer-send.is-stop {
3292
+ background: var(--token-surface-strong);
3293
+ color: var(--token-error);
3294
+ border-color: color-mix(in srgb, var(--token-error) 35%, transparent);
3295
+ }
3296
+ .ai-composer-send.is-stop:hover {
3297
+ background: var(--token-error-muted);
3298
+ }
3299
+
3300
+ /* 运行态:输入台整体 busy 标记(aria-busy 等价,状态机语言) */
3301
+ .ai-composer.is-running .ai-composer-box {
3302
+ border-color: color-mix(in srgb, var(--token-accent) 30%, var(--token-line-soft));
3303
+ }
3304
+
3305
+ /* 拖放文件高亮(粘贴/拖放附件入口,spec §11) */
3306
+ .ai-composer-box.is-dragover {
3307
+ border-color: var(--token-accent);
3308
+ box-shadow: var(--icen-ring);
3309
+ }
3310
+
3311
+ /* ══════════════ data-density 三档(summary 纯 CSS 降噪) ══════════════ */
3312
+
3313
+ /* summary:隐藏 meta、默认折叠 reasoning(head 展开仍可看)、次要消息压淡 */
3314
+ .ai-chat[data-density='summary'] .ai-msg-meta {
3315
+ display: none;
3316
+ }
3317
+ .ai-chat[data-density='summary'] .ai-reasoning-body {
3318
+ display: none;
3319
+ }
3320
+ .ai-chat[data-density='summary'] .ai-reasoning:has(.ai-reasoning-head[aria-expanded='true']) .ai-reasoning-body {
3321
+ display: block;
3322
+ }
3323
+ .ai-chat[data-density='summary'] .ai-msg--tool,
3324
+ .ai-chat[data-density='summary'] .ai-msg--system {
3325
+ opacity: 0.72;
3326
+ }
3327
+
3328
+ /* verbose:当前与 normal 同视觉(透明度三档中两档生效;verbose 预留全显档位) */
3329
+
3330
+ /* ══════════════ keyframes(本文件局部) ══════════════ */
3331
+
3332
+ @keyframes ai-shimmer {
3333
+ from {
3334
+ background-position: 200% 0;
3335
+ }
3336
+ to {
3337
+ background-position: -200% 0;
3338
+ }
3339
+ }
3340
+
3341
+ /* ══════════════ 动效守卫 ══════════════ */
3342
+
3343
+ @media (prefers-reduced-motion: reduce) {
3344
+ .ai-msg,
3345
+ .ai-chat-jump,
3346
+ .ai-composer-popup {
3347
+ animation: none;
3348
+ }
3349
+ .ai-chat-jump,
3350
+ .ai-msg-actions,
3351
+ .ai-composer-chip-remove,
3352
+ .ai-composer-btn,
3353
+ .ai-composer-send,
3354
+ .ai-composer-model,
3355
+ .ai-composer-model-chevron,
3356
+ .ai-composer-popup-option,
3357
+ .ai-composer-popup-search-input,
3358
+ .ai-reasoning-head,
3359
+ .ai-reasoning-head::before {
3360
+ transition: none;
3361
+ }
3362
+ .ai-reasoning.is-streaming .ai-reasoning-head::after {
3363
+ animation: none;
3364
+ background: none;
3365
+ -webkit-text-fill-color: currentColor;
3366
+ color: var(--token-text-faint);
3367
+ }
3368
+ .ai-msg-body.is-streaming::after,
3369
+ .ai-reasoning-body.is-streaming::after {
3370
+ animation: none;
3371
+ }
3372
+ }
3373
+
3374
+ /* 触屏无 hover:消息操作常显(否则不可达) */
3375
+ @media (pointer: coarse) {
3376
+ .ai-msg-actions {
3377
+ opacity: 1;
3378
+ visibility: visible;
3379
+ transform: none;
3380
+ }
3381
+ }
3382
+
3383
+ /* 实时待办 chip(setComposerTodo:业界模式——plan 状态挂输入区,不进对话流) */
3384
+ .ai-composer-todo {
3385
+ display: inline-flex;
3386
+ align-items: center;
3387
+ gap: 5px;
3388
+ height: 28px;
3389
+ padding: 0 8px;
3390
+ border: 1px solid var(--token-line-soft);
3391
+ border-radius: var(--radius-full);
3392
+ background: var(--token-surface);
3393
+ color: var(--token-text-muted);
3394
+ font-family: var(--font-mono);
3395
+ font-size: var(--density-font-size-2xs);
3396
+ font-variant-numeric: tabular-nums;
3397
+ cursor: pointer;
3398
+ transition: color var(--icen-tx-fast), border-color var(--icen-tx-fast);
3399
+ }
3400
+ .ai-composer-todo svg { width: 13px; height: 13px; color: var(--token-accent); }
3401
+ .ai-composer-todo:hover { border-color: var(--icen-hover-border, var(--token-line)); color: var(--token-text); }
3402
+ .ai-composer-todo:focus-visible { outline: none; box-shadow: var(--icen-ring); }
3403
+ .ai-composer-todo.is-done svg { color: var(--token-success); }
3404
+ .ai-composer-todo.is-error svg { color: var(--token-warning); }
3405
+ .ai-composer-todo-pop { min-width: 240px; max-width: 320px; }
3406
+ .ai-composer-todo-pop .ai-todo { padding: 0; }
3407
+
3408
+
3409
+ /* ── components/ai-diff.css ── */
3410
+ /*
3411
+ * @icen.ai/ui — Component: ai-diff(AI 差异审阅 + 文件 chips,与 behaviors/ai-diff.ts 配套)
3412
+ *
3413
+ * 完整 DOM 契约(类名与规格 §4.6/§4.7 一致):
3414
+ * <div class="ai-diff">
3415
+ * <div class="ai-diff-file is-added|is-modified|is-deleted|is-renamed
3416
+ * [.is-accepted|.is-rejected]" data-path="src/foo.ts">
3417
+ * <div class="ai-diff-head" role="button" tabindex="0" aria-expanded="false">
3418
+ * <span class="ai-diff-path">src/foo.ts</span> <!-- renamed:旧路径 → 新路径 -->
3419
+ * <span class="ai-diff-stat"><b class="ai-diff-add">+12</b> <b class="ai-diff-del">−4</b></span>
3420
+ * <span class="ai-diff-actions">
3421
+ * <button type="button" data-ai-diff-accept>接受</button>
3422
+ * <button type="button" data-ai-diff-reject>拒绝</button>
3423
+ * </span>
3424
+ * <span class="ai-diff-verdict" hidden>已接受|已拒绝</span> <!-- 状态章(JS 决策后显示) -->
3425
+ * </div>
3426
+ * <div class="ai-diff-body" hidden>
3427
+ * <table class="ai-diff-table">
3428
+ * <tr class="ai-diff-line--hunk"><td colspan="3">@@ -10,6 +10,8 @@ …</td></tr>
3429
+ * <tr class="ai-diff-line--ctx|ai-diff-line--add|ai-diff-line--del">
3430
+ * <td class="ai-diff-ln" aria-hidden="true">10</td>
3431
+ * <td class="ai-diff-ln" aria-hidden="true">10</td>
3432
+ * <td class="ai-diff-code">内容(行首符号由 ::before 提供)</td>
3433
+ * </tr>
3434
+ * </table>
3435
+ * </div>
3436
+ * </div>
3437
+ * </div>
3438
+ *
3439
+ * 文件 chips(纯 CSS 族,无 behavior;文件图标由消费方/ai-panel.ts 注入 svg):
3440
+ * <div class="ai-files">
3441
+ * <span class="ai-file-chip is-added|is-modified|is-deleted
3442
+ * ai-file-chip--ts|js|css|json|md|img|other">
3443
+ * <span class="ai-file-icon">…svg…</span>src/foo.ts
3444
+ * </span>
3445
+ * </div>
3446
+ *
3447
+ * 状态视觉:
3448
+ * add 行 = success 底 / del 行 = error 底 / hunk 行 = info 文字(bg-soft 底)
3449
+ * 文件头状态色边(inset 左色条):added=success / modified=accent / deleted=error / renamed=info
3450
+ * .is-accepted / .is-rejected = 整体淡化 + .ai-diff-verdict 状态章,actions 由 JS hidden
3451
+ *
3452
+ * 依赖:base.css(--icen-tx-* / --icen-ring / keyframes icen-pop-in)。
3453
+ * 只消费 --token-* / --density-* / --radius-*;动画时长全部走组件局部变量。
3454
+ */
3455
+
3456
+ /* ══════════════ ai-diff · 差异审阅 ══════════════ */
3457
+
3458
+ .ai-diff {
3459
+ display: flex;
3460
+ flex-direction: column;
3461
+ gap: var(--density-space-sm);
3462
+ font-size: var(--density-font-size-sm);
3463
+ min-width: 0;
3464
+ align-self: stretch; /* 输出容器宽度自稳定:宿主 flex/grid 居中时不 shrink-wrap */
3465
+ }
3466
+
3467
+ .ai-diff-file {
3468
+ border: 1px solid var(--token-line-soft);
3469
+ border-radius: var(--radius-md);
3470
+ background: var(--token-surface);
3471
+ overflow: hidden;
3472
+ min-width: 0;
3473
+ animation: icen-pop-in var(--ai-diff-pop-duration, var(--icen-tx-fast));
3474
+ }
3475
+
3476
+ /* 文件头状态色边(inset 色条避免 border 宽差导致布局抖动) */
3477
+ .ai-diff-file.is-added { box-shadow: inset 3px 0 0 var(--token-success); }
3478
+ .ai-diff-file.is-modified { box-shadow: inset 3px 0 0 var(--token-accent); }
3479
+ .ai-diff-file.is-deleted { box-shadow: inset 3px 0 0 var(--token-error); }
3480
+ .ai-diff-file.is-renamed { box-shadow: inset 3px 0 0 var(--token-info); }
3481
+
3482
+ /* 决策后淡化(状态章配色见 .ai-diff-verdict) */
3483
+ .ai-diff-file.is-accepted,
3484
+ .ai-diff-file.is-rejected {
3485
+ opacity: 0.66;
3486
+ transition: opacity var(--ai-diff-fade-duration, var(--icen-tx-normal));
3487
+ }
3488
+
3489
+ /* ── head ── */
3490
+
3491
+ .ai-diff-head {
3492
+ display: flex;
3493
+ align-items: center;
3494
+ gap: var(--density-space-sm);
3495
+ padding: var(--density-space-xs) var(--density-space-sm);
3496
+ min-width: 0;
3497
+ cursor: pointer;
3498
+ user-select: none;
3499
+ transition: background-color var(--icen-tx-fast);
3500
+ }
3501
+ .ai-diff-head:hover {
3502
+ background: var(--token-fill-1);
3503
+ }
3504
+ .ai-diff-head:focus-visible {
3505
+ outline: none;
3506
+ box-shadow: var(--icen-ring);
3507
+ }
3508
+
3509
+ .ai-diff-path {
3510
+ flex: 1;
3511
+ min-width: 0;
3512
+ overflow: hidden;
3513
+ text-overflow: ellipsis;
3514
+ white-space: nowrap;
3515
+ font-family: var(--font-mono);
3516
+ font-size: var(--density-font-size-xs);
3517
+ color: var(--token-text);
3518
+ }
3519
+
3520
+ .ai-diff-stat {
3521
+ flex-shrink: 0;
3522
+ display: inline-flex;
3523
+ gap: var(--density-space-xs);
3524
+ font-size: var(--density-font-size-xs);
3525
+ font-variant-numeric: tabular-nums;
3526
+ }
3527
+ .ai-diff-add { color: var(--token-success); font-weight: 600; }
3528
+ .ai-diff-del { color: var(--token-error); font-weight: 600; }
3529
+
3530
+ /* accept / reject 行内小钮(DOM 无固定类,按 data 属性定位) */
3531
+ .ai-diff-actions {
3532
+ flex-shrink: 0;
3533
+ display: inline-flex;
3534
+ gap: var(--density-space-xs);
3535
+ }
3536
+ .ai-diff-actions button {
3537
+ padding: 3px 10px;
3538
+ border: 1px solid var(--token-line-soft);
3539
+ border-radius: var(--radius-full);
3540
+ background: var(--token-surface);
3541
+ color: var(--token-text-muted);
3542
+ font-size: var(--density-font-size-xs);
3543
+ line-height: 1.4;
3544
+ cursor: pointer;
3545
+ transition:
3546
+ background-color var(--icen-tx-fast),
3547
+ border-color var(--icen-tx-fast),
3548
+ color var(--icen-tx-fast);
3549
+ }
3550
+ .ai-diff-actions button:focus-visible {
3551
+ outline: none;
3552
+ box-shadow: var(--icen-ring);
3553
+ }
3554
+ .ai-diff-actions [data-ai-diff-accept]:hover {
3555
+ color: var(--token-success);
3556
+ border-color: color-mix(in srgb, var(--token-success) 45%, transparent);
3557
+ background: var(--token-success-muted);
3558
+ }
3559
+ .ai-diff-actions [data-ai-diff-reject]:hover {
3560
+ color: var(--token-error);
3561
+ border-color: color-mix(in srgb, var(--token-error) 45%, transparent);
3562
+ background: var(--token-error-muted);
3563
+ }
3564
+
3565
+ /* 状态章:决策后 JS 置 text + 去 hidden */
3566
+ .ai-diff-verdict {
3567
+ flex-shrink: 0;
3568
+ padding: 2px 8px;
3569
+ border: 1px solid currentColor;
3570
+ border-radius: var(--radius-full);
3571
+ font-size: var(--density-font-size-xs);
3572
+ font-weight: 500;
3573
+ line-height: 1.4;
3574
+ }
3575
+ .ai-diff-file.is-accepted .ai-diff-verdict { color: var(--token-success); background: var(--token-success-muted); }
3576
+ .ai-diff-file.is-rejected .ai-diff-verdict { color: var(--token-error); background: var(--token-error-muted); }
3577
+
3578
+ /* ── body / 表格 ── */
3579
+
3580
+ .ai-diff-body {
3581
+ border-top: 1px solid var(--token-line-soft);
3582
+ overflow-x: auto;
3583
+ }
3584
+
3585
+ .ai-diff-table {
3586
+ width: 100%;
3587
+ border-collapse: collapse;
3588
+ font-family: var(--font-mono);
3589
+ font-size: var(--density-font-size-xs);
3590
+ line-height: 1.6;
3591
+ }
3592
+ .ai-diff-table td {
3593
+ padding: 0 var(--density-space-xs);
3594
+ vertical-align: top;
3595
+ }
3596
+
3597
+ /* hunk 头:info 文字 */
3598
+ .ai-diff-line--hunk td {
3599
+ padding: 2px var(--density-space-sm);
3600
+ background: var(--token-bg-soft);
3601
+ color: var(--token-info);
3602
+ user-select: none;
3603
+ white-space: nowrap;
3604
+ }
3605
+
3606
+ .ai-diff-ln {
3607
+ width: 1%;
3608
+ min-width: 3.5ch;
3609
+ text-align: right;
3610
+ color: var(--token-text-faint);
3611
+ font-variant-numeric: tabular-nums;
3612
+ user-select: none;
3613
+ }
3614
+ /* 行号区与代码区的分隔线画在第二列右侧 */
3615
+ .ai-diff-ln + .ai-diff-ln {
3616
+ border-right: 1px solid var(--token-line-soft);
3617
+ }
3618
+
3619
+ /* add = success 底 / del = error 底 */
3620
+ .ai-diff-line--add { background: var(--token-success-muted); }
3621
+ .ai-diff-line--del { background: var(--token-error-muted); }
3622
+
3623
+ .ai-diff-code {
3624
+ white-space: pre-wrap;
3625
+ word-break: break-all;
3626
+ padding-left: var(--density-space-sm);
3627
+ }
3628
+ /* 行首符号(选择 diff 内容时不带符号) */
3629
+ .ai-diff-code::before {
3630
+ user-select: none;
3631
+ }
3632
+ .ai-diff-line--add .ai-diff-code::before {
3633
+ content: '+ ';
3634
+ color: var(--token-success);
3635
+ }
3636
+ .ai-diff-line--del .ai-diff-code::before {
3637
+ content: '− ';
3638
+ color: var(--token-error);
3639
+ }
3640
+ .ai-diff-line--ctx .ai-diff-code::before {
3641
+ content: ' ';
3642
+ }
3643
+
3644
+ /* ══════════════ ai-files · 文件 chips(纯 CSS 族) ══════════════ */
3645
+
3646
+ .ai-files {
3647
+ display: flex;
3648
+ flex-wrap: wrap;
3649
+ gap: var(--density-space-xs);
3650
+ align-self: stretch; /* 容器自稳定;chip 自身保持内容宽 */
3651
+ }
3652
+
3653
+ .ai-file-chip {
3654
+ display: inline-flex;
3655
+ align-items: center;
3656
+ gap: 5px;
3657
+ max-width: 100%;
3658
+ padding: 2px 8px;
3659
+ border: 1px solid var(--token-line-soft);
3660
+ border-radius: var(--radius-full);
3661
+ background: var(--token-surface);
3662
+ color: var(--token-text-muted);
3663
+ font-family: var(--font-mono);
3664
+ font-size: var(--density-font-size-xs);
3665
+ line-height: 1.6;
3666
+ overflow: hidden;
3667
+ text-overflow: ellipsis;
3668
+ white-space: nowrap;
3669
+ transition:
3670
+ background-color var(--icen-tx-fast),
3671
+ border-color var(--icen-tx-fast);
3672
+ }
3673
+ .ai-file-chip:hover {
3674
+ background: var(--token-fill-1);
3675
+ border-color: var(--token-line);
3676
+ }
3677
+
3678
+ /* 状态色点(尾点,纯 CSS) */
3679
+ .ai-file-chip::after {
3680
+ content: '';
3681
+ flex-shrink: 0;
3682
+ width: 6px;
3683
+ height: 6px;
3684
+ border-radius: var(--radius-full);
3685
+ background: var(--token-text-faint);
3686
+ }
3687
+ .ai-file-chip.is-added::after { background: var(--token-success); }
3688
+ .ai-file-chip.is-modified::after { background: var(--token-accent); }
3689
+ .ai-file-chip.is-deleted::after { background: var(--token-error); }
3690
+
3691
+ .ai-file-chip.is-deleted {
3692
+ text-decoration: line-through;
3693
+ opacity: 0.75;
3694
+ }
3695
+
3696
+ .ai-file-icon {
3697
+ flex-shrink: 0;
3698
+ display: inline-flex;
3699
+ color: var(--token-text-faint);
3700
+ }
3701
+ .ai-file-icon svg {
3702
+ width: 13px;
3703
+ height: 13px;
3704
+ }
3705
+
3706
+ /* 文件类型色(语义 token):ts/js=冷调 accent-cold,css=accent,json=warning,md=info,img=success */
3707
+ .ai-file-chip--ts .ai-file-icon,
3708
+ .ai-file-chip--js .ai-file-icon { color: var(--token-accent-cold); }
3709
+ .ai-file-chip--css .ai-file-icon { color: var(--token-accent); }
3710
+ .ai-file-chip--json .ai-file-icon { color: var(--token-warning); }
3711
+ .ai-file-chip--md .ai-file-icon { color: var(--token-info); }
3712
+ .ai-file-chip--img .ai-file-icon { color: var(--token-success); }
3713
+
3714
+ /* ── 动效守卫 ── */
3715
+
3716
+ @media (prefers-reduced-motion: reduce) {
3717
+ .ai-diff-file { animation: none; }
3718
+ .ai-diff-file.is-accepted,
3719
+ .ai-diff-file.is-rejected { transition: none; }
3720
+ }
3721
+
3722
+
3723
+ /* ── components/ai-panel.css ── */
3724
+ /*
3725
+ * @icen.ai/ui — Component: ai-panel(AI 任务面板族:todo / context / usage,与 behaviors/ai-panel.ts 配套)
3726
+ *
3727
+ * 完整 DOM 契约(类名与规格 §4.8/§4.9/§4.10 一致):
3728
+ * <div class="ai-todo" [.is-complete] [data-ai-todo-interactive]>
3729
+ * <div class="ai-todo-head">
3730
+ * <span class="ai-todo-progress">2/5</span>
3731
+ * <div class="ai-todo-bar" role="progressbar" …><i style="width:40%"></i></div>
3732
+ * </div>
3733
+ * <div class="ai-todo-item is-pending|running|streaming|approval|done|error|cancelled
3734
+ * [role="button" tabindex="0"]" data-index="0">
3735
+ * <span class="ai-item-status"></span> <!-- 状态点,纯 CSS 由 .is-* 驱动 -->
3736
+ * <span class="ai-todo-text">实现登录页</span>
3737
+ * <span class="ai-todo-active">正在实现登录页…</span> <!-- is-running 时替换 text(CSS 驱动) -->
3738
+ * </div>
3739
+ * </div>
3740
+ *
3741
+ * <div class="ai-usage">
3742
+ * <div class="ai-usage-bar" role="img" aria-label="…">
3743
+ * <i class="ai-usage-seg ai-usage-seg--input|output|cache-read|cache-write|reasoning" style="width:35%"></i>…
3744
+ * </div>
3745
+ * <div class="ai-usage-legend">
3746
+ * <span class="ai-usage-legend-item">
3747
+ * <i class="ai-usage-dot ai-usage-seg--input"></i>
3748
+ * <span class="ai-usage-legend-label">输入</span> <span class="ai-usage-legend-value">35k</span>
3749
+ * </span>…
3750
+ * </div>
3751
+ * <div class="ai-usage-total">82k / 200k · 41%</div>
3752
+ * </div>
3753
+ *
3754
+ * <aside class="ai-context [.ai-context--inline]" hidden role="dialog" aria-label="上下文">
3755
+ * <div class="ai-context-head"><span>上下文</span>
3756
+ * <button type="button" data-ai-context-close aria-label="关闭">×</button></div>
3757
+ * <section class="ai-context-section"><h3>…</h3>
3758
+ * <div class="ai-context-section-body">…renderAiContext 产物…</div> <!-- 可选包裹,亦可直接放内容 -->
3759
+ * </section>…
3760
+ * </aside>
3761
+ *
3762
+ * .ai-item 行(MCP / Skills,规格 §2 基元):
3763
+ * <div class="ai-item is-done|is-error" style="--ai-item-tint: var(--token-accent)">
3764
+ * <span class="ai-item-icon">…svg…</span>
3765
+ * <span class="ai-item-main">
3766
+ * <span class="ai-item-title">github</span><span class="ai-item-sub">…</span>
3767
+ * </span>
3768
+ * <span class="ai-item-side"><span class="ai-item-status"></span><span class="ai-item-meta">12 工具</span></span>
3769
+ * </div>
3770
+ *
3771
+ * 依赖说明:
3772
+ * - .ai-item 完整版在 ai-tool.css(S2)。此处按任务约定重新声明最小骨架(含 --ai-item-tint
3773
+ * 局部变量与状态点),保证 ai-panel.css 单独引入时 todo/context 可用;两边类名/语义一致,
3774
+ * 同页面共存时后加载者覆盖同款属性,视觉不冲突。
3775
+ * - keyframes ai-status-pulse 为组件局部定义(等同 pill.css 的 pill-pulse),
3776
+ * 时长走 var(--pill-pulse-duration, 1.5s):pill.css 在场时与其同步,否则自洽。
3777
+ * - blink keyframes 来自 tokens/colors.css(.animate-blink 同款,共享复用)。
3778
+ * - base.css:--z-chrome / --icen-tx-* / --icen-ring。只消费 --token-* / --density-* / --radius-*。
3779
+ */
3780
+
3781
+ /* ══════════════ .ai-item 最小基元(自包含,兼容 ai-tool.css 完整版) ══════════════ */
3782
+
3783
+ .ai-item {
3784
+ --ai-item-tint: var(--token-accent);
3785
+ display: flex;
3786
+ align-items: center;
3787
+ gap: var(--density-space-sm);
3788
+ padding: var(--density-space-xs) var(--density-space-sm);
3789
+ border-radius: var(--radius-sm);
3790
+ font-size: var(--density-font-size-sm);
3791
+ min-width: 0;
3792
+ align-self: stretch; /* 宽度自稳定:宿主 flex/grid 居中时不 shrink-wrap */
3793
+ }
3794
+
3795
+ .ai-item-icon {
3796
+ flex-shrink: 0;
3797
+ display: inline-flex;
3798
+ color: var(--ai-item-tint);
3799
+ }
3800
+ .ai-item-icon svg {
3801
+ width: 15px;
3802
+ height: 15px;
3803
+ }
3804
+
3805
+ .ai-item-main {
3806
+ flex: 1;
3807
+ min-width: 0;
3808
+ display: flex;
3809
+ flex-direction: column;
3810
+ gap: 1px;
3811
+ }
3812
+ .ai-item-title {
3813
+ color: var(--token-text);
3814
+ line-height: 1.4;
3815
+ overflow: hidden;
3816
+ text-overflow: ellipsis;
3817
+ white-space: nowrap;
3818
+ }
3819
+ .ai-item-sub {
3820
+ font-size: var(--density-font-size-xs);
3821
+ color: var(--token-text-muted);
3822
+ overflow: hidden;
3823
+ text-overflow: ellipsis;
3824
+ white-space: nowrap;
3825
+ }
3826
+
3827
+ .ai-item-side {
3828
+ flex-shrink: 0;
3829
+ display: inline-flex;
3830
+ align-items: center;
3831
+ gap: var(--density-space-xs);
3832
+ }
3833
+ .ai-item-meta {
3834
+ font-size: var(--density-font-size-xs);
3835
+ color: var(--token-text-faint);
3836
+ font-variant-numeric: tabular-nums;
3837
+ }
3838
+
3839
+ /* ── 状态点 .ai-item-status:纯 CSS 圆点,由 .is-* 驱动(ai-item 与 ai-todo-item 共用) ── */
3840
+
3841
+ .ai-item-status {
3842
+ flex-shrink: 0;
3843
+ width: 8px;
3844
+ height: 8px;
3845
+ border-radius: var(--radius-full);
3846
+ border: 1.5px solid var(--token-text-faint);
3847
+ background: transparent;
3848
+ }
3849
+
3850
+ /* pending:faint 空心点(默认态即空心) */
3851
+ .ai-item.is-pending > .ai-item-status,
3852
+ .ai-todo-item.is-pending > .ai-item-status { }
3853
+
3854
+ /* running / streaming:accent 脉冲点 */
3855
+ .ai-item.is-running > .ai-item-status,
3856
+ .ai-todo-item.is-running > .ai-item-status,
3857
+ .ai-item.is-streaming > .ai-item-status,
3858
+ .ai-todo-item.is-streaming > .ai-item-status {
3859
+ border-color: var(--token-accent);
3860
+ background: var(--token-accent);
3861
+ animation: ai-status-pulse var(--pill-pulse-duration, 1.5s) ease-in-out infinite;
3862
+ }
3863
+
3864
+ /* approval:warning 点 */
3865
+ .ai-item.is-approval > .ai-item-status,
3866
+ .ai-todo-item.is-approval > .ai-item-status {
3867
+ border-color: var(--token-warning);
3868
+ background: var(--token-warning);
3869
+ }
3870
+
3871
+ /* done:success 点 */
3872
+ .ai-item.is-done > .ai-item-status,
3873
+ .ai-todo-item.is-done > .ai-item-status {
3874
+ border-color: var(--token-success);
3875
+ background: var(--token-success);
3876
+ }
3877
+
3878
+ /* error:error 点 */
3879
+ .ai-item.is-error > .ai-item-status,
3880
+ .ai-todo-item.is-error > .ai-item-status {
3881
+ border-color: var(--token-error);
3882
+ background: var(--token-error);
3883
+ }
3884
+
3885
+ /* cancelled:faint 实心点 + 标题删除线(规格 §1) */
3886
+ .ai-item.is-cancelled > .ai-item-status,
3887
+ .ai-todo-item.is-cancelled > .ai-item-status {
3888
+ border-color: var(--token-text-faint);
3889
+ background: var(--token-text-faint);
3890
+ }
3891
+ .ai-item.is-cancelled .ai-item-title,
3892
+ .ai-todo-item.is-cancelled .ai-todo-text {
3893
+ text-decoration: line-through;
3894
+ }
3895
+
3896
+ /* 组件局部 keyframes(等同 pill-pulse,避免硬依赖 pill.css) */
3897
+ @keyframes ai-status-pulse {
3898
+ 0%, 100% { opacity: 1; transform: scale(1); }
3899
+ 50% { opacity: 0.4; transform: scale(0.7); }
3900
+ }
3901
+
3902
+ /* ══════════════ ai-todo · 任务清单(§4.8) ══════════════ */
3903
+
3904
+ .ai-todo {
3905
+ display: flex;
3906
+ flex-direction: column;
3907
+ gap: var(--density-space-xs);
3908
+ font-size: var(--density-font-size-sm);
3909
+ min-width: 0;
3910
+ align-self: stretch; /* 输出容器宽度自稳定 */
3911
+ }
3912
+
3913
+ .ai-todo-head {
3914
+ display: flex;
3915
+ align-items: center;
3916
+ gap: var(--density-space-sm);
3917
+ min-width: 0;
3918
+ }
3919
+
3920
+ .ai-todo-progress {
3921
+ flex-shrink: 0;
3922
+ font-size: var(--density-font-size-xs);
3923
+ color: var(--token-text-muted);
3924
+ font-variant-numeric: tabular-nums;
3925
+ }
3926
+
3927
+ /* 进度条:feedback progress 视觉(token 消费) */
3928
+ .ai-todo-bar {
3929
+ flex: 1;
3930
+ height: 4px;
3931
+ border-radius: var(--radius-full);
3932
+ overflow: hidden;
3933
+ background: color-mix(in srgb, var(--token-line-soft) 40%, transparent);
3934
+ }
3935
+ .ai-todo-bar > i {
3936
+ display: block;
3937
+ height: 100%;
3938
+ border-radius: var(--radius-full);
3939
+ background: var(--token-accent);
3940
+ transition: width var(--icen-tx-slow);
3941
+ }
3942
+ .ai-todo.is-complete .ai-todo-bar > i {
3943
+ background: var(--token-success);
3944
+ }
3945
+
3946
+ .ai-todo-item {
3947
+ display: flex;
3948
+ align-items: center;
3949
+ gap: var(--density-space-sm);
3950
+ padding: 3px var(--density-space-xs);
3951
+ border-radius: var(--radius-xs);
3952
+ min-width: 0;
3953
+ }
3954
+
3955
+ /* 交互模式(initAiTodo 升级 role=button) */
3956
+ .ai-todo-item[role='button'] {
3957
+ cursor: pointer;
3958
+ transition: background-color var(--icen-tx-fast);
3959
+ }
3960
+ .ai-todo-item[role='button']:hover {
3961
+ background: var(--token-fill-1);
3962
+ }
3963
+ .ai-todo-item:focus-visible {
3964
+ outline: none;
3965
+ box-shadow: var(--icen-ring);
3966
+ }
3967
+
3968
+ .ai-todo-text {
3969
+ min-width: 0;
3970
+ overflow: hidden;
3971
+ text-overflow: ellipsis;
3972
+ white-space: nowrap;
3973
+ }
3974
+
3975
+ /* activeForm:运行中替换式强调(Claude Code 模式) */
3976
+ .ai-todo-active {
3977
+ display: none;
3978
+ min-width: 0;
3979
+ overflow: hidden;
3980
+ text-overflow: ellipsis;
3981
+ white-space: nowrap;
3982
+ color: var(--token-accent-deep);
3983
+ }
3984
+ .ai-todo-item.is-running .ai-todo-text { display: none; }
3985
+ .ai-todo-item.is-running .ai-todo-active { display: inline; }
3986
+
3987
+ /* done/cancelled:淡化 + 删除线 */
3988
+ .ai-todo-item.is-done .ai-todo-text,
3989
+ .ai-todo-item.is-cancelled .ai-todo-text {
3990
+ color: var(--token-text-faint);
3991
+ text-decoration: line-through;
3992
+ }
3993
+
3994
+ /* ══════════════ ai-usage · 用量条(§4.9) ══════════════ */
3995
+
3996
+ .ai-usage {
3997
+ display: flex;
3998
+ flex-direction: column;
3999
+ gap: var(--density-space-xs);
4000
+ font-size: var(--density-font-size-xs);
4001
+ color: var(--token-text-muted);
4002
+ min-width: 0;
4003
+ align-self: stretch; /* 输出容器宽度自稳定 */
4004
+ }
4005
+
4006
+ .ai-usage-bar {
4007
+ display: flex;
4008
+ height: 6px;
4009
+ border-radius: var(--radius-full);
4010
+ overflow: hidden;
4011
+ background: color-mix(in srgb, var(--token-line-soft) 40%, transparent);
4012
+ }
4013
+
4014
+ .ai-usage-seg {
4015
+ display: block;
4016
+ height: 100%;
4017
+ min-width: 0;
4018
+ transition: width var(--icen-tx-slow);
4019
+ }
4020
+
4021
+ /* 配色契约:input=accent / output=success / cacheRead=info / cacheWrite=warning / reasoning=faint */
4022
+ .ai-usage-seg--input { background: var(--token-accent); }
4023
+ .ai-usage-seg--output { background: var(--token-success); }
4024
+ .ai-usage-seg--cache-read { background: var(--token-info); }
4025
+ .ai-usage-seg--cache-write { background: var(--token-warning); }
4026
+ .ai-usage-seg--reasoning { background: var(--token-text-faint); }
4027
+
4028
+ /* 图例:色点 + 标签 + 数值(缓存分列,计费诚实) */
4029
+ .ai-usage-legend {
4030
+ display: flex;
4031
+ flex-wrap: wrap;
4032
+ gap: var(--density-space-xs) var(--density-space-sm);
4033
+ }
4034
+ .ai-usage-legend-item {
4035
+ display: inline-flex;
4036
+ align-items: center;
4037
+ gap: 5px;
4038
+ font-variant-numeric: tabular-nums;
4039
+ }
4040
+ .ai-usage-dot {
4041
+ flex-shrink: 0;
4042
+ width: 8px;
4043
+ height: 8px;
4044
+ border-radius: var(--radius-full);
4045
+ }
4046
+ .ai-usage-legend-label { color: var(--token-text-muted); }
4047
+ .ai-usage-legend-value { color: var(--token-text); font-weight: 500; }
4048
+
4049
+ .ai-usage-total {
4050
+ font-variant-numeric: tabular-nums;
4051
+ color: var(--token-text-muted);
4052
+ }
4053
+
4054
+ /* ══════════════ ai-usage-ring · 上下文窗口环形指示器(点击弹出完整分解) ══════════════ */
4055
+
4056
+ .ai-usage-ring {
4057
+ position: relative;
4058
+ display: inline-flex;
4059
+ align-items: center;
4060
+ justify-content: center;
4061
+ width: 36px;
4062
+ height: 36px;
4063
+ padding: 0;
4064
+ border: 0;
4065
+ background: transparent;
4066
+ border-radius: var(--radius-full);
4067
+ color: var(--token-text-muted);
4068
+ cursor: pointer;
4069
+ flex-shrink: 0;
4070
+ transition:
4071
+ color var(--icen-tx-fast),
4072
+ background-color var(--icen-tx-fast);
4073
+ }
4074
+ .ai-usage-ring:hover {
4075
+ background: var(--token-fill-1);
4076
+ color: var(--token-text);
4077
+ }
4078
+ .ai-usage-ring:focus-visible {
4079
+ outline: none;
4080
+ box-shadow: var(--icen-ring);
4081
+ }
4082
+
4083
+ .ai-usage-ring > svg {
4084
+ position: absolute;
4085
+ inset: 0;
4086
+ width: 100%;
4087
+ height: 100%;
4088
+ transform: rotate(-90deg); /* 12 点起笔 */
4089
+ }
4090
+ .ai-usage-ring-track {
4091
+ fill: none;
4092
+ stroke: var(--token-fill-2);
4093
+ stroke-width: 3;
4094
+ }
4095
+ .ai-usage-ring-fill {
4096
+ fill: none;
4097
+ stroke: var(--token-accent);
4098
+ stroke-width: 3;
4099
+ stroke-linecap: round;
4100
+ transition:
4101
+ stroke-dasharray var(--icen-tx-slow),
4102
+ stroke var(--icen-tx-fast);
4103
+ }
4104
+ /* 状态档:<60% ok(accent)/ 60–85% warn / >85% hot */
4105
+ .ai-usage-ring.is-warn .ai-usage-ring-fill { stroke: var(--token-warning); }
4106
+ .ai-usage-ring.is-hot .ai-usage-ring-fill { stroke: var(--token-error); }
4107
+ .ai-usage-ring.is-hot {
4108
+ animation: ai-usage-ring-pulse var(--ai-usage-ring-pulse-duration, 1.6s) ease-in-out infinite;
4109
+ }
4110
+ @keyframes ai-usage-ring-pulse {
4111
+ 0%, 100% { opacity: 1; }
4112
+ 50% { opacity: 0.55; }
4113
+ }
4114
+
4115
+ .ai-usage-ring-pct {
4116
+ position: relative;
4117
+ font-family: var(--font-mono);
4118
+ font-size: 9px;
4119
+ font-weight: 600;
4120
+ letter-spacing: -0.02em;
4121
+ font-variant-numeric: tabular-nums;
4122
+ user-select: none;
4123
+ }
4124
+
4125
+ /* 大号变体(常驻工具栏/头部用) */
4126
+ .ai-usage-ring--lg {
4127
+ width: 44px;
4128
+ height: 44px;
4129
+ }
4130
+ .ai-usage-ring--lg .ai-usage-ring-pct { font-size: 10px; }
4131
+
4132
+ /* 弹层(复用 .popover 基座,内容区只补间距) */
4133
+ .ai-usage-popover {
4134
+ display: flex;
4135
+ flex-direction: column;
4136
+ gap: var(--density-space-sm);
4137
+ padding: var(--density-space-md);
4138
+ }
4139
+ .ai-usage-popover-title {
4140
+ font-size: var(--density-font-size-sm);
4141
+ font-weight: 600;
4142
+ color: var(--token-text);
4143
+ }
4144
+
4145
+ @media (prefers-reduced-motion: reduce) {
4146
+ .ai-usage-ring.is-hot { animation: none; }
4147
+ .ai-usage-ring-fill { transition: none; }
4148
+ }
4149
+
4150
+ /* ══════════════ ai-context · 上下文抽屉(§4.10) ══════════════ */
4151
+
4152
+ .ai-context {
4153
+ position: fixed;
4154
+ inset: 0 0 0 auto;
4155
+ width: min(360px, 92vw);
4156
+ z-index: var(--z-chrome);
4157
+ display: flex;
4158
+ flex-direction: column;
4159
+ background: var(--token-surface);
4160
+ border-left: 1px solid var(--token-line-soft);
4161
+ box-shadow: var(--token-shadow-overlay);
4162
+ transform: translateX(100%);
4163
+ transition: transform var(--ai-context-duration, var(--duration-normal)) var(--ease-out);
4164
+ overflow-y: auto;
4165
+ }
4166
+ .ai-context[hidden] {
4167
+ display: none;
4168
+ }
4169
+ .ai-context.is-open {
4170
+ transform: translateX(0);
4171
+ }
4172
+
4173
+ /* 页面流内嵌变体:不做 fixed、不滑入 */
4174
+ .ai-context--inline {
4175
+ position: static;
4176
+ inset: auto;
4177
+ width: auto;
4178
+ z-index: auto;
4179
+ transform: none;
4180
+ transition: none;
4181
+ border: 1px solid var(--token-line-soft);
4182
+ border-radius: var(--radius-md);
4183
+ box-shadow: var(--token-shadow-elevated);
4184
+ overflow-y: visible;
4185
+ }
4186
+
4187
+ .ai-context-head {
4188
+ position: sticky;
4189
+ top: 0;
4190
+ z-index: 1;
4191
+ display: flex;
4192
+ align-items: center;
4193
+ justify-content: space-between;
4194
+ gap: var(--density-space-sm);
4195
+ padding: var(--density-space-sm) var(--density-space-md);
4196
+ background: var(--token-surface);
4197
+ border-bottom: 1px solid var(--token-line-soft);
4198
+ font-size: var(--density-font-size-sm);
4199
+ font-weight: 600;
4200
+ color: var(--token-text);
4201
+ }
4202
+
4203
+ .ai-context-head [data-ai-context-close] {
4204
+ flex-shrink: 0;
4205
+ display: inline-flex;
4206
+ align-items: center;
4207
+ justify-content: center;
4208
+ width: 26px;
4209
+ height: 26px;
4210
+ padding: 0;
4211
+ border: 0;
4212
+ border-radius: var(--radius-xs);
4213
+ background: none;
4214
+ color: var(--token-text-faint);
4215
+ font-size: 16px;
4216
+ line-height: 1;
4217
+ cursor: pointer;
4218
+ transition:
4219
+ background-color var(--icen-tx-fast),
4220
+ color var(--icen-tx-fast);
4221
+ }
4222
+ .ai-context-head [data-ai-context-close]:hover {
4223
+ color: var(--token-text);
4224
+ background: var(--token-fill-1);
4225
+ }
4226
+ .ai-context-head [data-ai-context-close]:focus-visible {
4227
+ outline: none;
4228
+ box-shadow: var(--icen-ring);
4229
+ }
4230
+
4231
+ .ai-context-section {
4232
+ display: flex;
4233
+ flex-direction: column;
4234
+ gap: var(--density-space-xs);
4235
+ padding: var(--density-space-sm) var(--density-space-md);
4236
+ min-width: 0;
4237
+ }
4238
+ .ai-context-section ~ .ai-context-section {
4239
+ border-top: 1px solid var(--token-line-soft);
4240
+ }
4241
+ .ai-context-section h3 {
4242
+ margin: 0;
4243
+ font-size: var(--density-font-size-xs);
4244
+ font-weight: 600;
4245
+ color: var(--token-text-muted);
4246
+ }
4247
+ .ai-context-section-body {
4248
+ display: flex;
4249
+ flex-direction: column;
4250
+ gap: var(--density-space-xs);
4251
+ min-width: 0;
4252
+ }
4253
+
4254
+ /* ══════════════ ai-audit(renderAiAudit,规格 §12.2) ══════════════ */
4255
+
4256
+ /*
4257
+ * <div class="ai-audit">
4258
+ * <div class="ai-audit-totals"><span class="ai-audit-totals-text">12 次请求 · 1 失败 · 1.2M tok · $0.42 · TTFT 240ms</span></div>
4259
+ * <div class="ai-audit-models">
4260
+ * <div class="ai-audit-model-row"><span class="ai-audit-model-name">kimi/kimi-k3</span>
4261
+ * <span class="ai-audit-model-meta">8 次 · 900k tok · $0.31</span></div>…
4262
+ * </div>
4263
+ * <div class="ai-audit-list">
4264
+ * <div class="ai-audit-item is-done|is-error">
4265
+ * <span class="ai-audit-dot"></span>
4266
+ * <span class="ai-audit-item-main"><span class="ai-audit-item-model">kimi · kimi-k3</span>
4267
+ * <span class="ai-audit-item-meta">14:03 · 1.2s · TTFT 240ms · 12.3k tok</span></span>
4268
+ * <span class="ai-audit-item-error">网络请求失败:…</span> <!-- 仅 is-error -->
4269
+ * </div>…
4270
+ * </div>
4271
+ * <button class="btn btn-sm ai-audit-clear">清除审计</button>
4272
+ * </div>
4273
+ */
4274
+
4275
+ .ai-audit {
4276
+ display: flex;
4277
+ flex-direction: column;
4278
+ gap: var(--density-space-sm);
4279
+ align-self: stretch;
4280
+ min-width: 0;
4281
+ font-size: var(--density-font-size-sm);
4282
+ }
4283
+ .ai-audit-empty {
4284
+ padding: var(--density-space-md);
4285
+ text-align: center;
4286
+ color: var(--token-text-faint);
4287
+ font-size: var(--density-font-size-xs);
4288
+ }
4289
+
4290
+ /* totals 行:等宽数字,faint 汇总 */
4291
+ .ai-audit-totals-text {
4292
+ display: block;
4293
+ font-family: var(--font-mono);
4294
+ font-size: var(--density-font-size-xs);
4295
+ color: var(--token-text-faint);
4296
+ font-variant-numeric: tabular-nums;
4297
+ }
4298
+
4299
+ /* byModel 分组行 */
4300
+ .ai-audit-models {
4301
+ display: flex;
4302
+ flex-direction: column;
4303
+ gap: 2px;
4304
+ }
4305
+ .ai-audit-model-row {
4306
+ display: flex;
4307
+ align-items: baseline;
4308
+ justify-content: space-between;
4309
+ gap: var(--density-space-sm);
4310
+ min-width: 0;
4311
+ }
4312
+ .ai-audit-model-name {
4313
+ font-family: var(--font-mono);
4314
+ font-size: var(--density-font-size-xs);
4315
+ color: var(--token-text);
4316
+ overflow: hidden;
4317
+ text-overflow: ellipsis;
4318
+ white-space: nowrap;
4319
+ }
4320
+ .ai-audit-model-meta {
4321
+ flex-shrink: 0;
4322
+ font-family: var(--font-mono);
4323
+ font-size: var(--density-font-size-2xs);
4324
+ color: var(--token-text-faint);
4325
+ font-variant-numeric: tabular-nums;
4326
+ }
4327
+
4328
+ /* 最近条目(最新在前) */
4329
+ .ai-audit-list {
4330
+ display: flex;
4331
+ flex-direction: column;
4332
+ gap: 2px;
4333
+ max-height: 280px;
4334
+ overflow-y: auto;
4335
+ }
4336
+ .ai-audit-item {
4337
+ display: flex;
4338
+ align-items: baseline;
4339
+ gap: var(--density-space-xs);
4340
+ padding: 2px var(--density-space-xs);
4341
+ border-radius: var(--radius-xs);
4342
+ min-width: 0;
4343
+ }
4344
+ .ai-audit-item:hover {
4345
+ background: var(--token-fill-1);
4346
+ }
4347
+ .ai-audit-dot {
4348
+ flex-shrink: 0;
4349
+ width: 6px;
4350
+ height: 6px;
4351
+ border-radius: 50%;
4352
+ background: var(--token-text-faint);
4353
+ }
4354
+ .ai-audit-item.is-done .ai-audit-dot {
4355
+ background: var(--token-success);
4356
+ }
4357
+ .ai-audit-item.is-error .ai-audit-dot {
4358
+ background: var(--token-error);
4359
+ }
4360
+ .ai-audit-item-main {
4361
+ display: flex;
4362
+ flex-direction: column;
4363
+ min-width: 0;
4364
+ flex: 1;
4365
+ }
4366
+ .ai-audit-item-model {
4367
+ font-family: var(--font-mono);
4368
+ font-size: var(--density-font-size-2xs);
4369
+ color: var(--token-text);
4370
+ overflow: hidden;
4371
+ text-overflow: ellipsis;
4372
+ white-space: nowrap;
4373
+ }
4374
+ .ai-audit-item-meta {
4375
+ font-family: var(--font-mono);
4376
+ font-size: var(--density-font-size-2xs);
4377
+ color: var(--token-text-faint);
4378
+ font-variant-numeric: tabular-nums;
4379
+ }
4380
+ .ai-audit-item-error {
4381
+ flex-shrink: 0;
4382
+ max-width: 45%;
4383
+ font-size: var(--density-font-size-2xs);
4384
+ color: var(--token-error);
4385
+ overflow: hidden;
4386
+ text-overflow: ellipsis;
4387
+ white-space: nowrap;
4388
+ }
4389
+ .ai-audit-item.is-error .ai-audit-item-model {
4390
+ color: var(--token-error);
4391
+ }
4392
+
4393
+ /* 清除钮(右对齐;视觉复用 .btn .btn-sm) */
4394
+ .ai-audit-clear {
4395
+ align-self: flex-end;
4396
+ }
4397
+
4398
+ /* ── 动效守卫 ── */
4399
+
4400
+ @media (prefers-reduced-motion: reduce) {
4401
+ .ai-todo-bar > i,
4402
+ .ai-usage-seg { transition: none; }
4403
+ .ai-context { transition: none; }
4404
+ .ai-item-status { animation: none; }
4405
+ }
4406
+
4407
+
4408
+ /* ── components/ai-tool.css ── */
4409
+ /*
4410
+ * @icen.ai/ui — Component: ai-tool(AI 工具调用卡 + 子智能体卡;含 .ai-item 条目基元完整定义)
4411
+ *
4412
+ * 完整契约:
4413
+ * ── 条目基元 .ai-item(规格 §2 骨架:所有行式 AI 条目的"基类",todo/panel 亦复用)──
4414
+ * <div class="ai-item is-running" style="--ai-item-tint: var(--token-accent)">
4415
+ * <span class="ai-item-icon"><!-- svg --></span>
4416
+ * <div class="ai-item-main">
4417
+ * <div class="ai-item-title">一行摘要</div>
4418
+ * <div class="ai-item-sub">次要信息(路径/参数)</div>
4419
+ * </div>
4420
+ * <div class="ai-item-side">
4421
+ * <span class="ai-item-status"></span> ← 状态点,纯 CSS 由 .is-* 驱动
4422
+ * <span class="ai-item-meta">1.2s</span>
4423
+ * </div>
4424
+ * <div class="ai-item-detail" hidden>展开详情</div>
4425
+ * </div>
4426
+ *
4427
+ * ── 工具调用卡(.ai-tool-head 复用 .ai-item-* 零件,不带 .ai-item 壳)──
4428
+ * <div class="ai-tool ai-tool--shell is-done" data-ai-id="…" data-ai-kind="shell">
4429
+ * <button class="ai-tool-head" type="button" aria-expanded="false">
4430
+ * <span class="ai-item-icon" aria-hidden="true">…svg…</span>
4431
+ * <span class="ai-item-main">
4432
+ * <span class="ai-item-title">Read</span>
4433
+ * <span class="ai-item-sub">src/foo.ts:1-50</span>
4434
+ * </span>
4435
+ * <span class="ai-item-status" role="img" aria-label="完成"></span>
4436
+ * <span class="ai-item-meta">0.4s</span>
4437
+ * </button>
4438
+ * <div class="ai-tool-body" hidden>
4439
+ * <div class="ai-tool-io"><div class="ai-tool-io-label">输入</div><pre class="ai-tool-io-content">…</pre></div>
4440
+ * <div class="ai-tool-io"><div class="ai-tool-io-label">输出</div><pre class="ai-tool-io-content">…</pre></div>
4441
+ * <div class="ai-tool-approval"> ← 仅 .is-approval 显示(纯 CSS 驱动)
4442
+ * <div class="ai-tool-approval-reason" hidden>审批原因</div> ← 可选(model.approval.reason)
4443
+ * <button class="btn btn-sm btn-primary" data-ai-approve type="button">允许</button>
4444
+ * <button class="btn btn-sm" data-ai-reject type="button">拒绝</button>
4445
+ * </div>
4446
+ * </div>
4447
+ * </div>
4448
+ *
4449
+ * ── 子智能体卡 ──
4450
+ * <div class="ai-subagent is-running" data-ai-id="…" data-ai-kind="subagent">
4451
+ * <button class="ai-subagent-head" type="button" aria-expanded="false">…head 结构同上…</button>
4452
+ * <div class="ai-subagent-body" hidden>
4453
+ * <div class="ai-subagent-stream"> ← 嵌套 .ai-tool / .ai-subagent;
4454
+ * … 行间 style="--ai-activity-index:i" 驱动渐入 stagger
4455
+ * </div>
4456
+ * <div class="ai-subagent-result">完成回执摘要</div>
4457
+ * </div>
4458
+ * </div>
4459
+ *
4460
+ * 状态机 7 态(全条目共享,规格 §1):
4461
+ * .is-pending faint 空心点
4462
+ * .is-running tint 脉冲点(--ai-item-pulse-duration 变量化时长)
4463
+ * .is-streaming ▍流式光标(colors.css blink keyframes,--ai-item-blink-duration 变量化)
4464
+ * .is-approval warning 点 + 外环;卡片边框 warning;显示审批按钮
4465
+ * .is-done success 纯 CSS 对勾
4466
+ * .is-error error 点 + 外环;失败条目由 behavior 首次渲染自动展开
4467
+ * .is-cancelled faint 点 + 标题删除线
4468
+ *
4469
+ * kind 修饰类 ai-tool--<kind> 只覆盖 --ai-item-tint(继承 = 变量级联,不改结构):
4470
+ * read/search/grep/glob=info · edit/write=accent · rm=error · shell=muted · mcp/skill=success
4471
+ * browser/fetch/todo/plan/subagent/note = 默认 accent(不写规则)
4472
+ * 第三方 registerAiKind 的 tint 由 behavior 内联 --ai-item-tint 兜底。
4473
+ */
4474
+
4475
+ /* ══════════════════════════════════════════════════════════════
4476
+ 条目基元 .ai-item(行式条目"基类")
4477
+ ══════════════════════════════════════════════════════════════ */
4478
+
4479
+ .ai-item {
4480
+ /* 局部变量槽:kind/变体只覆盖这些变量(--btn-* 同款继承模式) */
4481
+ --ai-item-tint: var(--token-accent);
4482
+ --ai-item-pulse-duration: 1.5s; /* running 脉冲(pill --pill-pulse-duration 同款变量化) */
4483
+ --ai-item-blink-duration: 1s; /* streaming 光标闪烁 */
4484
+
4485
+ display: flex;
4486
+ flex-wrap: wrap;
4487
+ align-items: center;
4488
+ gap: var(--density-space-sm);
4489
+ min-width: 0;
4490
+ align-self: stretch; /* 宽度自稳定:宿主 flex/grid 居中时不 shrink-wrap(width:100% 会塌成 min-content,禁用) */
4491
+ }
4492
+
4493
+ .ai-item-icon {
4494
+ display: inline-flex;
4495
+ align-items: center;
4496
+ justify-content: center;
4497
+ flex-shrink: 0;
4498
+ width: 20px;
4499
+ height: 20px;
4500
+ color: var(--ai-item-tint);
4501
+ transition: color var(--icen-tx-fast);
4502
+ }
4503
+ .ai-item-icon > svg {
4504
+ width: 16px;
4505
+ height: 16px;
4506
+ }
4507
+
4508
+ .ai-item-main {
4509
+ flex: 1 1 auto;
4510
+ min-width: 0;
4511
+ display: flex;
4512
+ flex-direction: column;
4513
+ gap: 1px;
4514
+ }
4515
+ .ai-item-title {
4516
+ font-size: var(--density-font-size-md);
4517
+ line-height: 1.4;
4518
+ color: var(--token-text);
4519
+ overflow: hidden;
4520
+ text-overflow: ellipsis;
4521
+ white-space: nowrap;
4522
+ }
4523
+ .ai-item-sub {
4524
+ font-size: var(--density-font-size-xs);
4525
+ line-height: 1.4;
4526
+ color: var(--token-text-muted);
4527
+ overflow: hidden;
4528
+ text-overflow: ellipsis;
4529
+ white-space: nowrap;
4530
+ }
4531
+ .ai-item-side {
4532
+ display: inline-flex;
4533
+ align-items: center;
4534
+ gap: var(--density-space-xs);
4535
+ margin-left: auto;
4536
+ flex-shrink: 0;
4537
+ }
4538
+ /* head 直排结构(无 .ai-item-side 包裹)时,状态点顶到右侧 */
4539
+ .ai-tool-head > .ai-item-status,
4540
+ .ai-subagent-head > .ai-item-status {
4541
+ margin-left: auto;
4542
+ }
4543
+
4544
+ .ai-item-meta {
4545
+ font-size: var(--density-font-size-xs);
4546
+ color: var(--token-text-faint);
4547
+ font-variant-numeric: tabular-nums;
4548
+ flex-shrink: 0;
4549
+ }
4550
+
4551
+ .ai-item-detail {
4552
+ flex-basis: 100%;
4553
+ min-width: 100%;
4554
+ }
4555
+
4556
+ /* ── 状态点 .ai-item-status(纯 CSS 圆点,由 .is-* 决定颜色与动效) ── */
4557
+ .ai-item-status {
4558
+ position: relative;
4559
+ flex-shrink: 0;
4560
+ width: 8px;
4561
+ height: 8px;
4562
+ border-radius: 50%;
4563
+ }
4564
+
4565
+ /* 等待:faint 空心点(.ai-subagent 与 .ai-item 共享同一状态语言) */
4566
+ :is(.ai-item, .ai-tool, .ai-subagent).is-pending .ai-item-status {
4567
+ border: 1.5px solid var(--token-text-faint);
4568
+ }
4569
+
4570
+ /* 执行中:tint 脉冲点 */
4571
+ :is(.ai-item, .ai-tool, .ai-subagent).is-running .ai-item-status {
4572
+ background: var(--ai-item-tint);
4573
+ animation: ai-item-pulse var(--ai-item-pulse-duration) ease-in-out infinite;
4574
+ }
4575
+ @keyframes ai-item-pulse {
4576
+ 0%, 100% { opacity: 1; transform: scale(1); }
4577
+ 50% { opacity: 0.4; transform: scale(0.7); }
4578
+ }
4579
+
4580
+ /* 流式中:▍光标闪烁(复用 colors.css 的 blink keyframes,animate-blink 同款) */
4581
+ :is(.ai-item, .ai-tool, .ai-subagent).is-streaming .ai-item-status {
4582
+ width: 3px;
4583
+ height: 12px;
4584
+ border-radius: 1px;
4585
+ background: var(--ai-item-tint);
4586
+ animation: blink var(--ai-item-blink-duration) step-end infinite;
4587
+ }
4588
+
4589
+ /* 待人确认:warning 点 + 外环 */
4590
+ :is(.ai-item, .ai-tool, .ai-subagent).is-approval .ai-item-status {
4591
+ background: var(--token-warning);
4592
+ box-shadow: 0 0 0 3px var(--token-warning-muted);
4593
+ }
4594
+
4595
+ /* 完成:success 对勾(纯 CSS,无 SVG) */
4596
+ :is(.ai-item, .ai-tool, .ai-subagent).is-done .ai-item-status::after {
4597
+ content: "";
4598
+ position: absolute;
4599
+ left: 1px;
4600
+ top: -1px;
4601
+ width: 5px;
4602
+ height: 9px;
4603
+ border-right: 1.5px solid var(--token-success);
4604
+ border-bottom: 1.5px solid var(--token-success);
4605
+ transform: rotate(45deg);
4606
+ }
4607
+
4608
+ /* 失败:error 点 + 外环 */
4609
+ :is(.ai-item, .ai-tool, .ai-subagent).is-error .ai-item-status {
4610
+ background: var(--token-error);
4611
+ box-shadow: 0 0 0 3px var(--token-error-muted);
4612
+ }
4613
+
4614
+ /* 已取消:faint 点 + 标题删除线 */
4615
+ :is(.ai-item, .ai-tool, .ai-subagent).is-cancelled .ai-item-status {
4616
+ background: var(--token-text-faint);
4617
+ opacity: 0.6;
4618
+ }
4619
+ :is(.ai-item, .ai-tool, .ai-subagent).is-cancelled .ai-item-title {
4620
+ color: var(--token-text-faint);
4621
+ text-decoration: line-through;
4622
+ }
4623
+
4624
+ /* ══════════════════════════════════════════════════════════════
4625
+ 工具调用卡 .ai-tool
4626
+ ══════════════════════════════════════════════════════════════ */
4627
+
4628
+ .ai-tool {
4629
+ --ai-item-tint: var(--token-accent);
4630
+ align-self: stretch; /* 宽度自稳定:宿主 flex/grid 居中时不 shrink-wrap */
4631
+ border: 1px solid var(--token-line-soft);
4632
+ border-radius: var(--radius-md);
4633
+ background: var(--token-surface);
4634
+ }
4635
+
4636
+ /* ── kind 修饰类:只覆盖 --ai-item-tint ──
4637
+ browser/fetch/todo/plan/subagent/note 用默认 accent,不写规则 */
4638
+ .ai-tool--read,
4639
+ .ai-tool--search,
4640
+ .ai-tool--grep,
4641
+ .ai-tool--glob {
4642
+ --ai-item-tint: var(--token-info);
4643
+ }
4644
+ .ai-tool--edit,
4645
+ .ai-tool--write {
4646
+ --ai-item-tint: var(--token-accent);
4647
+ }
4648
+ .ai-tool--rm {
4649
+ --ai-item-tint: var(--token-error);
4650
+ }
4651
+ .ai-tool--shell {
4652
+ --ai-item-tint: var(--token-text-muted);
4653
+ }
4654
+ .ai-tool--mcp,
4655
+ .ai-tool--skill {
4656
+ --ai-item-tint: var(--token-success);
4657
+ }
4658
+
4659
+ /* ── head ── */
4660
+ .ai-tool-head,
4661
+ .ai-subagent-head {
4662
+ display: flex;
4663
+ align-items: center;
4664
+ gap: var(--density-space-sm);
4665
+ width: 100%;
4666
+ padding: var(--density-space-xs) var(--density-space-sm);
4667
+ border: none;
4668
+ border-radius: inherit; /* hover 底色不戳破卡片圆角 */
4669
+ background: transparent;
4670
+ color: inherit;
4671
+ font: inherit;
4672
+ text-align: left;
4673
+ cursor: pointer;
4674
+ -webkit-tap-highlight-color: transparent;
4675
+ transition: background-color var(--icen-tx-fast);
4676
+ }
4677
+ .ai-tool-head:hover,
4678
+ .ai-subagent-head:hover {
4679
+ background: var(--token-fill-1);
4680
+ }
4681
+ .ai-tool-head:focus-visible,
4682
+ .ai-subagent-head:focus-visible {
4683
+ outline: none;
4684
+ box-shadow: var(--icen-ring);
4685
+ }
4686
+
4687
+ /* ── body ── */
4688
+ .ai-tool-body {
4689
+ display: flex;
4690
+ flex-direction: column;
4691
+ gap: var(--density-space-sm);
4692
+ padding: var(--density-space-sm);
4693
+ border-top: 1px solid var(--token-line-soft);
4694
+ animation: icen-pop-in var(--icen-tx-fast);
4695
+ }
4696
+ .ai-tool-body[hidden],
4697
+ .ai-subagent-body[hidden],
4698
+ .ai-tool-io[hidden],
4699
+ .ai-subagent-result[hidden] {
4700
+ display: none;
4701
+ }
4702
+
4703
+ /* ── 输入/输出区块(pre 等宽、可滚、max-height 收敛) ── */
4704
+ .ai-tool-io {
4705
+ display: flex;
4706
+ flex-direction: column;
4707
+ gap: 4px;
4708
+ min-width: 0;
4709
+ }
4710
+ .ai-tool-io-label {
4711
+ font-size: var(--density-font-size-2xs);
4712
+ font-weight: 600;
4713
+ letter-spacing: 0.08em;
4714
+ text-transform: uppercase;
4715
+ color: var(--token-text-faint);
4716
+ }
4717
+ .ai-tool-io-content {
4718
+ --ai-tool-io-max: 12rem; /* 收敛上限(组件局部变量) */
4719
+ margin: 0;
4720
+ padding: var(--density-space-xs) var(--density-space-sm);
4721
+ max-height: var(--ai-tool-io-max);
4722
+ overflow: auto;
4723
+ background: var(--token-bg-soft);
4724
+ border: 1px solid var(--token-line-soft);
4725
+ border-radius: var(--radius-sm);
4726
+ font-family: var(--font-mono);
4727
+ font-size: var(--density-font-size-sm);
4728
+ line-height: 1.5;
4729
+ color: var(--token-text);
4730
+ white-space: pre-wrap;
4731
+ word-break: break-word;
4732
+ }
4733
+
4734
+ /* 多模态回执(MCP content 数组 → 媒体/链接,spec §10.3):
4735
+ 容器纵向排布;媒体本体复用 ai-chat.css 的 .ai-msg-media 全局样式 */
4736
+ .ai-tool-io-media {
4737
+ display: flex;
4738
+ flex-direction: column;
4739
+ gap: var(--density-space-xs);
4740
+ min-width: 0;
4741
+ }
4742
+
4743
+ /* ── 审批区(仅 .is-approval 显示,纯 CSS 驱动) ── */
4744
+ .ai-tool-approval {
4745
+ display: none;
4746
+ align-items: center;
4747
+ gap: var(--density-space-sm);
4748
+ padding-top: var(--density-space-xs);
4749
+ border-top: 1px dashed var(--token-line-soft);
4750
+ }
4751
+ .ai-tool.is-approval .ai-tool-approval {
4752
+ display: flex;
4753
+ }
4754
+ .ai-tool-approval-reason {
4755
+ flex: 1 1 auto;
4756
+ min-width: 0;
4757
+ font-size: var(--density-font-size-xs);
4758
+ line-height: 1.5;
4759
+ color: var(--token-text-muted);
4760
+ }
4761
+ .ai-tool.is-approval {
4762
+ border-color: color-mix(in srgb, var(--token-warning) 40%, transparent);
4763
+ }
4764
+ .ai-tool.is-error {
4765
+ border-color: color-mix(in srgb, var(--token-error) 40%, transparent);
4766
+ }
4767
+
4768
+ /* ══════════════════════════════════════════════════════════════
4769
+ 子智能体卡 .ai-subagent(嵌套活动流 = 缩进 + 左侧引导线,可递归)
4770
+ ══════════════════════════════════════════════════════════════ */
4771
+
4772
+ .ai-subagent {
4773
+ --ai-item-tint: var(--token-accent);
4774
+ align-self: stretch; /* 宽度自稳定:宿主 flex/grid 居中时不 shrink-wrap */
4775
+ border: 1px solid var(--token-line-soft);
4776
+ border-left: 3px solid var(--ai-item-tint); /* 代理卡身份:tint 左边条 */
4777
+ border-radius: var(--radius-md);
4778
+ background: var(--token-surface);
4779
+ }
4780
+ .ai-subagent.is-error {
4781
+ border-left-color: var(--token-error);
4782
+ }
4783
+ .ai-subagent.is-done {
4784
+ border-left-color: var(--token-success);
4785
+ }
4786
+
4787
+ .ai-subagent-body {
4788
+ display: flex;
4789
+ flex-direction: column;
4790
+ gap: var(--density-space-sm);
4791
+ padding: var(--density-space-sm);
4792
+ border-top: 1px solid var(--token-line-soft);
4793
+ }
4794
+
4795
+ /* 嵌套流:缩进 + 引导线;行间渐入 stagger 由 --ai-activity-index(render 写入)驱动 */
4796
+ .ai-subagent-stream {
4797
+ --ai-stagger-step: 40ms; /* 活动项渐入步长(组件局部变量) */
4798
+ position: relative;
4799
+ display: flex;
4800
+ flex-direction: column;
4801
+ gap: var(--density-space-xs);
4802
+ padding-left: var(--density-space-md);
4803
+ }
4804
+ .ai-subagent-stream::before {
4805
+ content: "";
4806
+ position: absolute;
4807
+ left: 5px;
4808
+ top: var(--density-space-xs);
4809
+ bottom: var(--density-space-xs);
4810
+ width: 1px;
4811
+ background: var(--token-line-soft);
4812
+ }
4813
+ /* 展开(body 从 hidden 解除)时,活动项逐条 icen-pop-in 渐入 */
4814
+ .ai-subagent-stream > * {
4815
+ animation: icen-pop-in var(--icen-tx-fast) backwards;
4816
+ animation-delay: calc(var(--ai-activity-index, 0) * var(--ai-stagger-step));
4817
+ }
4818
+
4819
+ /* 完成回执 */
4820
+ .ai-subagent-result {
4821
+ padding: var(--density-space-xs) var(--density-space-sm);
4822
+ background: var(--token-bg-soft);
4823
+ border: 1px solid var(--token-line-soft);
4824
+ border-radius: var(--radius-sm);
4825
+ font-size: var(--density-font-size-sm);
4826
+ line-height: 1.5;
4827
+ color: var(--token-text-muted);
4828
+ }
4829
+ .ai-subagent.is-done .ai-subagent-result {
4830
+ color: var(--token-text);
4831
+ border-color: color-mix(in srgb, var(--token-success) 30%, transparent);
4832
+ }
4833
+
4834
+ /* ══════════════════════════════════════════════════════════════
4835
+ 动效守卫
4836
+ ══════════════════════════════════════════════════════════════ */
4837
+ @media (prefers-reduced-motion: reduce) {
4838
+ :is(.ai-item, .ai-tool, .ai-subagent).is-running .ai-item-status,
4839
+ :is(.ai-item, .ai-tool, .ai-subagent).is-streaming .ai-item-status {
4840
+ animation: none;
4841
+ }
4842
+ .ai-tool-body,
4843
+ .ai-subagent-stream > * {
4844
+ animation: none;
4845
+ }
4846
+ .ai-tool-head,
4847
+ .ai-subagent-head {
4848
+ transition: none;
4849
+ }
4850
+ }
4851
+
4852
+ /* ═══ 工具卡内嵌图表(回执 = ChartSpec 时 setIo 走 renderChart,§13)═══ */
4853
+ .ai-tool-io-chart {
4854
+ width: 100%;
4855
+ min-width: 0;
4856
+ }
4857
+
4858
+ /* ═══ AI 工具挂载区(ai-tools.ts 的 createAiToolArea,§13)═══
4859
+ * .ai-tools-mount > .ai-tools-item > head(名/时间/×)+ body(工具自渲染) */
4860
+ .ai-tools-mount {
4861
+ display: grid;
4862
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
4863
+ gap: var(--density-space-md);
4864
+ align-items: start;
4865
+ }
4866
+ .ai-tools-item {
4867
+ display: flex;
4868
+ flex-direction: column;
4869
+ min-width: 0;
4870
+ border: 1px solid var(--token-line-soft);
4871
+ border-radius: var(--radius-md);
4872
+ background: var(--token-surface);
4873
+ animation: icen-pop-in var(--duration-normal) var(--ease-out);
4874
+ }
4875
+ .ai-tools-item-head {
4876
+ display: flex;
4877
+ align-items: center;
4878
+ gap: var(--density-space-sm);
4879
+ padding: var(--density-space-xs) var(--density-space-sm);
4880
+ border-bottom: 1px solid var(--token-line-soft);
4881
+ }
4882
+ .ai-tools-item-name {
4883
+ font-family: var(--font-mono);
4884
+ font-size: var(--density-font-size-xs);
4885
+ color: var(--token-text);
4886
+ }
4887
+ .ai-tools-item-meta {
4888
+ flex: 1;
4889
+ font-family: var(--font-mono);
4890
+ font-size: var(--density-font-size-2xs);
4891
+ color: var(--token-text-faint);
4892
+ font-variant-numeric: tabular-nums;
4893
+ }
4894
+ .ai-tools-item-remove {
4895
+ border: 0;
4896
+ background: transparent;
4897
+ color: var(--token-text-faint);
4898
+ cursor: pointer;
4899
+ border-radius: var(--radius-xs);
4900
+ font-size: var(--density-font-size-sm);
4901
+ line-height: 1;
4902
+ padding: 2px 6px;
4903
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
4904
+ }
4905
+ .ai-tools-item-remove:hover { color: var(--token-error); background: var(--token-fill-1); }
4906
+ .ai-tools-item-remove:focus-visible { outline: none; box-shadow: var(--icen-ring); }
4907
+ .ai-tools-item-body {
4908
+ padding: var(--density-space-sm);
4909
+ min-width: 0;
2267
4910
  }
2268
4911
 
2269
- /* ══════════════════════════════════════════════════════════════
2270
- 动效与无障碍兜底
2271
- ══════════════════════════════════════════════════════════════ */
2272
-
2273
- /* reduced-motion:所有动画/过渡压缩到 ~0ms(保留可访问性最小可视信号) */
2274
4912
  @media (prefers-reduced-motion: reduce) {
2275
- *, *::before, *::after {
2276
- animation-duration: 0.01ms !important;
2277
- animation-iteration-count: 1 !important;
2278
- transition-duration: 0.01ms !important;
2279
- scroll-behavior: auto !important;
2280
- }
4913
+ .ai-tools-item { animation: none; }
2281
4914
  }
2282
4915
 
2283
- /* 触屏设备(无精细指针):关闭 hover 抬升、压低 pressable scale */
2284
- @media (pointer: coarse) {
2285
- .lift:hover { transform: none; }
2286
- .pressable:active { transform: translateY(0) scale(0.99); }
4916
+ /* 挂载项折叠态:head 为 button(chevron + 名/时间 + ×) */
4917
+ .ai-tools-item-head {
4918
+ cursor: pointer;
4919
+ border: 0;
4920
+ background: transparent;
4921
+ text-align: left;
4922
+ width: 100%;
4923
+ font: inherit;
2287
4924
  }
2288
-
2289
- /* 高对比度模式(Windows High Contrast / 强制 colors) */
2290
- @media (forced-colors: active) {
2291
- .pressable, .control, .lift, .surface-elevated, .focus-ring:focus-visible {
2292
- forced-color-adjust: none;
2293
- }
2294
- .focus-ring:focus-visible,
2295
- .pressable:focus-visible {
2296
- outline: 2px solid Highlight;
2297
- outline-offset: 2px;
2298
- }
4925
+ .ai-tools-item-chevron {
4926
+ flex-shrink: 0;
4927
+ color: var(--token-text-faint);
4928
+ font-size: var(--density-font-size-xs);
4929
+ transition: transform var(--icen-tx-fast);
2299
4930
  }
2300
-
2301
- /* 打印:去掉阴影/背景/动效,节约墨水并避免打印截断 */
2302
- @media print {
2303
- *, *::before, *::after {
2304
- background: transparent !important;
2305
- color: #000 !important;
2306
- box-shadow: none !important;
2307
- text-shadow: none !important;
2308
- }
2309
- body { font-size: 12pt; line-height: 1.4; }
2310
- a, a:visited { text-decoration: underline; }
2311
- a[href]::after { content: ' (' attr(href) ')'; font-size: 90%; }
2312
- pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
2313
- h1, h2, h3 { page-break-after: avoid; }
2314
- .pressable, .lift, .surface-elevated { box-shadow: none !important; transform: none !important; }
4931
+ .ai-tools-item-head[aria-expanded='false'] .ai-tools-item-chevron {
4932
+ transform: rotate(-90deg);
4933
+ }
4934
+ .ai-tools-item.is-collapsed .ai-tools-item-body {
4935
+ display: none;
4936
+ }
4937
+ .ai-tools-item-head:focus-visible {
4938
+ outline: none;
4939
+ box-shadow: var(--icen-ring);
2315
4940
  }
2316
4941
 
2317
4942
 
@@ -2454,11 +5079,11 @@ html {
2454
5079
  height: 16px;
2455
5080
  padding: 0 4px;
2456
5081
  font: inherit;
2457
- font-size: 10px;
5082
+ font-size: var(--density-font-size-2xs);
2458
5083
  font-weight: 600;
2459
5084
  line-height: 1;
2460
5085
  font-variant-numeric: tabular-nums;
2461
- color: var(--token-on-accent, #fff);
5086
+ color: var(--token-on-accent);
2462
5087
  background: var(--token-error);
2463
5088
  border: 2px solid var(--token-surface);
2464
5089
  border-radius: var(--radius-full);
@@ -2481,7 +5106,7 @@ html {
2481
5106
  .badge--sm {
2482
5107
  min-width: 14px;
2483
5108
  height: 14px;
2484
- font-size: 9px;
5109
+ font-size: var(--density-font-size-2xs);
2485
5110
  padding: 0 3px;
2486
5111
  }
2487
5112
  .badge--sm.badge--dot {
@@ -2526,7 +5151,7 @@ html {
2526
5151
  /* 挂在实底色按钮上时用更浅的边框 */
2527
5152
  .btn-primary .badge,
2528
5153
  .btn-danger .badge {
2529
- border-color: var(--token-on-accent, #fff);
5154
+ border-color: var(--token-on-accent);
2530
5155
  }
2531
5156
 
2532
5157
  /* 数字超过 99 显示 99+(消费方决定是否使用,这里只保证宽度容纳) */
@@ -2535,10 +5160,6 @@ html {
2535
5160
  padding: 0 5px;
2536
5161
  }
2537
5162
 
2538
- @media (prefers-reduced-motion: reduce) {
2539
- .badge { transition: none; }
2540
- }
2541
-
2542
5163
 
2543
5164
  /* ── components/breadcrumb.css ── */
2544
5165
  /*
@@ -2663,7 +5284,7 @@ html {
2663
5284
  border-radius: var(--radius-md);
2664
5285
  box-shadow: var(--token-shadow-overlay);
2665
5286
  z-index: var(--z-popover);
2666
- animation: breadcrumb-drop-in var(--icen-tx-fast);
5287
+ animation: icen-pop-in var(--icen-tx-fast);
2667
5288
  }
2668
5289
  .breadcrumb-dropdown-menu[hidden] { display: none; }
2669
5290
  .breadcrumb-dropdown-menu a {
@@ -2679,10 +5300,6 @@ html {
2679
5300
  background: var(--token-fill-1);
2680
5301
  color: var(--token-text);
2681
5302
  }
2682
- @keyframes breadcrumb-drop-in {
2683
- from { opacity: 0; transform: scale(0.96) translateY(-4px); }
2684
- to { opacity: 1; transform: scale(1) translateY(0); }
2685
- }
2686
5303
 
2687
5304
  /* ── 面包屑分隔符变体 ── */
2688
5305
  .breadcrumb--slash .breadcrumb-sep::after { content: '/'; }
@@ -2782,7 +5399,7 @@ html {
2782
5399
  /* ── 变体 ── */
2783
5400
  .btn-primary {
2784
5401
  --btn-bg: var(--token-accent);
2785
- --btn-fg: var(--token-on-accent, #fff);
5402
+ --btn-fg: var(--token-on-accent);
2786
5403
  --btn-border: var(--token-accent);
2787
5404
  --btn-bg-hover: var(--token-accent-deep, color-mix(in srgb, var(--token-accent) 85%, black));
2788
5405
  --btn-border-hover: var(--token-accent-deep, var(--token-accent));
@@ -2798,7 +5415,7 @@ html {
2798
5415
  --btn-bg-hover: var(--token-error);
2799
5416
  --btn-border-hover: var(--token-error);
2800
5417
  }
2801
- .btn-danger:hover { color: var(--token-on-accent, #fff); }
5418
+ .btn-danger:hover { color: var(--token-on-accent); }
2802
5419
 
2803
5420
  .btn-ghost {
2804
5421
  --btn-bg: transparent;
@@ -2869,14 +5486,11 @@ html {
2869
5486
  border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
2870
5487
  border-top-color: currentColor;
2871
5488
  border-radius: 50%;
2872
- animation: btn-spin var(--duration-slow) linear infinite;
5489
+ animation: icen-spin var(--btn-spin-duration, var(--duration-slow)) linear infinite;
2873
5490
  opacity: 1; /* 覆盖 .pressable::after 渐变(若与 .pressable 同用) */
2874
5491
  background: none;
2875
5492
  pointer-events: none;
2876
5493
  }
2877
- @keyframes btn-spin {
2878
- to { transform: rotate(360deg); }
2879
- }
2880
5494
 
2881
5495
  /* 图标按钮内部 svg 默认尺寸对齐字号 */
2882
5496
  .btn-icon-start, .btn-icon-end {
@@ -2886,7 +5500,7 @@ html {
2886
5500
  }
2887
5501
 
2888
5502
  @media (prefers-reduced-motion: reduce) {
2889
- .btn.is-loading::after { animation-duration: 1.2s; }
5503
+ .btn.is-loading::after { --btn-spin-duration: 1.2s; }
2890
5504
  }
2891
5505
 
2892
5506
 
@@ -2918,6 +5532,7 @@ html {
2918
5532
  */
2919
5533
 
2920
5534
  .card {
5535
+ position: relative;
2921
5536
  background: var(--token-card);
2922
5537
  border: 1px solid var(--token-outline-soft);
2923
5538
  box-shadow: var(--token-shadow-inset);
@@ -2984,14 +5599,11 @@ html {
2984
5599
  transform: scale(1.04);
2985
5600
  }
2986
5601
  .card-media > img,
2987
- .card-media > video {
2988
- transition: transform var(--icen-tx-slow) var(--ease-out);
2989
- }
2990
- .card-media > img,
2991
5602
  .card-media > video {
2992
5603
  width: 100%;
2993
5604
  height: 100%;
2994
5605
  object-fit: cover;
5606
+ transition: transform var(--icen-tx-slow) var(--ease-out);
2995
5607
  }
2996
5608
  .card-tag {
2997
5609
  position: absolute;
@@ -3000,7 +5612,7 @@ html {
3000
5612
  padding: 2px var(--density-space-xs);
3001
5613
  font-size: var(--density-font-size-xs);
3002
5614
  font-weight: 600;
3003
- color: var(--token-on-accent, #fff);
5615
+ color: var(--token-on-accent);
3004
5616
  background: color-mix(in srgb, var(--token-text) 70%, transparent);
3005
5617
  border-radius: var(--radius-xs);
3006
5618
  backdrop-filter: blur(8px);
@@ -3114,7 +5726,6 @@ html {
3114
5726
  right: var(--density-space-sm);
3115
5727
  z-index: 1;
3116
5728
  }
3117
- .card { position: relative; }
3118
5729
 
3119
5730
 
3120
5731
  /* ── components/chart-area.css ── */
@@ -3216,6 +5827,16 @@ html {
3216
5827
  .chart-vbar-bar, .chart-hbar-bar { transition: none; }
3217
5828
  }
3218
5829
 
5830
+ /* ── 多系列:分组(簇内并排)与堆叠(列内向上叠)── */
5831
+ .chart-vbar-track--multi { gap: 2px; }
5832
+ .chart-vbar-track--multi > .chart-vbar-bar { flex: 1; min-width: 0; width: auto; }
5833
+ .chart-vbar-track--multi.chart-vbar-track--stacked {
5834
+ flex-direction: column;
5835
+ justify-content: flex-end;
5836
+ gap: 0;
5837
+ }
5838
+ .chart-vbar-track--multi.chart-vbar-track--stacked > .chart-vbar-bar { flex: none; width: 100%; }
5839
+
3219
5840
 
3220
5841
  /* ── components/chart-gauge.css ── */
3221
5842
  /*
@@ -3247,7 +5868,7 @@ html {
3247
5868
  gap: 1px;
3248
5869
  }
3249
5870
  .chart-gauge-value {
3250
- font-size: 20px;
5871
+ font-size: var(--density-font-size-2xl);
3251
5872
  font-weight: 700;
3252
5873
  font-variant-numeric: tabular-nums;
3253
5874
  color: var(--token-text);
@@ -3312,6 +5933,46 @@ html {
3312
5933
  .chart-heatmap-legend > span:first-child { margin-right: 2px; }
3313
5934
  .chart-heatmap-legend > span:last-child { margin-left: 2px; }
3314
5935
 
5936
+ /* ── 贡献日历(renderCalendar):月份标签 + 星期列 + 周格 ──
5937
+ * 布局 grid:左列星期标签(一/三/五/日),右列自上而下 月份行 / 周格 / 图例。
5938
+ * 月份标签按周列定位:left = col / columns * 100%。 */
5939
+ .chart-calendar {
5940
+ display: grid;
5941
+ grid-template-columns: 14px 1fr;
5942
+ grid-template-rows: 14px auto auto;
5943
+ column-gap: 4px;
5944
+ row-gap: 3px;
5945
+ }
5946
+ .chart-calendar-months {
5947
+ grid-column: 2;
5948
+ position: relative;
5949
+ font-size: var(--density-font-size-2xs);
5950
+ color: var(--token-text-faint);
5951
+ }
5952
+ .chart-calendar-month {
5953
+ position: absolute;
5954
+ top: 0;
5955
+ left: calc(var(--col, 0) / var(--columns, 1) * 100%);
5956
+ white-space: nowrap;
5957
+ }
5958
+ .chart-calendar-weekdays {
5959
+ grid-column: 1;
5960
+ grid-row: 2;
5961
+ display: grid;
5962
+ grid-template-rows: repeat(7, 1fr);
5963
+ font-size: var(--density-font-size-2xs);
5964
+ color: var(--token-text-faint);
5965
+ line-height: 1;
5966
+ }
5967
+ .chart-calendar .chart-heatmap-grid {
5968
+ grid-column: 2;
5969
+ grid-row: 2;
5970
+ }
5971
+ .chart-calendar .chart-heatmap-legend {
5972
+ grid-column: 2;
5973
+ grid-row: 3;
5974
+ }
5975
+
3315
5976
 
3316
5977
  /* ── components/chart-line.css ── */
3317
5978
  /*
@@ -3382,6 +6043,33 @@ html {
3382
6043
  .chart-radar { gap: var(--density-space-md); }
3383
6044
 
3384
6045
 
6046
+ /* ── components/chart-scatter.css ── */
6047
+ /*
6048
+ * @icen.ai/ui — Component: chart-scatter(散点/气泡图专属样式)
6049
+ * 依赖:charts.css(基座 .chart / tooltip / [data-chart-mark] 交互态)
6050
+ *
6051
+ * DOM 由 behaviors/charts.ts 的 renderScatter 构建:
6052
+ * .chart-scatter > svg(360×220 viewBox,网格 + 刻度 + circle[data-chart-mark])
6053
+ * 气泡第三维:points[].size 映射半径 3–10;hover/点击/右键走通用层委托。
6054
+ */
6055
+
6056
+ .chart-scatter svg {
6057
+ width: 100%;
6058
+ height: auto;
6059
+ display: block;
6060
+ }
6061
+ .chart-scatter circle[data-chart-mark] {
6062
+ transition: fill-opacity var(--icen-tx-fast);
6063
+ }
6064
+ .chart-scatter circle[data-chart-mark]:hover {
6065
+ fill-opacity: 0.85;
6066
+ }
6067
+
6068
+ @media (prefers-reduced-motion: reduce) {
6069
+ .chart-scatter circle[data-chart-mark] { transition: none; }
6070
+ }
6071
+
6072
+
3385
6073
  /* ── components/chart-sparkline.css ── */
3386
6074
  /*
3387
6075
  * @icen.ai/ui — Component: chart-sparkline(迷你趋势线专属样式)
@@ -3505,7 +6193,7 @@ html {
3505
6193
  flex: 1;
3506
6194
  min-width: 0;
3507
6195
  text-align: center;
3508
- font-size: 9px;
6196
+ font-size: var(--density-font-size-2xs);
3509
6197
  color: var(--token-text-faint);
3510
6198
  overflow: hidden;
3511
6199
  text-overflow: ellipsis;
@@ -3521,6 +6209,91 @@ html {
3521
6209
  .chart-tone-5 { --chart-tone: var(--token-info); }
3522
6210
  .chart-tone-6 { --chart-tone: color-mix(in srgb, var(--token-text) 50%, transparent); }
3523
6211
 
6212
+ /* ═══ 通用层(renderChart):标题 / 内置 tooltip / 标记交互 / 图例切换 ═══ */
6213
+
6214
+ /* 标题(spec.title 有值时渲染) */
6215
+ .chart-title {
6216
+ font-size: var(--density-font-size-sm);
6217
+ font-weight: 600;
6218
+ color: var(--token-text);
6219
+ }
6220
+
6221
+ /* 头部行(renderChart 渲染):标题 + 图例可见性小眼睛 */
6222
+ .chart-head {
6223
+ display: flex;
6224
+ align-items: center;
6225
+ justify-content: space-between;
6226
+ gap: var(--density-space-sm);
6227
+ min-height: 24px;
6228
+ }
6229
+ .chart-legend-eye {
6230
+ display: inline-flex;
6231
+ align-items: center;
6232
+ justify-content: center;
6233
+ width: 22px;
6234
+ height: 22px;
6235
+ padding: 0;
6236
+ border: 0;
6237
+ border-radius: var(--radius-xs);
6238
+ background: transparent;
6239
+ color: var(--token-text-faint);
6240
+ cursor: pointer;
6241
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
6242
+ }
6243
+ .chart-legend-eye:hover { color: var(--token-text); background: var(--token-fill-1); }
6244
+ .chart-legend-eye:focus-visible { outline: none; box-shadow: var(--icen-ring); }
6245
+ .chart-legend-eye svg { width: 13px; height: 13px; }
6246
+
6247
+ /* 图例统一隐藏:容器级继承——data-legend-hidden 一条选择器覆盖全部图型的图例 */
6248
+ [data-legend-hidden='true'] .chart-legend,
6249
+ [data-legend-hidden='true'] .chart-legend--toggle,
6250
+ [data-legend-hidden='true'] .chart-heatmap-legend {
6251
+ display: none;
6252
+ }
6253
+
6254
+ /* 内置 tooltip:portal 到 body 的单例,position: fixed 跟随指针 */
6255
+ .chart-tooltip {
6256
+ position: fixed;
6257
+ z-index: var(--z-tooltip);
6258
+ max-width: 280px;
6259
+ padding: 3px 8px;
6260
+ border-radius: var(--radius-sm);
6261
+ background: var(--token-card-strong);
6262
+ border: 1px solid var(--token-line);
6263
+ box-shadow: var(--token-shadow-elevated);
6264
+ font-size: var(--density-font-size-xs);
6265
+ font-variant-numeric: tabular-nums;
6266
+ color: var(--token-text);
6267
+ pointer-events: none;
6268
+ }
6269
+ .chart-tooltip[hidden] { display: none; }
6270
+
6271
+ /* 数据标记(各图型的条/点/段/格):交互态由委托层挂 .is-hover,图例切换挂 .is-off */
6272
+ [data-chart-mark] { cursor: pointer; }
6273
+ [data-chart-mark].is-hover { filter: brightness(1.12); }
6274
+ [data-chart-mark].is-off { opacity: 0.16; pointer-events: none; }
6275
+ svg circle[data-chart-mark].is-hover { stroke-width: 3; }
6276
+
6277
+ /* 可切换图例:行可点(Enter/Space 等价),.is-off 划线淡化 */
6278
+ .chart-legend--toggle .chart-legend-toggle-item {
6279
+ cursor: pointer;
6280
+ border-radius: var(--radius-xs);
6281
+ padding: 2px 4px;
6282
+ margin: -2px -4px;
6283
+ transition: background-color var(--icen-tx-fast);
6284
+ }
6285
+ .chart-legend--toggle .chart-legend-toggle-item:hover { background: var(--token-fill-1); }
6286
+ .chart-legend--toggle .chart-legend-toggle-item:focus-visible {
6287
+ outline: none;
6288
+ box-shadow: var(--icen-ring);
6289
+ }
6290
+ .chart-legend--toggle .is-off .chart-legend-label,
6291
+ .chart-legend--toggle .is-off .chart-legend-value {
6292
+ color: var(--token-text-faint);
6293
+ text-decoration: line-through;
6294
+ }
6295
+ .chart-legend--toggle .is-off .chart-legend-dot { opacity: 0.3; }
6296
+
3524
6297
 
3525
6298
  /* ── components/command-palette.css ── */
3526
6299
  /*
@@ -3713,7 +6486,7 @@ html {
3713
6486
 
3714
6487
  .command-palette-item-kbd {
3715
6488
  font-family: var(--font-mono);
3716
- font-size: 10px;
6489
+ font-size: var(--density-font-size-2xs);
3717
6490
  color: var(--token-text-faint);
3718
6491
  padding: 1px 5px;
3719
6492
  border: 1px solid var(--token-line-soft);
@@ -3744,7 +6517,7 @@ html {
3744
6517
  }
3745
6518
  .command-palette-foot-hint kbd {
3746
6519
  font-family: var(--font-mono);
3747
- font-size: 10px;
6520
+ font-size: var(--density-font-size-2xs);
3748
6521
  padding: 1px 4px;
3749
6522
  border: 1px solid var(--token-line-soft);
3750
6523
  border-radius: var(--radius-xs);
@@ -3802,7 +6575,7 @@ html {
3802
6575
  height: 16px;
3803
6576
  min-width: 16px;
3804
6577
  padding: 0 4px;
3805
- font-size: 10px;
6578
+ font-size: var(--density-font-size-2xs);
3806
6579
  }
3807
6580
  .kbd-group {
3808
6581
  display: inline-flex;
@@ -3966,11 +6739,11 @@ html {
3966
6739
  z-index: 1;
3967
6740
  }
3968
6741
  .timeline-dot svg { width: 13px; height: 13px; }
3969
- .timeline-dot--accent { background: var(--token-accent); color: var(--token-on-accent, #fff); border-color: transparent; }
3970
- .timeline-dot--success { background: var(--token-success); color: var(--token-on-accent, #fff); border-color: transparent; }
3971
- .timeline-dot--warning { background: var(--token-warning); color: var(--token-on-accent, #fff); border-color: transparent; }
3972
- .timeline-dot--error { background: var(--token-error); color: var(--token-on-accent, #fff); border-color: transparent; }
3973
- .timeline-dot--info { background: var(--token-info); color: var(--token-on-accent, #fff); border-color: transparent; }
6742
+ .timeline-dot--accent { background: var(--token-accent); color: var(--token-on-accent); border-color: transparent; }
6743
+ .timeline-dot--success { background: var(--token-success); color: var(--token-on-accent); border-color: transparent; }
6744
+ .timeline-dot--warning { background: var(--token-warning); color: var(--token-on-accent); border-color: transparent; }
6745
+ .timeline-dot--error { background: var(--token-error); color: var(--token-on-accent); border-color: transparent; }
6746
+ .timeline-dot--info { background: var(--token-info); color: var(--token-on-accent); border-color: transparent; }
3974
6747
 
3975
6748
  .timeline-body { flex: 1; min-width: 0; }
3976
6749
  .timeline-head {
@@ -4136,9 +6909,9 @@ html {
4136
6909
  min-width: 16px;
4137
6910
  height: 16px;
4138
6911
  padding: 0 4px;
4139
- font-size: 10px;
6912
+ font-size: var(--density-font-size-2xs);
4140
6913
  font-weight: 600;
4141
- color: var(--token-on-accent, #fff);
6914
+ color: var(--token-on-accent);
4142
6915
  background: var(--token-accent);
4143
6916
  border-radius: var(--radius-full);
4144
6917
  flex-shrink: 0;
@@ -4228,7 +7001,6 @@ html {
4228
7001
  /* 自定义 dot 尺寸 */
4229
7002
  .timeline-dot--sm { width: 20px; height: 20px; }
4230
7003
  .timeline-dot--sm svg { width: 10px; height: 10px; }
4231
- .timeline-dot--sm ~ .timeline-item:not(:last-child)::after,
4232
7004
  .timeline--sm .timeline-item:not(:last-child)::after { left: 10px; top: 24px; }
4233
7005
 
4234
7006
  /* 时间线自定义内容槽(放标签、卡片等) */
@@ -4374,17 +7146,17 @@ html {
4374
7146
  * <button class="copy-btn" data-copy="复制的文本">复制</button>
4375
7147
  * <button class="copy-btn copy-btn--ghost" data-copy="…">复制链接</button>
4376
7148
  *
4377
- * 图标变体(带 data-copy-icon,仅加 .done 类;由 CSS 显隐子 svg):
7149
+ * 图标变体(带 data-copy-icon,仅加 .is-done 类;由 CSS 显隐子 svg):
4378
7150
  * <button class="copy-btn copy-btn--icon" data-copy="…" data-copy-icon aria-label="复制">
4379
7151
  * <svg class="copy-icon-clipboard">…</svg>
4380
7152
  * <svg class="copy-icon-check" hidden>…</svg>
4381
7153
  * </button>
4382
7154
  *
4383
7155
  * behavior 自动处理:
4384
- * - 文本变体:成功后 textContent = '已复制' + 加 .done 类,1.4s 后还原
4385
- * - 图标变体(带 data-copy-icon):仅加 .done 类(不改文本),由 CSS 切换 svg 显隐
7156
+ * - 文本变体:成功后 textContent = '已复制' + 加 .is-done 类,1.4s 后还原
7157
+ * - 图标变体(带 data-copy-icon):仅加 .is-done 类(不改文本),由 CSS 切换 svg 显隐
4386
7158
  *
4387
- * 与 toast.ok 区别:copy 是"原地反馈"(按钮自身闪烁),toast 是"全局通知"。
7159
+ * 与 toast.toast--success 区别:copy 是"原地反馈"(按钮自身闪烁),toast 是"全局通知"。
4388
7160
  */
4389
7161
 
4390
7162
  .copy-btn {
@@ -4433,7 +7205,7 @@ html {
4433
7205
  }
4434
7206
 
4435
7207
  /* ── 成功闪烁态(behavior 1.4s 后移除)── */
4436
- .copy-btn.done {
7208
+ .copy-btn.is-done {
4437
7209
  color: var(--token-success);
4438
7210
  border-color: color-mix(in srgb, var(--token-success) 40%, transparent);
4439
7211
  background: color-mix(in srgb, var(--token-success) 10%, transparent);
@@ -4451,10 +7223,10 @@ html {
4451
7223
  flex-shrink: 0;
4452
7224
  }
4453
7225
 
4454
- /* .done 时显隐切换:剪贴板 → 对勾 */
7226
+ /* .is-done 时显隐切换:剪贴板 → 对勾 */
4455
7227
  .copy-btn[data-copy-icon] .copy-icon-check { display: none; }
4456
- .copy-btn[data-copy-icon].done .copy-icon-clipboard { display: none; }
4457
- .copy-btn[data-copy-icon].done .copy-icon-check { display: inline-block; }
7228
+ .copy-btn[data-copy-icon].is-done .copy-icon-clipboard { display: none; }
7229
+ .copy-btn[data-copy-icon].is-done .copy-icon-check { display: inline-block; }
4458
7230
 
4459
7231
  @media (prefers-reduced-motion: reduce) {
4460
7232
  .copy-btn { transition: none; }
@@ -4589,11 +7361,11 @@ html {
4589
7361
  .dt-cell--head.is-sortable:hover { color: var(--token-text); }
4590
7362
  .dt-cell--head:focus-visible {
4591
7363
  outline: none;
4592
- box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent);
7364
+ box-shadow: var(--icen-ring-inset);
4593
7365
  }
4594
7366
  .dt-head-title { overflow: hidden; text-overflow: ellipsis; }
4595
7367
  .dt-sort {
4596
- font-size: 10px;
7368
+ font-size: var(--density-font-size-2xs);
4597
7369
  color: var(--token-text-faint);
4598
7370
  flex-shrink: 0;
4599
7371
  }
@@ -4629,11 +7401,7 @@ html {
4629
7401
  border-radius: var(--radius-md);
4630
7402
  box-shadow: var(--token-shadow-overlay);
4631
7403
  font-weight: 400;
4632
- animation: dt-panel-in var(--icen-tx-fast);
4633
- }
4634
- @keyframes dt-panel-in {
4635
- from { opacity: 0; transform: scale(0.96) translateY(-4px); }
4636
- to { opacity: 1; transform: scale(1) translateY(0); }
7404
+ animation: icen-pop-in var(--icen-tx-fast);
4637
7405
  }
4638
7406
  .dt-filter-list {
4639
7407
  max-height: 220px;
@@ -4864,7 +7632,7 @@ html {
4864
7632
  .dt-resize:active::after {
4865
7633
  background: var(--token-accent);
4866
7634
  }
4867
- .dt-cell--head { /* 确保相对定位供 resize 绝对定位 */ }
7635
+ /* .dt-cell--head 已在主规则 position: relative,供 resize 手柄绝对定位 */
4868
7636
 
4869
7637
  /* ── 冻结列(左固定 sticky) ── */
4870
7638
  .datatable--frozen .dt-cell--frozen {
@@ -4997,11 +7765,7 @@ html {
4997
7765
  border-radius: var(--radius-md);
4998
7766
  box-shadow: var(--token-shadow-overlay);
4999
7767
  font-size: var(--density-font-size-sm);
5000
- animation: icen-dp-in var(--icen-tx-fast);
5001
- }
5002
- @keyframes icen-dp-in {
5003
- from { opacity: 0; transform: scale(0.96) translateY(-4px); }
5004
- to { opacity: 1; transform: scale(1) translateY(0); }
7768
+ animation: icen-pop-in var(--icen-tx-fast);
5005
7769
  }
5006
7770
 
5007
7771
  .date-picker-head {
@@ -5085,10 +7849,10 @@ html {
5085
7849
  }
5086
7850
  .date-picker-cell.is-selected {
5087
7851
  background: var(--token-accent);
5088
- color: var(--token-on-accent, #fff);
7852
+ color: var(--token-on-accent);
5089
7853
  font-weight: 600;
5090
7854
  }
5091
- .date-picker-cell.is-selected.is-today { border-color: var(--token-on-accent, #fff); }
7855
+ .date-picker-cell.is-selected.is-today { border-color: var(--token-on-accent); }
5092
7856
  .date-picker-cell.is-outside {
5093
7857
  color: var(--token-text-faint);
5094
7858
  opacity: 0.55;
@@ -5273,10 +8037,6 @@ html {
5273
8037
  .empty--404 .empty-title { font-size: 56px; }
5274
8038
  }
5275
8039
 
5276
- @media (prefers-reduced-motion: reduce) {
5277
- .empty { animation: none; }
5278
- }
5279
-
5280
8040
 
5281
8041
  /* ── components/feedback.css ── */
5282
8042
  /*
@@ -5430,7 +8190,7 @@ html {
5430
8190
  /* 不定进度(spinner 替代品):bar 来回扫动 */
5431
8191
  .progress--indeterminate .progress-bar {
5432
8192
  width: 35% !important;
5433
- animation: progress-sweep 1.4s var(--ease-in-out, var(--ease-out)) infinite;
8193
+ animation: progress-sweep var(--progress-sweep-duration, 1.4s) var(--ease-in-out, var(--ease-out)) infinite;
5434
8194
  }
5435
8195
  @keyframes progress-sweep {
5436
8196
  0% { transform: translateX(-100%); }
@@ -5454,7 +8214,7 @@ html {
5454
8214
  border: 2px solid color-mix(in srgb, var(--token-accent) 30%, transparent);
5455
8215
  border-top-color: var(--token-accent);
5456
8216
  border-radius: var(--radius-full);
5457
- animation: icen-spin 0.8s linear infinite;
8217
+ animation: icen-spin var(--spinner-duration, 0.8s) linear infinite;
5458
8218
  }
5459
8219
  .spinner--sm { width: 14px; height: 14px; border-width: 2px; }
5460
8220
  .spinner--lg { width: 32px; height: 32px; border-width: 3px; }
@@ -5462,8 +8222,6 @@ html {
5462
8222
  .spinner--error { border-color: color-mix(in srgb, var(--token-error) 30%, transparent); border-top-color: var(--token-error); }
5463
8223
  .spinner--muted { border-color: color-mix(in srgb, var(--token-text-faint) 30%, transparent); border-top-color: var(--token-text-faint); }
5464
8224
 
5465
- @keyframes icen-spin { to { transform: rotate(360deg); } }
5466
-
5467
8225
  /* 文字+spinner 组合 */
5468
8226
  .spinner-wrap {
5469
8227
  display: inline-flex;
@@ -5478,7 +8236,7 @@ html {
5478
8236
  display: block;
5479
8237
  background: color-mix(in srgb, var(--token-text) 8%, transparent);
5480
8238
  border-radius: var(--radius-sm);
5481
- animation: icen-skeleton-pulse 2s ease-in-out infinite;
8239
+ animation: icen-skeleton-pulse var(--skeleton-duration, 2s) ease-in-out infinite;
5482
8240
  }
5483
8241
  .skeleton--circle { border-radius: var(--radius-full); }
5484
8242
  .skeleton--text {
@@ -5487,11 +8245,6 @@ html {
5487
8245
  }
5488
8246
  .skeleton--text:last-child { width: 60%; }
5489
8247
 
5490
- @keyframes icen-skeleton-pulse {
5491
- 0%, 100% { opacity: 1; }
5492
- 50% { opacity: 0.5; }
5493
- }
5494
-
5495
8248
  /* ── alert 增强 ── */
5496
8249
 
5497
8250
  /* 可操作横幅(含按钮) */
@@ -5585,7 +8338,7 @@ html {
5585
8338
  transparent
5586
8339
  );
5587
8340
  background-size: 20px 20px;
5588
- animation: progress-stripe 1s linear infinite;
8341
+ animation: progress-stripe var(--progress-stripe-duration, 1s) linear infinite;
5589
8342
  }
5590
8343
  @keyframes progress-stripe {
5591
8344
  from { background-position: 0 0; }
@@ -5617,7 +8370,7 @@ html {
5617
8370
  height: 6px;
5618
8371
  border-radius: var(--radius-full);
5619
8372
  background: var(--token-accent);
5620
- animation: spinner-dot 1.4s ease-in-out infinite;
8373
+ animation: spinner-dot var(--spinner-dots-duration, 1.4s) ease-in-out infinite;
5621
8374
  }
5622
8375
  .spinner--dots span:nth-child(2) { animation-delay: 0.2s; }
5623
8376
  .spinner--dots span:nth-child(3) { animation-delay: 0.4s; }
@@ -5643,7 +8396,7 @@ html {
5643
8396
  height: 100%;
5644
8397
  background: var(--token-accent);
5645
8398
  border-radius: 1px;
5646
- animation: spinner-bar 1s ease-in-out infinite;
8399
+ animation: spinner-bar var(--spinner-bars-duration, 1s) ease-in-out infinite;
5647
8400
  }
5648
8401
  .spinner--bars span:nth-child(2) { animation-delay: 0.1s; }
5649
8402
  .spinner--bars span:nth-child(3) { animation-delay: 0.2s; }
@@ -5657,7 +8410,7 @@ html {
5657
8410
  .spinner-overlay {
5658
8411
  position: fixed;
5659
8412
  inset: 0;
5660
- z-index: var(--z-overlay, 9999);
8413
+ z-index: var(--z-dialog);
5661
8414
  display: flex;
5662
8415
  align-items: center;
5663
8416
  justify-content: center;
@@ -5703,7 +8456,7 @@ html {
5703
8456
  height: 12px;
5704
8457
  border-radius: var(--radius-xs);
5705
8458
  background: color-mix(in srgb, var(--token-text) 8%, transparent);
5706
- animation: icen-skeleton-pulse 2s ease-in-out infinite;
8459
+ animation: icen-skeleton-pulse var(--skeleton-duration, 2s) ease-in-out infinite;
5707
8460
  }
5708
8461
 
5709
8462
  /* 骨架波纹变体(shimmer,区别于 pulse) */
@@ -5715,7 +8468,7 @@ html {
5715
8468
  color-mix(in srgb, var(--token-text) 6%, transparent) 100%
5716
8469
  );
5717
8470
  background-size: 200% 100%;
5718
- animation: skeleton-shimmer 1.5s ease-in-out infinite;
8471
+ animation: skeleton-shimmer var(--skeleton-shimmer-duration, 1.5s) ease-in-out infinite;
5719
8472
  }
5720
8473
  @keyframes skeleton-shimmer {
5721
8474
  from { background-position: 200% 0; }
@@ -5727,7 +8480,7 @@ html {
5727
8480
  .progress--indeterminate .progress-bar { animation: none; width: 100% !important; }
5728
8481
  .progress--striped .progress-bar { animation: none; }
5729
8482
  .progress-ring-fill { transition: none; }
5730
- .spinner { animation-duration: 2s; }
8483
+ .spinner { --spinner-duration: 2s; }
5731
8484
  .spinner--dots span,
5732
8485
  .spinner--bars span { animation: none; }
5733
8486
  .skeleton,
@@ -5887,7 +8640,7 @@ html {
5887
8640
 
5888
8641
  /* label 带可选标记 */
5889
8642
  .form-label-optional {
5890
- font-size: 10px;
8643
+ font-size: var(--density-font-size-2xs);
5891
8644
  font-weight: 400;
5892
8645
  color: var(--token-text-faint);
5893
8646
  margin-left: 4px;
@@ -5968,11 +8721,11 @@ html {
5968
8721
  border: 2px solid color-mix(in srgb, var(--token-accent) 30%, transparent);
5969
8722
  border-top-color: var(--token-accent);
5970
8723
  border-radius: 50%;
5971
- animation: form-loading-spin 0.8s linear infinite;
8724
+ animation: icen-spin var(--form-loading-duration, 0.8s) linear infinite;
5972
8725
  }
5973
- @keyframes form-loading-spin { to { transform: rotate(360deg); } }
5974
8726
 
5975
- /* ── 页面级横幅:操作结果 / 引导提示 ── */
8727
+ /* ── 页面级横幅:操作结果 / 引导提示 ──
8728
+ 与 feedback.css 的 alert 共享同一套视觉 token(--banner-* 仅在底色/文字/描边上分流) */
5976
8729
  .banner-ok,
5977
8730
  .banner-err {
5978
8731
  display: flex;
@@ -5984,16 +8737,19 @@ html {
5984
8737
  font-size: var(--density-font-size-sm);
5985
8738
  line-height: 1.5;
5986
8739
  border: 1px solid transparent;
8740
+ background: var(--banner-bg);
8741
+ color: var(--banner-fg);
8742
+ border-color: var(--banner-border);
5987
8743
  }
5988
8744
  .banner-ok {
5989
- background: var(--token-success-muted);
5990
- color: var(--token-success);
5991
- border-color: color-mix(in srgb, var(--token-success) 25%, transparent);
8745
+ --banner-bg: var(--token-success-muted);
8746
+ --banner-fg: var(--token-success);
8747
+ --banner-border: color-mix(in srgb, var(--token-success) 25%, transparent);
5992
8748
  }
5993
8749
  .banner-err {
5994
- background: var(--token-error-muted);
5995
- color: var(--token-error);
5996
- border-color: color-mix(in srgb, var(--token-error) 25%, transparent);
8750
+ --banner-bg: var(--token-error-muted);
8751
+ --banner-fg: var(--token-error);
8752
+ --banner-border: color-mix(in srgb, var(--token-error) 25%, transparent);
5997
8753
  }
5998
8754
  /* block 模式:横向占满,带图标位 */
5999
8755
  .banner-block {
@@ -6001,10 +8757,6 @@ html {
6001
8757
  padding: var(--density-space-md);
6002
8758
  }
6003
8759
 
6004
- @media (prefers-reduced-motion: reduce) {
6005
- .banner-ok, .banner-err { transition: none; }
6006
- }
6007
-
6008
8760
 
6009
8761
  /* ── components/input.css ── */
6010
8762
  /*
@@ -6028,6 +8780,7 @@ html {
6028
8780
 
6029
8781
  /* ── 单行 ── */
6030
8782
  .input-wrap {
8783
+ position: relative;
6031
8784
  display: flex;
6032
8785
  align-items: center;
6033
8786
  gap: var(--density-space-xs);
@@ -6149,6 +8902,7 @@ html {
6149
8902
 
6150
8903
  /* ── 多行:同一 wrapper 视觉,内部 textarea 裸奔 ── */
6151
8904
  .textarea-wrap {
8905
+ position: relative;
6152
8906
  display: flex;
6153
8907
  width: 100%;
6154
8908
  background: var(--token-control);
@@ -6198,7 +8952,6 @@ html {
6198
8952
  }
6199
8953
 
6200
8954
  /* 字符计数(behavior 可挂到 .input-wrap 或 .textarea-wrap 内尾部) */
6201
- .input-wrap { position: relative; }
6202
8955
  .input-wrap--has-counter { padding-right: calc(var(--density-space-sm) + 48px); }
6203
8956
  .input-wrap--has-counter .input-counter {
6204
8957
  position: absolute;
@@ -6223,7 +8976,6 @@ html {
6223
8976
  .input-counter.is-warn { color: var(--token-warning); }
6224
8977
  .input-counter.is-error { color: var(--token-error); font-weight: 600; }
6225
8978
 
6226
- .textarea-wrap { position: relative; }
6227
8979
  .textarea-wrap .textarea { padding-bottom: 24px; }
6228
8980
 
6229
8981
  /* 有计数器时的 textarea 底部留空 */
@@ -6238,23 +8990,23 @@ html {
6238
8990
  /*
6239
8991
  * @icen.ai/ui — Component: layout(布局原语,全部是工具类,无 JS)
6240
8992
  * stack / divider / surface / text / grid / center / container / aspect / visually-hidden
6241
- * 全部消费 --token-*;档位类名与 opengal primitives/layout(-containers) 一一对应。
8993
+ * 全部消费 --token-* 与 --density-*;档位类名与 opengal primitives/layout(-containers) 一一对应。
6242
8994
  */
6243
8995
 
6244
- /* ── Stack:flex 堆叠(默认列向,gap 8px) ── */
6245
- .stack { display: flex; flex-direction: column; gap: 8px; }
8996
+ /* ── Stack:flex 堆叠(默认列向,gap = density-space-sm) ── */
8997
+ .stack { display: flex; flex-direction: column; gap: var(--density-space-sm); }
6246
8998
  .stack--row { flex-direction: row; }
6247
8999
  .stack--inline { display: inline-flex; }
6248
9000
  .stack--wrap { flex-wrap: wrap; }
6249
9001
  .stack--gap-0 { gap: 0; }
6250
- .stack--gap-1 { gap: 4px; }
6251
- .stack--gap-2 { gap: 8px; }
6252
- .stack--gap-3 { gap: 12px; }
6253
- .stack--gap-4 { gap: 16px; }
6254
- .stack--gap-6 { gap: 24px; }
6255
- .stack--gap-8 { gap: 32px; }
6256
- .stack--gap-12 { gap: 48px; }
6257
- .stack--gap-16 { gap: 64px; }
9002
+ .stack--gap-1 { gap: var(--density-space-xs); }
9003
+ .stack--gap-2 { gap: var(--density-space-sm); }
9004
+ .stack--gap-3 { gap: var(--density-space-md); }
9005
+ .stack--gap-4 { gap: var(--density-space-lg); }
9006
+ .stack--gap-6 { gap: var(--density-space-xl); }
9007
+ .stack--gap-8 { gap: var(--density-space-2xl); }
9008
+ .stack--gap-12 { gap: var(--density-space-3xl); }
9009
+ .stack--gap-16 { gap: var(--density-space-4xl); }
6258
9010
  .stack--items-start { align-items: flex-start; }
6259
9011
  .stack--items-center { align-items: center; }
6260
9012
  .stack--items-end { align-items: flex-end; }
@@ -6271,13 +9023,13 @@ html {
6271
9023
  .divider { background: var(--token-outline-soft); height: 1px; width: 100%; }
6272
9024
  .divider--vertical { width: 1px; height: 100%; align-self: stretch; }
6273
9025
 
6274
- /* ── Surface:卡片容器(默认 inset 阴影 + 16px padding) ── */
9026
+ /* ── Surface:卡片容器(默认 inset 阴影 + density-space-lg padding) ── */
6275
9027
  .surface {
6276
9028
  border-radius: var(--radius-lg);
6277
9029
  background: var(--token-card);
6278
9030
  border: 1px solid var(--token-outline-soft);
6279
9031
  box-shadow: var(--token-shadow-inset);
6280
- padding: 16px;
9032
+ padding: var(--density-space-lg);
6281
9033
  }
6282
9034
  .surface--elevated { box-shadow: var(--token-shadow-card); }
6283
9035
  .surface--overlay {
@@ -6286,18 +9038,18 @@ html {
6286
9038
  box-shadow: var(--token-shadow-overlay);
6287
9039
  }
6288
9040
  .surface--p-none { padding: 0; }
6289
- .surface--p-sm { padding: 12px; }
6290
- .surface--p-md { padding: 16px; }
6291
- .surface--p-lg { padding: 20px; }
9041
+ .surface--p-sm { padding: var(--density-space-md); }
9042
+ .surface--p-md { padding: var(--density-space-lg); }
9043
+ .surface--p-lg { padding: calc(var(--density-space-lg) + var(--density-space-xs)); }
6292
9044
 
6293
9045
  /* ── Text:字号 / 语义色 / 截断 ── */
6294
- .text--xs { font-size: 11px; }
6295
- .text--sm { font-size: 12px; }
6296
- .text--base { font-size: 13px; }
6297
- .text--md { font-size: 14px; }
6298
- .text--lg { font-size: 15px; }
6299
- .text--xl { font-size: 18px; }
6300
- .text--2xl { font-size: 22px; }
9046
+ .text--xs { font-size: var(--density-font-size-xs); }
9047
+ .text--sm { font-size: var(--density-font-size-sm); }
9048
+ .text--base { font-size: var(--density-font-size-md); }
9049
+ .text--md { font-size: var(--density-font-size-lg); }
9050
+ .text--lg { font-size: var(--density-font-size-lg); }
9051
+ .text--xl { font-size: var(--density-font-size-xl); }
9052
+ .text--2xl { font-size: var(--density-font-size-2xl); }
6301
9053
  .text--muted { color: var(--token-text-muted); }
6302
9054
  .text--faint { color: var(--token-text-faint); }
6303
9055
  .text--accent { color: var(--token-accent); }
@@ -6306,8 +9058,8 @@ html {
6306
9058
  .text--error { color: var(--token-error); }
6307
9059
  .text--truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6308
9060
 
6309
- /* ── Grid:栅格(默认 gap 12px) ── */
6310
- .grid { display: grid; gap: 12px; }
9061
+ /* ── Grid:栅格(默认 gap = density-space-md) ── */
9062
+ .grid { display: grid; gap: var(--density-space-md); }
6311
9063
  .grid--cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
6312
9064
  .grid--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
6313
9065
  .grid--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@@ -6315,28 +9067,28 @@ html {
6315
9067
  .grid--cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
6316
9068
  .grid--cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
6317
9069
  .grid--gap-0 { gap: 0; }
6318
- .grid--gap-4 { gap: 4px; }
6319
- .grid--gap-8 { gap: 8px; }
6320
- .grid--gap-12 { gap: 12px; }
6321
- .grid--gap-16 { gap: 16px; }
6322
- .grid--gap-24 { gap: 24px; }
9070
+ .grid--gap-4 { gap: var(--density-space-xs); }
9071
+ .grid--gap-8 { gap: var(--density-space-sm); }
9072
+ .grid--gap-12 { gap: var(--density-space-md); }
9073
+ .grid--gap-16 { gap: var(--density-space-lg); }
9074
+ .grid--gap-24 { gap: var(--density-space-xl); }
6323
9075
 
6324
9076
  /* ── Center:flex 居中盒 ── */
6325
9077
  .center { display: flex; align-items: center; justify-content: center; }
6326
9078
 
6327
9079
  /* ── Container:限宽容器(默认 960px) ── */
6328
- .container { width: 100%; margin-inline: auto; padding-inline: 16px; max-width: 960px; }
9080
+ .container { width: 100%; margin-inline: auto; padding-inline: var(--density-space-lg); max-width: 960px; }
6329
9081
  .container--sm { max-width: 480px; }
6330
9082
  .container--md { max-width: 720px; }
6331
9083
  .container--lg { max-width: 960px; }
6332
9084
  .container--xl { max-width: 1200px; }
6333
9085
  .container--full { max-width: none; }
6334
9086
 
6335
- /* ── Aspect:固定比例盒(padding-bottom hack,内容 absolute inset:0) ── */
6336
- .aspect { position: relative; width: 100%; overflow: hidden; padding-bottom: 56.25%; }
6337
- .aspect--square { padding-bottom: 100%; }
6338
- .aspect--4-3 { padding-bottom: 75%; }
6339
- .aspect--2-3 { padding-bottom: 150%; }
9087
+ /* ── Aspect:固定比例盒(aspect-ratio,内容 absolute inset:0 铺满) ── */
9088
+ .aspect { position: relative; width: 100%; overflow: hidden; aspect-ratio: 16 / 9; }
9089
+ .aspect--square { aspect-ratio: 1 / 1; }
9090
+ .aspect--4-3 { aspect-ratio: 4 / 3; }
9091
+ .aspect--2-3 { aspect-ratio: 2 / 3; }
6340
9092
  .aspect > * { position: absolute; inset: 0; }
6341
9093
 
6342
9094
  /* ── VisuallyHidden:屏幕阅读器专用文本(sr-only 等价) ── */
@@ -6353,6 +9105,10 @@ html {
6353
9105
  * 包含:avatar(头像)/ media-card(封面卡,滑出 overlay)/ rating(星级评分)/ carousel(轮播,与 behaviors/carousel 配套)
6354
9106
  * 规格源自 opengal data/display.tsx 的 Avatar、data/cartridge-card.tsx 的 hover overlay、
6355
9107
  * data/exhibit.tsx 的 Carousel 与 Rating。
9108
+ *
9109
+ * 例外声明(deliberate exception):carousel-thumbs 隐藏滚动条是有意设计
9110
+ * (缩略图条视觉纯净,滚动仍可用)。违反「组件不写滚动条样式」纪律仅此处,
9111
+ * 先例参照 scroll-area.css 的尺寸变体。
6356
9112
  */
6357
9113
 
6358
9114
  /* ══════════════ avatar · 头像 ══════════════ */
@@ -6372,8 +9128,8 @@ html {
6372
9128
  flex-shrink: 0;
6373
9129
  user-select: none;
6374
9130
  }
6375
- .avatar--xs { width: 20px; height: 20px; font-size: 9px; }
6376
- .avatar--sm { width: 28px; height: 28px; font-size: 10px; }
9131
+ .avatar--xs { width: 20px; height: 20px; font-size: var(--density-font-size-2xs); }
9132
+ .avatar--sm { width: 28px; height: 28px; font-size: var(--density-font-size-2xs); }
6377
9133
  .avatar--lg { width: 48px; height: 48px; font-size: var(--density-font-size-md); }
6378
9134
  .avatar--xl { width: 80px; height: 80px; font-size: var(--density-font-size-lg); }
6379
9135
  .avatar img {
@@ -6417,7 +9173,7 @@ html {
6417
9173
  /* 渐变底头像 */
6418
9174
  .avatar--gradient {
6419
9175
  background: linear-gradient(135deg, var(--token-accent), var(--token-accent-deep, var(--token-accent)));
6420
- color: var(--token-on-accent, #fff);
9176
+ color: var(--token-on-accent);
6421
9177
  }
6422
9178
 
6423
9179
  /* 方形头像 */
@@ -6435,7 +9191,7 @@ html {
6435
9191
  justify-content: center;
6436
9192
  width: 28px;
6437
9193
  height: 28px;
6438
- font-size: 10px;
9194
+ font-size: var(--density-font-size-2xs);
6439
9195
  font-weight: 600;
6440
9196
  color: var(--token-text-muted);
6441
9197
  background: var(--token-fill-2);
@@ -6457,7 +9213,7 @@ html {
6457
9213
  .media-card:hover {
6458
9214
  transform: translateY(-6px);
6459
9215
  box-shadow:
6460
- 0 8px 32px color-mix(in srgb, var(--token-text) 18%, transparent),
9216
+ var(--token-shadow-overlay),
6461
9217
  0 0 20px var(--token-accent-glow);
6462
9218
  }
6463
9219
  .media-card-cover {
@@ -6485,7 +9241,7 @@ html {
6485
9241
  }
6486
9242
  .media-card:hover .media-card-overlay {
6487
9243
  transform: translateY(0);
6488
- transition-delay: 80ms;
9244
+ transition-delay: var(--media-overlay-delay, 80ms);
6489
9245
  }
6490
9246
  .media-card-overlay-title {
6491
9247
  font-size: var(--density-font-size-md);
@@ -6690,7 +9446,7 @@ html {
6690
9446
  outline: none;
6691
9447
  box-shadow: var(--icen-ring);
6692
9448
  }
6693
- .carousel-dot.active,
9449
+ .carousel-dot.is-active,
6694
9450
  .carousel-dot[aria-current='true'] {
6695
9451
  width: 20px;
6696
9452
  background: var(--token-accent);
@@ -6706,7 +9462,7 @@ html {
6706
9462
  transition: width var(--icen-tx-slow);
6707
9463
  }
6708
9464
 
6709
- /* 缩略图导航条 */
9465
+ /* 缩略图导航条(deliberate exception:隐藏滚动条是设计意图,见文件头注释) */
6710
9466
  .carousel-thumbs {
6711
9467
  display: flex;
6712
9468
  gap: var(--density-space-xs);
@@ -6816,7 +9572,7 @@ html {
6816
9572
  border: 1px solid var(--token-outline);
6817
9573
  box-shadow: var(--token-shadow-overlay);
6818
9574
  border-radius: var(--radius-lg);
6819
- animation: icen-menu-in var(--icen-tx-fast);
9575
+ animation: icen-menu-in var(--menu-in-duration, var(--icen-tx-fast));
6820
9576
  }
6821
9577
  .menu[data-side='top'] { animation-name: icen-menu-in-top; }
6822
9578
  .menu[data-side='left'] { animation-name: icen-menu-in-left; }
@@ -6827,7 +9583,7 @@ html {
6827
9583
  min-width: 218px;
6828
9584
  max-width: 286px;
6829
9585
  z-index: var(--z-tooltip); /* 上下文菜单高于普通 popover,与 tooltip 平齐 */
6830
- animation-duration: 100ms;
9586
+ --menu-in-duration: 100ms;
6831
9587
  }
6832
9588
 
6833
9589
  /* ── 分组标签 / 头(context-menu 的 header 项) ── */
@@ -7092,7 +9848,7 @@ html {
7092
9848
  border: 0;
7093
9849
  cursor: pointer;
7094
9850
  color: var(--token-text-faint);
7095
- font-size: 16px;
9851
+ font-size: var(--density-font-size-lg);
7096
9852
  line-height: 1;
7097
9853
  border-radius: var(--radius-sm);
7098
9854
  transition:
@@ -7231,7 +9987,12 @@ html {
7231
9987
  backdrop-filter: blur(12px) saturate(1.1);
7232
9988
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
7233
9989
  border-bottom: 1px solid var(--token-outline-soft);
7234
- transition: all var(--icen-tx-slow);
9990
+ transition:
9991
+ background-color var(--icen-tx-slow),
9992
+ border-color var(--icen-tx-slow),
9993
+ box-shadow var(--icen-tx-slow),
9994
+ transform var(--icen-tx-slow),
9995
+ backdrop-filter var(--icen-tx-slow);
7235
9996
  }
7236
9997
  .nav:has(.nav-bottom) {
7237
9998
  height: auto;
@@ -7288,6 +10049,7 @@ html {
7288
10049
  .nav-bottom { width: 100%; padding-top: var(--density-space-xs); }
7289
10050
 
7290
10051
  .nav-btn {
10052
+ position: relative;
7291
10053
  display: inline-flex;
7292
10054
  align-items: center;
7293
10055
  gap: var(--density-space-xs);
@@ -7319,7 +10081,7 @@ html {
7319
10081
  .nav-btn.is-active,
7320
10082
  .nav-btn[aria-current='page'] {
7321
10083
  background: var(--token-accent);
7322
- color: var(--token-on-accent, #fff);
10084
+ color: var(--token-on-accent);
7323
10085
  font-weight: 500;
7324
10086
  box-shadow: var(--token-shadow-elevated);
7325
10087
  }
@@ -7389,7 +10151,7 @@ html {
7389
10151
  .nav-search-kbd {
7390
10152
  margin-left: auto;
7391
10153
  padding: 1px 5px;
7392
- font-size: 10px;
10154
+ font-size: var(--density-font-size-2xs);
7393
10155
  font-family: var(--font-mono);
7394
10156
  color: var(--token-text-faint);
7395
10157
  background: var(--token-fill-2);
@@ -7409,14 +10171,13 @@ html {
7409
10171
  border-radius: var(--radius-full);
7410
10172
  pointer-events: none;
7411
10173
  }
7412
- .nav-btn { position: relative; }
7413
10174
  .nav-btn-badge--count {
7414
10175
  min-width: 16px;
7415
10176
  height: 16px;
7416
10177
  padding: 0 4px;
7417
- font-size: 9px;
10178
+ font-size: var(--density-font-size-2xs);
7418
10179
  font-weight: 700;
7419
- color: var(--token-on-accent, #fff);
10180
+ color: var(--token-on-accent);
7420
10181
  display: inline-flex;
7421
10182
  align-items: center;
7422
10183
  justify-content: center;
@@ -7478,7 +10239,7 @@ html {
7478
10239
  *
7479
10240
  * 完整契约(由 behavior 渲染):
7480
10241
  * <div class="notification-container" role="region" aria-label="通知" data-position="top-right">
7481
- * <div class="notification [notif--success|warning|error|info]
10242
+ * <div class="notification [notification--success|warning|error|info]
7482
10243
  * [.is-priority] [.is-unread] [.is-progress] [.is-clickable] [.is-paused]
7483
10244
  * [.has-avatar]" role="alert|button">
7484
10245
  * <span class="notification-icon">…svg 或 img.notification-avatar…</span>
@@ -7547,19 +10308,19 @@ html {
7547
10308
  overflow: hidden; /* 倒计时进度条贴底 */
7548
10309
  animation: icen-notif-in var(--icen-tx-normal);
7549
10310
  }
7550
- .notification.notif--success { border-left-color: var(--token-success); }
7551
- .notification.notif--warning { border-left-color: var(--token-warning); }
7552
- .notification.notif--error { border-left-color: var(--token-error); }
7553
- .notification.notif--info { border-left-color: var(--token-info); }
10311
+ .notification.notification--success { border-left-color: var(--token-success); }
10312
+ .notification.notification--warning { border-left-color: var(--token-warning); }
10313
+ .notification.notification--error { border-left-color: var(--token-error); }
10314
+ .notification.notification--info { border-left-color: var(--token-info); }
7554
10315
 
7555
10316
  /* ── 优先级:高优先级强调(左侧加粗 + 更强阴影)── */
7556
10317
  .notification.is-priority {
7557
10318
  border-left-width: 4px;
7558
10319
  box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-accent) 24%, transparent);
7559
10320
  }
7560
- .notification.is-priority.notif--success { box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-success) 28%, transparent); }
7561
- .notification.is-priority.notif--warning { box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-warning) 28%, transparent); }
7562
- .notification.is-priority.notif--error { box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-error) 28%, transparent); }
10321
+ .notification.is-priority.notification--success { box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-success) 28%, transparent); }
10322
+ .notification.is-priority.notification--warning { box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-warning) 28%, transparent); }
10323
+ .notification.is-priority.notification--error { box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-error) 28%, transparent); }
7563
10324
 
7564
10325
  /* ── 未读:标题左侧 4px 强调点 + 加粗 ── */
7565
10326
  .notification.is-unread .notification-title::before {
@@ -7574,9 +10335,9 @@ html {
7574
10335
  background: var(--token-accent);
7575
10336
  flex-shrink: 0;
7576
10337
  }
7577
- .notification.is-unread.notif--success .notification-title::before { background: var(--token-success); }
7578
- .notification.is-unread.notif--warning .notification-title::before { background: var(--token-warning); }
7579
- .notification.is-unread.notif--error .notification-title::before { background: var(--token-error); }
10338
+ .notification.is-unread.notification--success .notification-title::before { background: var(--token-success); }
10339
+ .notification.is-unread.notification--warning .notification-title::before { background: var(--token-warning); }
10340
+ .notification.is-unread.notification--error .notification-title::before { background: var(--token-error); }
7580
10341
 
7581
10342
  /* ── 可点击:整条 hover 抬升 ── */
7582
10343
  .notification.is-clickable {
@@ -7604,10 +10365,10 @@ html {
7604
10365
  color: var(--token-text-faint);
7605
10366
  }
7606
10367
  .notification-icon svg { width: 16px; height: 16px; }
7607
- .notif--success .notification-icon { color: var(--token-success); }
7608
- .notif--warning .notification-icon { color: var(--token-warning); }
7609
- .notif--error .notification-icon { color: var(--token-error); }
7610
- .notif--info .notification-icon { color: var(--token-info); }
10368
+ .notification--success .notification-icon { color: var(--token-success); }
10369
+ .notification--warning .notification-icon { color: var(--token-warning); }
10370
+ .notification--error .notification-icon { color: var(--token-error); }
10371
+ .notification--info .notification-icon { color: var(--token-info); }
7611
10372
 
7612
10373
  /* 头像(圆形图) */
7613
10374
  .notification-avatar {
@@ -7624,7 +10385,7 @@ html {
7624
10385
  }
7625
10386
 
7626
10387
  /* 进度通知的图标默认走品牌色 */
7627
- .notification.is-progress:not(.notif--success):not(.notif--warning):not(.notif--error) .notification-icon {
10388
+ .notification.is-progress:not(.notification--success):not(.notification--warning):not(.notification--error) .notification-icon {
7628
10389
  color: var(--token-accent);
7629
10390
  }
7630
10391
 
@@ -7690,23 +10451,23 @@ html {
7690
10451
  /* primary:实底 accent */
7691
10452
  .notification-action--primary {
7692
10453
  background: var(--token-accent);
7693
- color: var(--token-on-accent, #fff);
10454
+ color: var(--token-on-accent);
7694
10455
  border-color: var(--token-accent);
7695
10456
  }
7696
10457
  .notification-action--primary:hover {
7697
10458
  background: var(--token-accent-deep, var(--token-accent));
7698
- color: var(--token-on-accent, #fff);
10459
+ color: var(--token-on-accent);
7699
10460
  border-color: var(--token-accent-deep, var(--token-accent));
7700
10461
  }
7701
10462
  /* danger:实底 error */
7702
10463
  .notification-action--danger {
7703
10464
  background: var(--token-error);
7704
- color: var(--token-on-accent, #fff);
10465
+ color: var(--token-on-accent);
7705
10466
  border-color: var(--token-error);
7706
10467
  }
7707
10468
  .notification-action--danger:hover {
7708
10469
  filter: brightness(0.92);
7709
- color: var(--token-on-accent, #fff);
10470
+ color: var(--token-on-accent);
7710
10471
  border-color: var(--token-error);
7711
10472
  }
7712
10473
 
@@ -7751,9 +10512,9 @@ html {
7751
10512
  border-radius: var(--radius-full);
7752
10513
  transition: width var(--icen-tx-normal) var(--ease-out);
7753
10514
  }
7754
- .notification.notif--success .notification-progress-bar { background: var(--token-success); }
7755
- .notification.notif--warning .notification-progress-bar { background: var(--token-warning); }
7756
- .notification.notif--error .notification-progress-bar { background: var(--token-error); }
10515
+ .notification.notification--success .notification-progress-bar { background: var(--token-success); }
10516
+ .notification.notification--warning .notification-progress-bar { background: var(--token-warning); }
10517
+ .notification.notification--error .notification-progress-bar { background: var(--token-error); }
7757
10518
  .notification-progress-label {
7758
10519
  flex: 0 0 auto;
7759
10520
  font-size: var(--density-font-size-xs);
@@ -7809,9 +10570,9 @@ html {
7809
10570
  animation-fill-mode: forwards;
7810
10571
  animation-play-state: running;
7811
10572
  }
7812
- .notification.notif--success .notification-countdown-fill { background: var(--token-success); opacity: 0.6; }
7813
- .notification.notif--warning .notification-countdown-fill { background: var(--token-warning); opacity: 0.6; }
7814
- .notification.notif--error .notification-countdown-fill { background: var(--token-error); opacity: 0.6; }
10573
+ .notification.notification--success .notification-countdown-fill { background: var(--token-success); opacity: 0.6; }
10574
+ .notification.notification--warning .notification-countdown-fill { background: var(--token-warning); opacity: 0.6; }
10575
+ .notification.notification--error .notification-countdown-fill { background: var(--token-error); opacity: 0.6; }
7815
10576
  .notification.is-paused .notification-countdown-fill {
7816
10577
  animation-play-state: paused;
7817
10578
  }
@@ -7826,7 +10587,7 @@ html {
7826
10587
  }
7827
10588
 
7828
10589
  /* ── 退场动画 ── */
7829
- .notification.leaving {
10590
+ .notification.is-leaving {
7830
10591
  opacity: 0;
7831
10592
  transform: translateX(20px);
7832
10593
  transition:
@@ -7850,7 +10611,7 @@ html {
7850
10611
 
7851
10612
  @media (prefers-reduced-motion: reduce) {
7852
10613
  .notification { animation: none; }
7853
- .notification.leaving { transform: none; }
10614
+ .notification.is-leaving { transform: none; }
7854
10615
  .notification-countdown-fill { animation: none; transform-origin: left; }
7855
10616
  .notification-progress-bar { transition: none; }
7856
10617
  }
@@ -7907,7 +10668,7 @@ html {
7907
10668
  .page-btn.is-active,
7908
10669
  .page-btn[aria-current='page'] {
7909
10670
  background: var(--token-accent);
7910
- color: var(--token-on-accent, #fff);
10671
+ color: var(--token-on-accent);
7911
10672
  font-weight: 500;
7912
10673
  box-shadow: var(--token-shadow-elevated);
7913
10674
  }
@@ -7982,8 +10743,8 @@ html {
7982
10743
  * @icen.ai/ui — Component: panel(白卡片容器 + 标题栏 + 可选 footer)
7983
10744
  *
7984
10745
  * 完整契约:
7985
- * <div class="panel-block [panel-block--flush] [panel-block--accent]
7986
- * [panel-block--collapsible] [.is-collapsed]">
10746
+ * <div class="panel [panel--flush] [panel--accent]
10747
+ * [panel--collapsible] [.is-collapsed]">
7987
10748
  * <div class="panel-title">
7988
10749
  * <div class="panel-title-text">
7989
10750
  * <span>标题</span>
@@ -8004,10 +10765,10 @@ html {
8004
10765
  * --collapsible:标题栏出现展开/折叠钮(behavior 可选,也可靠纯 CSS details/summary)。
8005
10766
  * --accent:强调色标题边。
8006
10767
  * .panel-subtitle:标题下方的副标题/描述文字。
8007
- * .panel-block.is-collapsed:折叠态——隐藏 body + foot。
10768
+ * .panel.is-collapsed:折叠态——隐藏 body + foot。
8008
10769
  */
8009
10770
 
8010
- .panel-block {
10771
+ .panel {
8011
10772
  background: var(--token-surface);
8012
10773
  border: 1px solid var(--token-line-soft);
8013
10774
  border-radius: var(--radius-lg);
@@ -8043,7 +10804,7 @@ html {
8043
10804
  white-space: nowrap;
8044
10805
  }
8045
10806
  .panel-subtitle {
8046
- font-size: 11px;
10807
+ font-size: var(--density-font-size-xs);
8047
10808
  font-weight: 400;
8048
10809
  letter-spacing: 0;
8049
10810
  text-transform: none;
@@ -8087,19 +10848,19 @@ html {
8087
10848
  height: 14px;
8088
10849
  transition: transform var(--icen-tx-fast);
8089
10850
  }
8090
- .panel-block.is-collapsed .panel-toggle-icon {
10851
+ .panel.is-collapsed .panel-toggle-icon {
8091
10852
  transform: rotate(-90deg);
8092
10853
  }
8093
10854
 
8094
10855
  /* ── 折叠态:隐藏 body + foot,去掉标题栏底边 ── */
8095
- .panel-block.is-collapsed .panel-body,
8096
- .panel-block.is-collapsed .panel-foot {
10856
+ .panel.is-collapsed .panel-body,
10857
+ .panel.is-collapsed .panel-foot {
8097
10858
  display: none;
8098
10859
  }
8099
- .panel-block.is-collapsed .panel-title {
10860
+ .panel.is-collapsed .panel-title {
8100
10861
  border-bottom-color: transparent;
8101
10862
  }
8102
- .panel-block--collapsible .panel-body {
10863
+ .panel--collapsible .panel-body {
8103
10864
  animation: panel-expand var(--icen-tx-normal) var(--ease-out);
8104
10865
  }
8105
10866
  @keyframes panel-expand {
@@ -8113,7 +10874,7 @@ html {
8113
10874
  font-size: var(--density-font-size-md);
8114
10875
  line-height: 1.6;
8115
10876
  }
8116
- .panel-block--flush .panel-body { padding: 0; }
10877
+ .panel--flush .panel-body { padding: 0; }
8117
10878
 
8118
10879
  .panel-foot {
8119
10880
  display: flex;
@@ -8126,26 +10887,26 @@ html {
8126
10887
  }
8127
10888
 
8128
10889
  /* ── 强调变体 ── */
8129
- .panel-block--accent { border-color: color-mix(in srgb, var(--token-accent) 30%, transparent); }
8130
- .panel-block--accent .panel-title {
10890
+ .panel--accent { border-color: color-mix(in srgb, var(--token-accent) 30%, transparent); }
10891
+ .panel--accent .panel-title {
8131
10892
  color: var(--token-accent);
8132
10893
  background: var(--token-accent-subtle);
8133
10894
  }
8134
10895
 
8135
10896
  /* ── 平面变体(无阴影,嵌套时用) ── */
8136
- .panel-block--flat {
10897
+ .panel--flat {
8137
10898
  box-shadow: none;
8138
10899
  background: var(--token-bg-soft);
8139
10900
  }
8140
10901
 
8141
10902
  /* ── 可折叠模式:标题栏可点(纯 CSS 方案用 details/summary) ── */
8142
- .panel-block--collapsible .panel-title {
10903
+ .panel--collapsible .panel-title {
8143
10904
  cursor: pointer;
8144
10905
  }
8145
10906
 
8146
10907
  @media (prefers-reduced-motion: reduce) {
8147
10908
  .panel-toggle-icon { transition: none; }
8148
- .panel-block--collapsible .panel-body { animation: none; }
10909
+ .panel--collapsible .panel-body { animation: none; }
8149
10910
  }
8150
10911
 
8151
10912
 
@@ -8154,7 +10915,7 @@ html {
8154
10915
  * @icen.ai/ui — Component: pill(状态徽章 / tag)
8155
10916
  *
8156
10917
  * 完整契约:
8157
- * <span class="pill [ok|warn|bad|accent|info] [.pill--dot|pill--outline|pill--sm]">文本</span>
10918
+ * <span class="pill [pill--success|pill--warning|pill--error|pill--info|pill--accent] [.pill--dot|pill--outline|pill--sm]">文本</span>
8158
10919
  *
8159
10920
  * 语义色纪律:仅绿/黄/红/信息 + 品牌 accent。
8160
10921
  * --dot:左侧加状态色圆点;--outline:透明底、纯描边;--sm:紧凑尺寸。
@@ -8176,27 +10937,27 @@ html {
8176
10937
  vertical-align: middle;
8177
10938
  }
8178
10939
 
8179
- .pill.ok {
10940
+ .pill--success {
8180
10941
  color: var(--token-success);
8181
10942
  border-color: color-mix(in srgb, var(--token-success) 35%, transparent);
8182
10943
  background: color-mix(in srgb, var(--token-success) 9%, transparent);
8183
10944
  }
8184
- .pill.warn {
10945
+ .pill--warning {
8185
10946
  color: var(--token-warning);
8186
10947
  border-color: color-mix(in srgb, var(--token-warning) 35%, transparent);
8187
10948
  background: color-mix(in srgb, var(--token-warning) 9%, transparent);
8188
10949
  }
8189
- .pill.bad {
10950
+ .pill--error {
8190
10951
  color: var(--token-error);
8191
10952
  border-color: color-mix(in srgb, var(--token-error) 35%, transparent);
8192
10953
  background: color-mix(in srgb, var(--token-error) 9%, transparent);
8193
10954
  }
8194
- .pill.accent {
10955
+ .pill--accent {
8195
10956
  color: var(--token-accent-deep, var(--token-accent));
8196
10957
  border-color: color-mix(in srgb, var(--token-accent) 40%, transparent);
8197
10958
  background: color-mix(in srgb, var(--token-accent) 9%, transparent);
8198
10959
  }
8199
- .pill.info {
10960
+ .pill--info {
8200
10961
  color: var(--token-info);
8201
10962
  border-color: color-mix(in srgb, var(--token-info) 35%, transparent);
8202
10963
  background: color-mix(in srgb, var(--token-info) 9%, transparent);
@@ -8221,7 +10982,7 @@ html {
8221
10982
  /* 尺寸变体 */
8222
10983
  .pill--sm {
8223
10984
  padding: 1px var(--density-space-xs);
8224
- font-size: 10px;
10985
+ font-size: var(--density-font-size-2xs);
8225
10986
  }
8226
10987
  .pill--lg {
8227
10988
  padding: 4px calc(var(--density-space-sm) + 2px);
@@ -8231,13 +10992,13 @@ html {
8231
10992
  /* 实色变体:accent 实底(用于 count、活跃状态等强调场景) */
8232
10993
  .pill--solid {
8233
10994
  background: var(--token-accent);
8234
- color: var(--token-on-accent, #fff);
10995
+ color: var(--token-on-accent);
8235
10996
  border-color: var(--token-accent);
8236
10997
  }
8237
- .pill--solid.ok { background: var(--token-success); border-color: var(--token-success); color: var(--token-on-accent, #fff); }
8238
- .pill--solid.warn { background: var(--token-warning); border-color: var(--token-warning); color: var(--token-on-accent, #fff); }
8239
- .pill--solid.bad { background: var(--token-error); border-color: var(--token-error); color: var(--token-on-accent, #fff); }
8240
- .pill--solid.info { background: var(--token-info); border-color: var(--token-info); color: var(--token-on-accent, #fff); }
10998
+ .pill--solid.pill--success { background: var(--token-success); border-color: var(--token-success); }
10999
+ .pill--solid.pill--warning { background: var(--token-warning); border-color: var(--token-warning); }
11000
+ .pill--solid.pill--error { background: var(--token-error); border-color: var(--token-error); }
11001
+ .pill--solid.pill--info { background: var(--token-info); border-color: var(--token-info); }
8241
11002
 
8242
11003
  /* ── 增强 ── */
8243
11004
 
@@ -8278,7 +11039,7 @@ html {
8278
11039
  border-radius: var(--radius-full);
8279
11040
  background: currentColor;
8280
11041
  margin-right: 2px;
8281
- animation: pill-pulse 1.5s ease-in-out infinite;
11042
+ animation: pill-pulse var(--pill-pulse-duration, 1.5s) ease-in-out infinite;
8282
11043
  }
8283
11044
  @keyframes pill-pulse {
8284
11045
  0%, 100% { opacity: 1; transform: scale(1); }
@@ -8291,7 +11052,7 @@ html {
8291
11052
  height: 18px;
8292
11053
  padding: 0 5px;
8293
11054
  justify-content: center;
8294
- font-size: 10px;
11055
+ font-size: var(--density-font-size-2xs);
8295
11056
  font-weight: 700;
8296
11057
  font-variant-numeric: tabular-nums;
8297
11058
  line-height: 1;
@@ -8300,7 +11061,7 @@ html {
8300
11061
  /* 渐变变体 */
8301
11062
  .pill--gradient {
8302
11063
  background: linear-gradient(135deg, var(--token-accent), var(--token-accent-deep, var(--token-accent)));
8303
- color: var(--token-on-accent, #fff);
11064
+ color: var(--token-on-accent);
8304
11065
  border-color: transparent;
8305
11066
  }
8306
11067
 
@@ -8630,7 +11391,7 @@ html {
8630
11391
  .segmented-item.is-active,
8631
11392
  .segmented-item[aria-checked='true'] {
8632
11393
  background: var(--token-accent);
8633
- color: var(--token-on-accent, #fff);
11394
+ color: var(--token-on-accent);
8634
11395
  box-shadow: var(--token-shadow-elevated);
8635
11396
  }
8636
11397
  .segmented-item:disabled,
@@ -8688,7 +11449,7 @@ html {
8688
11449
  .toggle-item.is-active,
8689
11450
  .toggle-item[aria-checked='true'] {
8690
11451
  background: var(--token-accent);
8691
- color: var(--token-on-accent, #fff);
11452
+ color: var(--token-on-accent);
8692
11453
  box-shadow: var(--token-shadow-card);
8693
11454
  }
8694
11455
  .toggle-item:disabled,
@@ -8739,7 +11500,7 @@ html {
8739
11500
  }
8740
11501
  .stepper-btn:focus-visible {
8741
11502
  outline: none;
8742
- box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent);
11503
+ box-shadow: var(--icen-ring-inset);
8743
11504
  }
8744
11505
  .stepper-btn:disabled,
8745
11506
  .stepper-btn[aria-disabled='true'] {
@@ -8783,7 +11544,7 @@ html {
8783
11544
  height: 48px;
8784
11545
  padding: 0;
8785
11546
  text-align: center;
8786
- font-size: 18px;
11547
+ font-size: var(--density-font-size-xl);
8787
11548
  font-weight: 600;
8788
11549
  font-family: var(--font-mono);
8789
11550
  color: var(--token-text);
@@ -8812,7 +11573,7 @@ html {
8812
11573
  cursor: not-allowed;
8813
11574
  }
8814
11575
  .otp--sm .otp-cell { width: 32px; height: 40px; font-size: var(--density-font-size-lg); }
8815
- .otp--lg .otp-cell { width: 48px; height: 56px; font-size: 22px; }
11576
+ .otp--lg .otp-cell { width: 48px; height: 56px; font-size: var(--density-font-size-2xl); }
8816
11577
 
8817
11578
  @media (prefers-reduced-motion: reduce) {
8818
11579
  .segmented-item, .toggle-item, .stepper-btn, .stepper, .otp-cell { transition: none; }
@@ -8923,7 +11684,8 @@ html {
8923
11684
  * </div>
8924
11685
  *
8925
11686
  * behavior 维护:
8926
- * - .select.is-open ↔ aria-expanded;panel [hidden] 切换;ESC/外部点击关闭
11687
+ * - .select.is-open ↔ aria-expanded;panel portal 到 body(inline left/top/width/max-height,
11688
+ * 带 data-side="top|bottom";多选时带 data-select-multiple);ESC/外部点击关闭
8927
11689
  * - 选项 .is-focused 跟随键盘箭头;Enter 选中 → 写回 .select-value / .is-selected
8928
11690
  * - data-select-search:面板顶部自动插入搜索框,实时过滤(含 data-keywords),空分组自动隐藏
8929
11691
  * - data-select-multiple:点击切选不关面板,trigger 显示计数,hidden input 同步逗号分隔值
@@ -8932,7 +11694,6 @@ html {
8932
11694
  .select {
8933
11695
  position: relative;
8934
11696
  width: 100%;
8935
- isolation: isolate; /* panel 不被后面兄弟节点的层叠遮挡 */
8936
11697
  }
8937
11698
 
8938
11699
  .select-trigger {
@@ -9002,12 +11763,11 @@ html {
9002
11763
  }
9003
11764
  .select.is-open .select-chevron { transform: rotate(180deg); }
9004
11765
 
11766
+ /* 面板由 behavior portal 到 body:left/top/width/max-height 由 JS 按 trigger rect inline 写入;
11767
+ data-side="top|bottom" 标记翻侧方向;多选时 panel 带 data-select-multiple 属性。 */
9005
11768
  .select-panel {
9006
11769
  position: absolute;
9007
- top: calc(100% + 4px);
9008
- left: 0;
9009
- min-width: 100%;
9010
- max-height: 240px;
11770
+ z-index: var(--z-popover);
9011
11771
  overflow-y: auto;
9012
11772
  overscroll-behavior: contain;
9013
11773
  padding: 4px;
@@ -9015,17 +11775,10 @@ html {
9015
11775
  border: 1px solid var(--token-outline);
9016
11776
  border-radius: var(--radius-md);
9017
11777
  box-shadow: var(--token-shadow-overlay);
9018
- z-index: var(--z-popover);
9019
11778
  transform-origin: top center;
9020
- animation: select-panel-in var(--icen-tx-fast);
9021
- }
9022
- @keyframes select-panel-in {
9023
- from { opacity: 0; transform: scale(0.96) translateY(-4px); }
9024
- to { opacity: 1; transform: scale(1) translateY(0); }
9025
- }
9026
- @media (prefers-reduced-motion: reduce) {
9027
- .select-panel { animation: none; }
11779
+ animation: icen-pop-in var(--icen-tx-fast);
9028
11780
  }
11781
+ .select-panel[data-side='top'] { transform-origin: bottom center; }
9029
11782
 
9030
11783
  /* 选项 */
9031
11784
  .select-option {
@@ -9127,7 +11880,7 @@ html {
9127
11880
  border-radius: var(--radius-xs);
9128
11881
  background: transparent;
9129
11882
  color: var(--token-text-faint);
9130
- font-size: 15px;
11883
+ font-size: var(--density-font-size-lg);
9131
11884
  line-height: 1;
9132
11885
  cursor: pointer;
9133
11886
  transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
@@ -9146,8 +11899,8 @@ html {
9146
11899
  flex-wrap: wrap;
9147
11900
  }
9148
11901
 
9149
- /* 多选项 checkbox 指示器(选项左侧) */
9150
- .select[data-select-multiple] .select-option::before {
11902
+ /* 多选项 checkbox 指示器(选项左侧;panel portal 到 body,属性在 panel 上) */
11903
+ .select-panel[data-select-multiple] .select-option::before {
9151
11904
  content: '';
9152
11905
  width: 14px;
9153
11906
  height: 14px;
@@ -9156,11 +11909,11 @@ html {
9156
11909
  border-radius: var(--radius-xs);
9157
11910
  transition: background-color var(--icen-tx-fast), border-color var(--icen-tx-fast);
9158
11911
  }
9159
- .select[data-select-multiple] .select-option.is-selected::before {
11912
+ .select-panel[data-select-multiple] .select-option.is-selected::before {
9160
11913
  background: var(--token-accent);
9161
11914
  border-color: var(--token-accent);
9162
11915
  }
9163
- .select[data-select-multiple] .select-option.is-selected::after {
11916
+ .select-panel[data-select-multiple] .select-option.is-selected::after {
9164
11917
  content: '✓';
9165
11918
  margin-left: auto;
9166
11919
  }
@@ -9345,7 +12098,7 @@ html {
9345
12098
  padding: 0 5px;
9346
12099
  font-size: var(--density-font-size-xs);
9347
12100
  font-weight: 600;
9348
- color: var(--token-on-accent, #fff);
12101
+ color: var(--token-on-accent);
9349
12102
  background: var(--token-accent);
9350
12103
  border-radius: var(--radius-full);
9351
12104
  font-variant-numeric: tabular-nums;
@@ -9883,7 +12636,6 @@ html {
9883
12636
  }
9884
12637
  .split-pane--vertical .split-pane-first {
9885
12638
  flex: 0 0 var(--split, 50%);
9886
- flex-basis: var(--split, 50%);
9887
12639
  min-height: 0;
9888
12640
  }
9889
12641
  .split-pane--vertical .split-pane-second {
@@ -9922,10 +12674,10 @@ html {
9922
12674
  * <div class="stat-card [.stat-card--interactive] [.stat-card--horizontal]">
9923
12675
  * <div class="stat-head">
9924
12676
  * <span class="stat-label">用户总数</span>
9925
- * <span class="stat-icon [.icon--ok|.icon--warn|.icon--bad|.icon--info]>…svg…</span>
12677
+ * <span class="stat-icon [.icon--success|.icon--warning|.icon--error|.icon--info]>…svg…</span>
9926
12678
  * </div>
9927
12679
  * <div class="stat-body">
9928
- * <div class="stat-num [.accent|.warn|.bad|.info]">0</div>
12680
+ * <div class="stat-num [--accent|--warning|--error|--info]">0</div>
9929
12681
  * <div class="stat-delta [.up|.down|.flat]"> ← 可选趋势
9930
12682
  * <svg>…arrow…</svg> +12.4%
9931
12683
  * <span class="stat-period">比上周</span>
@@ -10001,10 +12753,10 @@ html {
10001
12753
  background: var(--token-accent-subtle);
10002
12754
  color: var(--token-accent);
10003
12755
  }
10004
- .stat-icon.icon--ok { background: color-mix(in srgb, var(--token-success) 14%, transparent); color: var(--token-success); }
10005
- .stat-icon.icon--warn { background: color-mix(in srgb, var(--token-warning) 14%, transparent); color: var(--token-warning); }
10006
- .stat-icon.icon--bad { background: color-mix(in srgb, var(--token-error) 14%, transparent); color: var(--token-error); }
10007
- .stat-icon.icon--info { background: color-mix(in srgb, var(--token-info) 14%, transparent); color: var(--token-info); }
12756
+ .stat-icon.icon--success { background: color-mix(in srgb, var(--token-success) 14%, transparent); color: var(--token-success); }
12757
+ .stat-icon.icon--warning { background: color-mix(in srgb, var(--token-warning) 14%, transparent); color: var(--token-warning); }
12758
+ .stat-icon.icon--error { background: color-mix(in srgb, var(--token-error) 14%, transparent); color: var(--token-error); }
12759
+ .stat-icon.icon--info { background: color-mix(in srgb, var(--token-info) 14%, transparent); color: var(--token-info); }
10008
12760
  .stat-icon svg { width: 14px; height: 14px; }
10009
12761
 
10010
12762
  /* ── 内容体(数字 + 趋势) ── */
@@ -10015,17 +12767,17 @@ html {
10015
12767
  }
10016
12768
 
10017
12769
  .stat-num {
10018
- font-size: 24px;
12770
+ font-size: var(--density-font-size-2xl);
10019
12771
  font-weight: 700;
10020
12772
  line-height: 1.2;
10021
12773
  font-variant-numeric: tabular-nums lining-nums;
10022
12774
  color: var(--token-text);
10023
12775
  word-break: break-all;
10024
12776
  }
10025
- .stat-num.accent { color: var(--token-accent); }
10026
- .stat-num.warn { color: var(--token-warning); }
10027
- .stat-num.bad { color: var(--token-error); }
10028
- .stat-num.info { color: var(--token-info); }
12777
+ .stat-num--accent { color: var(--token-accent); }
12778
+ .stat-num--warning { color: var(--token-warning); }
12779
+ .stat-num--error { color: var(--token-error); }
12780
+ .stat-num--info { color: var(--token-info); }
10029
12781
 
10030
12782
  .stat-delta {
10031
12783
  display: inline-flex;
@@ -10050,7 +12802,7 @@ html {
10050
12802
  gap: 2px;
10051
12803
  }
10052
12804
  .stat-card--sm .stat-num { font-size: var(--density-font-size-lg); }
10053
- .stat-card--sm .stat-label { font-size: 10px; }
12805
+ .stat-card--sm .stat-label { font-size: var(--density-font-size-2xs); }
10054
12806
 
10055
12807
  /* ── 横向布局(dashboard 顶栏:标签左 + 数字右) ── */
10056
12808
  .stat-card--horizontal {
@@ -10131,20 +12883,16 @@ html {
10131
12883
  margin: 0 1px;
10132
12884
  }
10133
12885
 
10134
- /* ── 加载态骨架 ── */
12886
+ /* ── 加载态骨架(脉冲 keyframes 见 base.css 的 icen-skeleton-pulse) ── */
10135
12887
  .stat-card.is-loading .stat-num,
10136
12888
  .stat-card.is-loading .stat-delta {
10137
12889
  color: transparent;
10138
12890
  background: var(--token-fill-2);
10139
12891
  border-radius: var(--radius-xs);
10140
- animation: stat-skeleton 1.5s ease-in-out infinite;
12892
+ animation: icen-skeleton-pulse var(--stat-skeleton-duration, 1.5s) ease-in-out infinite;
10141
12893
  }
10142
12894
  .stat-card.is-loading .stat-num { height: 28px; }
10143
12895
  .stat-card.is-loading .stat-delta { height: 14px; width: 60%; }
10144
- @keyframes stat-skeleton {
10145
- 0%, 100% { opacity: 1; }
10146
- 50% { opacity: 0.5; }
10147
- }
10148
12896
  @media (prefers-reduced-motion: reduce) {
10149
12897
  .stat-card.is-loading .stat-num,
10150
12898
  .stat-card.is-loading .stat-delta { animation: none; }
@@ -10216,7 +12964,7 @@ html {
10216
12964
  }
10217
12965
  .step--finish .step-dot {
10218
12966
  background: var(--token-accent);
10219
- color: var(--token-on-accent, #fff);
12967
+ color: var(--token-on-accent);
10220
12968
  }
10221
12969
  .step--process .step-dot {
10222
12970
  background: var(--token-accent-subtle);
@@ -10307,7 +13055,7 @@ html {
10307
13055
  width: 16px;
10308
13056
  height: 16px;
10309
13057
  }
10310
- .step--finish .step-dot-icon { color: var(--token-on-accent, #fff); }
13058
+ .step--finish .step-dot-icon { color: var(--token-on-accent); }
10311
13059
  .step--process .step-dot-icon { color: var(--token-accent); }
10312
13060
  .step--wait .step-dot-icon { color: var(--token-text-faint); }
10313
13061
  .step--error .step-dot-icon { color: var(--token-error); }
@@ -10449,8 +13197,8 @@ html {
10449
13197
  width: var(--switch-thumb);
10450
13198
  height: var(--switch-thumb);
10451
13199
  border-radius: var(--radius-full);
10452
- background: var(--token-on-accent, #fff);
10453
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
13200
+ background: var(--token-on-accent);
13201
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--token-text) 15%, transparent);
10454
13202
  transition: transform var(--icen-tx-bounce);
10455
13203
  }
10456
13204
  .switch[aria-checked='true'] .switch-thumb {
@@ -10461,10 +13209,6 @@ html {
10461
13209
  .switch--sm { --switch-w: 36px; --switch-h: 20px; --switch-thumb: 16px; }
10462
13210
  .switch--lg { --switch-w: 56px; --switch-h: 28px; --switch-thumb: 24px; }
10463
13211
 
10464
- @media (prefers-reduced-motion: reduce) {
10465
- .switch-thumb { transition: none; }
10466
- }
10467
-
10468
13212
  /* ── 勾选框(✓ 与半选横条均 ::before 自绘) ── */
10469
13213
  .checkbox {
10470
13214
  --checkbox-size: 16px;
@@ -10494,7 +13238,7 @@ html {
10494
13238
  .checkbox[aria-checked='mixed'] {
10495
13239
  background: var(--token-accent);
10496
13240
  border-color: var(--token-accent);
10497
- color: var(--token-on-accent, #fff);
13241
+ color: var(--token-on-accent);
10498
13242
  }
10499
13243
  .checkbox::before {
10500
13244
  content: '';
@@ -10614,7 +13358,7 @@ html {
10614
13358
  display: flex;
10615
13359
  align-items: center;
10616
13360
  justify-content: center;
10617
- font-size: 9px;
13361
+ font-size: var(--density-font-size-2xs);
10618
13362
  font-weight: 700;
10619
13363
  color: transparent;
10620
13364
  }
@@ -10624,9 +13368,9 @@ html {
10624
13368
  left: 6px;
10625
13369
  top: 50%;
10626
13370
  transform: translateY(-50%);
10627
- font-size: 9px;
13371
+ font-size: var(--density-font-size-2xs);
10628
13372
  font-weight: 700;
10629
- color: var(--token-on-accent, #fff);
13373
+ color: var(--token-on-accent);
10630
13374
  }
10631
13375
  .switch--text[aria-checked='false']::before {
10632
13376
  content: 'OFF';
@@ -10634,7 +13378,7 @@ html {
10634
13378
  right: 6px;
10635
13379
  top: 50%;
10636
13380
  transform: translateY(-50%);
10637
- font-size: 9px;
13381
+ font-size: var(--density-font-size-2xs);
10638
13382
  font-weight: 700;
10639
13383
  color: var(--token-text-faint);
10640
13384
  }
@@ -10694,7 +13438,7 @@ html {
10694
13438
  background-color var(--icen-tx-fast);
10695
13439
  }
10696
13440
  .radio-card:hover {
10697
- border-color: var(--token-hover-border, color-mix(in srgb, var(--token-accent) 30%, transparent));
13441
+ border-color: var(--icen-hover-border);
10698
13442
  }
10699
13443
  .radio-card-head {
10700
13444
  display: flex;
@@ -10750,7 +13494,7 @@ html {
10750
13494
  *
10751
13495
  * 完整契约:
10752
13496
  * <div class="table-wrap [table-wrap--bordered]">
10753
- * <table class="admin-table [--zebra|--compact|--dense] [--sticky-col]">
13497
+ * <table class="table [--zebra|--compact|--dense] [--sticky-col]">
10754
13498
  * <caption class="table-caption">标题说明</caption> ← 可选
10755
13499
  * <thead>
10756
13500
  * <tr><th [colspan=rowspan] [data-sort]>列名</th>…</tr>
@@ -10770,9 +13514,9 @@ html {
10770
13514
  * </div>
10771
13515
  *
10772
13516
  * .table-wrap:窄屏横向滚动(滚动条样式见 base.css);--bordered 加外边框。
10773
- * .admin-table--zebra:斑马纹(奇数行底色)。
10774
- * .admin-table--compact:紧凑(cell padding 缩小);--dense:极紧凑(行高更紧)。
10775
- * .admin-table--sticky-col:首列水平粘滞(配合 td/th .col-fixed,长表横向滚动时保持可见)。
13517
+ * .table--zebra:斑马纹(奇数行底色)。
13518
+ * .table--compact:紧凑(cell padding 缩小);--dense:极紧凑(行高更紧)。
13519
+ * .table--sticky-col:首列水平粘滞(配合 td/th .col-fixed,长表横向滚动时保持可见)。
10776
13520
  * .row-expand[hidden]:配合 .row-link.is-expanded 控制展开行显隐。
10777
13521
  */
10778
13522
 
@@ -10785,18 +13529,18 @@ html {
10785
13529
  border: 1px solid var(--token-line-soft);
10786
13530
  }
10787
13531
 
10788
- .admin-table {
13532
+ .table {
10789
13533
  width: 100%;
10790
13534
  border-collapse: collapse;
10791
13535
  font-size: var(--density-font-size-md);
10792
13536
  font-variant-numeric: tabular-nums;
10793
13537
  }
10794
- .admin-table th,
10795
- .admin-table td {
13538
+ .table th,
13539
+ .table td {
10796
13540
  padding: var(--density-space-sm) calc(var(--density-space-md) + 2px);
10797
13541
  text-align: left;
10798
13542
  }
10799
- .admin-table th {
13543
+ .table th {
10800
13544
  font-weight: 600;
10801
13545
  font-size: var(--density-font-size-xs);
10802
13546
  letter-spacing: 0.05em;
@@ -10807,27 +13551,27 @@ html {
10807
13551
  background: var(--token-bg-soft);
10808
13552
  position: sticky;
10809
13553
  top: 0;
10810
- z-index: 1;
13554
+ z-index: var(--z-raised);
10811
13555
  }
10812
- .admin-table td {
13556
+ .table td {
10813
13557
  border-bottom: 1px solid var(--token-line-soft);
10814
13558
  vertical-align: middle;
10815
13559
  color: var(--token-text);
10816
13560
  }
10817
- .admin-table tbody tr:last-child td { border-bottom: 0; }
10818
- .admin-table tbody tr {
13561
+ .table tbody tr:last-child td { border-bottom: 0; }
13562
+ .table tbody tr {
10819
13563
  transition: background-color var(--icen-tx-fast);
10820
13564
  }
10821
- .admin-table tbody tr:hover {
13565
+ .table tbody tr:hover {
10822
13566
  background: var(--token-surface-strong);
10823
13567
  }
10824
- .admin-table tbody tr.row-link {
13568
+ .table tbody tr.row-link {
10825
13569
  cursor: pointer;
10826
13570
  }
10827
- .admin-table tbody tr.is-selected {
13571
+ .table tbody tr.is-selected {
10828
13572
  background: var(--token-accent-subtle);
10829
13573
  }
10830
- .admin-table tfoot td {
13574
+ .table tfoot td {
10831
13575
  border-top: 1px solid var(--token-line);
10832
13576
  border-bottom: 0;
10833
13577
  font-weight: 500;
@@ -10835,51 +13579,50 @@ html {
10835
13579
  }
10836
13580
 
10837
13581
  /* 紧凑变体 */
10838
- .admin-table--compact th,
10839
- .admin-table--compact td {
13582
+ .table--compact th,
13583
+ .table--compact td {
10840
13584
  padding: var(--density-space-xs) var(--density-space-sm);
10841
13585
  }
10842
- .admin-table--dense th,
10843
- .admin-table--dense td {
13586
+ .table--dense th,
13587
+ .table--dense td {
10844
13588
  padding: 4px var(--density-space-xs);
10845
13589
  font-size: var(--density-font-size-sm);
10846
13590
  }
10847
13591
 
10848
13592
  /* 右对齐数字列 */
10849
- .admin-table td.num {
13593
+ .table td.num {
10850
13594
  text-align: right;
10851
13595
  font-variant-numeric: tabular-nums lining-nums;
10852
13596
  }
10853
13597
 
10854
- /* 操作列 */
10855
- .admin-table .ops {
13598
+ /* 操作列(.btn-sm 必伴 .btn,无需单独列) */
13599
+ .table .ops {
10856
13600
  white-space: nowrap;
10857
13601
  text-align: right;
10858
13602
  }
10859
- .admin-table .ops .btn,
10860
- .admin-table .ops .btn-sm {
13603
+ .table .ops .btn {
10861
13604
  margin-left: var(--density-space-xs);
10862
13605
  }
10863
13606
 
10864
13607
  /* 可排序表头(使用方 data-sort 指示) */
10865
- .admin-table th[data-sort] {
13608
+ .table th[data-sort] {
10866
13609
  cursor: pointer;
10867
13610
  user-select: none;
10868
13611
  }
10869
- .admin-table th[data-sort]::after {
13612
+ .table th[data-sort]::after {
10870
13613
  content: '↕';
10871
13614
  margin-left: var(--density-space-xs);
10872
13615
  color: var(--token-text-faint);
10873
13616
  opacity: 0.6;
10874
13617
  }
10875
- .admin-table th[data-sort='asc']::after { content: '↑'; opacity: 1; color: var(--token-accent); }
10876
- .admin-table th[data-sort='desc']::after { content: '↓'; opacity: 1; color: var(--token-accent); }
13618
+ .table th[data-sort='asc']::after { content: '↑'; opacity: 1; color: var(--token-accent); }
13619
+ .table th[data-sort='desc']::after { content: '↓'; opacity: 1; color: var(--token-accent); }
10877
13620
 
10878
13621
  /* ── 斑马纹 ── */
10879
- .admin-table--zebra tbody tr:nth-child(odd) {
13622
+ .table--zebra tbody tr:nth-child(odd) {
10880
13623
  background: color-mix(in srgb, var(--token-bg-soft) 50%, transparent);
10881
13624
  }
10882
- .admin-table--zebra tbody tr:hover {
13625
+ .table--zebra tbody tr:hover {
10883
13626
  background: var(--token-surface-strong);
10884
13627
  }
10885
13628
 
@@ -10894,30 +13637,30 @@ html {
10894
13637
  }
10895
13638
 
10896
13639
  /* ── 首列粘滞(长表横向滚动时保持可见) ── */
10897
- .admin-table--sticky-col th:first-child,
10898
- .admin-table--sticky-col .col-fixed {
13640
+ .table--sticky-col th:first-child,
13641
+ .table--sticky-col .col-fixed {
10899
13642
  position: sticky;
10900
13643
  left: 0;
10901
- z-index: 2;
13644
+ z-index: calc(var(--z-raised) + 1);
10902
13645
  background: var(--token-bg-soft);
10903
- box-shadow: 2px 0 4px color-mix(in srgb, var(--token-shadow-card-base, rgba(0,0,0,0.06)) 40%, transparent);
13646
+ box-shadow: 2px 0 4px color-mix(in srgb, var(--token-text) 5%, transparent);
10904
13647
  }
10905
- .admin-table--sticky-col tbody td.col-fixed {
13648
+ .table--sticky-col tbody td.col-fixed {
10906
13649
  background: var(--token-surface);
10907
13650
  }
10908
- .admin-table--sticky-col tbody tr:hover td.col-fixed {
13651
+ .table--sticky-col tbody tr:hover td.col-fixed {
10909
13652
  background: var(--token-surface-strong);
10910
13653
  }
10911
13654
 
10912
13655
  /* ── 展开行 ── */
10913
- .admin-table .row-expand > td {
13656
+ .table .row-expand > td {
10914
13657
  padding: 0 var(--density-space-md);
10915
13658
  background: var(--token-bg-soft);
10916
13659
  border-bottom: 1px solid var(--token-line-soft);
10917
13660
  font-size: var(--density-font-size-sm);
10918
13661
  color: var(--token-text-muted);
10919
13662
  }
10920
- .admin-table .row-expand[hidden] { display: none; }
13663
+ .table .row-expand[hidden] { display: none; }
10921
13664
  /* 展开行的内容容器(使用方可加 padding/动画) */
10922
13665
  .row-expand-content {
10923
13666
  padding: var(--density-space-sm) 0;
@@ -10929,22 +13672,22 @@ html {
10929
13672
  }
10930
13673
 
10931
13674
  /* ── 列宽工具类 ── */
10932
- .admin-table .col-narrow { width: 80px; }
10933
- .admin-table .col-wide { width: 240px; }
10934
- .admin-table .col-action { width: 1%; white-space: nowrap; }
13675
+ .table .col-narrow { width: 80px; }
13676
+ .table .col-wide { width: 240px; }
13677
+ .table .col-action { width: 1%; white-space: nowrap; }
10935
13678
 
10936
13679
  /* ── 行状态色 ── */
10937
- .admin-table tbody tr.row-success { background: color-mix(in srgb, var(--token-success) 6%, transparent); }
10938
- .admin-table tbody tr.row-warning { background: color-mix(in srgb, var(--token-warning) 6%, transparent); }
10939
- .admin-table tbody tr.row-error { background: color-mix(in srgb, var(--token-error) 6%, transparent); }
13680
+ .table tbody tr.row-success { background: color-mix(in srgb, var(--token-success) 6%, transparent); }
13681
+ .table tbody tr.row-warning { background: color-mix(in srgb, var(--token-warning) 6%, transparent); }
13682
+ .table tbody tr.row-error { background: color-mix(in srgb, var(--token-error) 6%, transparent); }
10940
13683
 
10941
13684
  /* ── 粘滞表头阴影提示 ── */
10942
- .admin-table thead th {
13685
+ .table thead th {
10943
13686
  box-shadow: 0 1px 0 var(--token-line);
10944
13687
  }
10945
13688
 
10946
13689
  /* ── 表头分组(rowspan/colspan 样式) ── */
10947
- .admin-table th[rowspan] { vertical-align: bottom; }
13690
+ .table th[rowspan] { vertical-align: bottom; }
10948
13691
 
10949
13692
  @media (prefers-reduced-motion: reduce) {
10950
13693
  .row-expand-content { animation: none; }
@@ -10956,20 +13699,19 @@ html {
10956
13699
  * @icen.ai/ui — Component: tabs(分区导航 / 子导航)
10957
13700
  *
10958
13701
  * 完整契约:
10959
- * <div class="page-tabs" role="tablist"> ← 页面内分区
10960
- * <button class="page-tab [.active]" role="tab" [aria-selected="true"]>…</button>
13702
+ * <div class="tabs" role="tablist"> ← 页面内分区
13703
+ * <button class="page-tab [.is-active]" role="tab" [aria-selected="true"]>…</button>
10961
13704
  * </div>
10962
13705
  *
10963
- * <nav class="admin-tabs" role="navigation"> ← 顶部子导航(a 链接)
10964
- * <a class="[.active]" [aria-current="page"] href="…">…</a>
13706
+ * <nav class="tabs" role="navigation"> ← 顶部子导航(a 链接)
13707
+ * <a class="[.is-active]" [aria-current="page"] href="…">…</a>
10965
13708
  * </nav>
10966
13709
  *
10967
13710
  * 与 behaviors/tabs 配套:[data-tabs] > [data-tab] + [data-tab-panel],行为层维护 aria。
10968
- * 窄屏换行不滚动(CLAUDE.md 纪律:tab 条永不出现滚动条)。
13711
+ * 窄屏换行不滚动(纪律:tab 条永不出现滚动条,一律 flex-wrap: wrap)。
10969
13712
  */
10970
13713
 
10971
- .page-tabs,
10972
- .admin-tabs {
13714
+ .tabs {
10973
13715
  display: flex;
10974
13716
  gap: 2px;
10975
13717
  flex-wrap: wrap;
@@ -11001,14 +13743,14 @@ html {
11001
13743
  border-radius: var(--radius-sm);
11002
13744
  box-shadow: var(--icen-ring);
11003
13745
  }
11004
- .page-tab.active,
13746
+ .page-tab.is-active,
11005
13747
  .page-tab[aria-selected='true'] {
11006
13748
  color: var(--token-accent);
11007
13749
  border-bottom-color: var(--token-accent);
11008
13750
  font-weight: 700;
11009
13751
  }
11010
13752
 
11011
- .admin-tabs a {
13753
+ .tabs a {
11012
13754
  padding: var(--density-space-sm) var(--density-space-md);
11013
13755
  font-size: var(--density-font-size-md);
11014
13756
  color: var(--token-text-muted);
@@ -11020,44 +13762,44 @@ html {
11020
13762
  color var(--icen-tx-fast),
11021
13763
  border-color var(--icen-tx-fast);
11022
13764
  }
11023
- .admin-tabs a:hover {
13765
+ .tabs a:hover {
11024
13766
  color: var(--token-accent);
11025
13767
  text-decoration: none;
11026
13768
  }
11027
- .admin-tabs a:focus-visible {
13769
+ .tabs a:focus-visible {
11028
13770
  outline: none;
11029
13771
  border-radius: var(--radius-sm);
11030
13772
  box-shadow: var(--icen-ring);
11031
13773
  }
11032
- .admin-tabs a.active,
11033
- .admin-tabs a[aria-current='page'] {
13774
+ .tabs a.is-active,
13775
+ .tabs a[aria-current='page'] {
11034
13776
  color: var(--token-accent);
11035
13777
  border-bottom-color: var(--token-accent);
11036
13778
  }
11037
13779
 
11038
- /* 卡片变体(可选 .page-tabs--cards):tab 变成 pill,没有底边线 */
11039
- .page-tabs--cards {
13780
+ /* 卡片变体(可选 .tabs--cards):tab 变成 pill,没有底边线 */
13781
+ .tabs--cards {
11040
13782
  border-bottom: 0;
11041
13783
  gap: var(--density-space-xs);
11042
13784
  margin-bottom: var(--density-space-md);
11043
13785
  }
11044
- .page-tabs--cards .page-tab {
13786
+ .tabs--cards .page-tab {
11045
13787
  padding: var(--density-space-xs) var(--density-space-md);
11046
13788
  border: 1px solid var(--token-outline-soft);
11047
13789
  border-radius: var(--radius-full);
11048
13790
  margin-bottom: 0;
11049
13791
  }
11050
- .page-tabs--cards .page-tab.active,
11051
- .page-tabs--cards .page-tab[aria-selected='true'] {
13792
+ .tabs--cards .page-tab.is-active,
13793
+ .tabs--cards .page-tab[aria-selected='true'] {
11052
13794
  background: var(--token-accent);
11053
- color: var(--token-on-accent, #fff);
13795
+ color: var(--token-on-accent);
11054
13796
  border-color: var(--token-accent);
11055
13797
  font-weight: 500;
11056
13798
  }
11057
13799
 
11058
13800
  /* 等宽变体:tab 平均占满容器 */
11059
- .page-tabs--equal .page-tab,
11060
- .admin-tabs--equal a {
13801
+ .tabs--equal .page-tab,
13802
+ .tabs--equal a {
11061
13803
  flex: 1;
11062
13804
  text-align: center;
11063
13805
  }
@@ -11065,11 +13807,11 @@ html {
11065
13807
  /* ── 增强 ── */
11066
13808
 
11067
13809
  /* 尺寸变体 */
11068
- .page-tabs--sm .page-tab {
13810
+ .tabs--sm .page-tab {
11069
13811
  padding: var(--density-space-xs) var(--density-space-sm);
11070
13812
  font-size: var(--density-font-size-sm);
11071
13813
  }
11072
- .page-tabs--lg .page-tab {
13814
+ .tabs--lg .page-tab {
11073
13815
  padding: var(--density-space-md) calc(var(--density-space-md) + 4px);
11074
13816
  font-size: var(--density-font-size-lg);
11075
13817
  }
@@ -11091,7 +13833,7 @@ html {
11091
13833
  height: 18px;
11092
13834
  padding: 0 5px;
11093
13835
  margin-left: var(--density-space-xs);
11094
- font-size: 10px;
13836
+ font-size: var(--density-font-size-2xs);
11095
13837
  font-weight: 600;
11096
13838
  color: var(--token-text-faint);
11097
13839
  background: var(--token-fill-2);
@@ -11099,10 +13841,10 @@ html {
11099
13841
  font-variant-numeric: tabular-nums;
11100
13842
  line-height: 1;
11101
13843
  }
11102
- .page-tab.active .page-tab-badge,
13844
+ .page-tab.is-active .page-tab-badge,
11103
13845
  .page-tab[aria-selected='true'] .page-tab-badge {
11104
13846
  background: var(--token-accent);
11105
- color: var(--token-on-accent, #fff);
13847
+ color: var(--token-on-accent);
11106
13848
  }
11107
13849
 
11108
13850
  /* 可关闭 tab */
@@ -11129,7 +13871,7 @@ html {
11129
13871
  .page-tab-close svg { width: 10px; height: 10px; }
11130
13872
 
11131
13873
  /* 分段式 tab(替代底边线,用 segmented 视觉) */
11132
- .page-tabs--segment {
13874
+ .tabs--segment {
11133
13875
  display: inline-flex;
11134
13876
  border-bottom: none;
11135
13877
  gap: 2px;
@@ -11139,52 +13881,42 @@ html {
11139
13881
  border-radius: var(--radius-md);
11140
13882
  box-shadow: var(--token-shadow-inset);
11141
13883
  }
11142
- .page-tabs--segment .page-tab {
13884
+ .tabs--segment .page-tab {
11143
13885
  border-bottom: none;
11144
13886
  margin-bottom: 0;
11145
13887
  padding: var(--density-space-xs) var(--density-space-md);
11146
13888
  border-radius: var(--radius-sm);
11147
13889
  }
11148
- .page-tabs--segment .page-tab.active,
11149
- .page-tabs--segment .page-tab[aria-selected='true'] {
13890
+ .tabs--segment .page-tab.is-active,
13891
+ .tabs--segment .page-tab[aria-selected='true'] {
11150
13892
  background: var(--token-accent);
11151
- color: var(--token-on-accent, #fff);
13893
+ color: var(--token-on-accent);
11152
13894
  border-bottom-color: transparent;
11153
13895
  font-weight: 500;
11154
13896
  }
11155
13897
 
11156
13898
  /* 垂直 tab(侧栏式) */
11157
- .page-tabs--vertical {
13899
+ .tabs--vertical {
11158
13900
  flex-direction: column;
11159
13901
  border-bottom: none;
11160
13902
  border-right: 1px solid var(--token-line-soft);
11161
13903
  margin-bottom: 0;
11162
13904
  min-width: 160px;
11163
13905
  }
11164
- .page-tabs--vertical .page-tab {
13906
+ .tabs--vertical .page-tab {
11165
13907
  border-bottom: none;
11166
13908
  border-right: 2px solid transparent;
11167
13909
  margin-bottom: 0;
11168
13910
  margin-right: -1px;
11169
13911
  }
11170
- .page-tabs--vertical .page-tab.active,
11171
- .page-tabs--vertical .page-tab[aria-selected='true'] {
13912
+ .tabs--vertical .page-tab.is-active,
13913
+ .tabs--vertical .page-tab[aria-selected='true'] {
11172
13914
  border-right-color: var(--token-accent);
11173
13915
  border-bottom-color: transparent;
11174
13916
  }
11175
13917
 
11176
- /* tab 溢出渐变提示(窄屏溢出时右侧渐变遮罩——纯视觉提示,不违反"不滚动"纪律,仍 flex-wrap) */
11177
- .page-tabs--scroll {
11178
- flex-wrap: nowrap;
11179
- overflow-x: auto;
11180
- overflow-y: hidden;
11181
- scrollbar-width: none;
11182
- -ms-overflow-style: none;
11183
- }
11184
- .page-tabs--scroll::-webkit-scrollbar { display: none; }
11185
-
11186
13918
  @media (prefers-reduced-motion: reduce) {
11187
- .page-tab, .admin-tabs a, .page-tab-close { transition: none; }
13919
+ .page-tab, .tabs a, .page-tab-close { transition: none; }
11188
13920
  }
11189
13921
 
11190
13922
 
@@ -11231,7 +13963,7 @@ html {
11231
13963
  .tag-input.is-max,
11232
13964
  .tag-input.is-max:focus-within {
11233
13965
  border-color: color-mix(in srgb, var(--token-warning) 60%, transparent);
11234
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--token-warning) 14%, transparent);
13966
+ box-shadow: var(--icen-ring-warning);
11235
13967
  }
11236
13968
  .tag-input.is-disabled,
11237
13969
  .tag-input[aria-disabled='true'],
@@ -11289,7 +14021,7 @@ html {
11289
14021
  cursor: pointer;
11290
14022
  border-radius: var(--radius-full);
11291
14023
  color: inherit;
11292
- font-size: 11px;
14024
+ font-size: var(--density-font-size-xs);
11293
14025
  line-height: 1;
11294
14026
  opacity: 0.7;
11295
14027
  transition:
@@ -11300,7 +14032,7 @@ html {
11300
14032
  .tag-chip-x:hover {
11301
14033
  opacity: 1;
11302
14034
  background: color-mix(in srgb, currentColor 18%, transparent);
11303
- color: var(--token-on-accent, #fff);
14035
+ color: var(--token-on-accent);
11304
14036
  }
11305
14037
  .tag-chip-x:focus-visible {
11306
14038
  outline: none;
@@ -11326,7 +14058,7 @@ html {
11326
14058
 
11327
14059
  /* 重复值抖动反馈(behavior 短暂挂 is-shake) */
11328
14060
  .tag-input.is-shake {
11329
- animation: tag-shake 0.32s var(--ease-out);
14061
+ animation: tag-shake var(--tag-shake-duration, 0.32s) var(--ease-out);
11330
14062
  }
11331
14063
  @keyframes tag-shake {
11332
14064
  0%, 100% { transform: translateX(0); }
@@ -11510,7 +14242,7 @@ button.tag,
11510
14242
  *
11511
14243
  * 完整契约:
11512
14244
  * <div class="toast-container" role="status" aria-live="polite">
11513
- * <div class="toast [ok|err|warn]" role="status|alert">
14245
+ * <div class="toast [toast--success|toast--error|toast--warning|toast--info]" role="status|alert">
11514
14246
  * <span class="toast-msg">文本</span>
11515
14247
  * <button class="toast-action">撤销</button> ← 可选 action
11516
14248
  * </div>
@@ -11533,6 +14265,8 @@ button.tag,
11533
14265
 
11534
14266
  .toast {
11535
14267
  pointer-events: auto;
14268
+ position: relative;
14269
+ overflow: hidden;
11536
14270
  display: inline-flex;
11537
14271
  align-items: center;
11538
14272
  gap: var(--density-space-sm);
@@ -11550,9 +14284,10 @@ button.tag,
11550
14284
  }
11551
14285
 
11552
14286
  /* 语义色:左缘 3px 强调 */
11553
- .toast.ok { border-left-color: var(--token-success); }
11554
- .toast.err { border-left-color: var(--token-error); }
11555
- .toast.warn { border-left-color: var(--token-warning); }
14287
+ .toast--success { border-left-color: var(--token-success); }
14288
+ .toast--error { border-left-color: var(--token-error); }
14289
+ .toast--warning { border-left-color: var(--token-warning); }
14290
+ .toast--info { border-left-color: var(--token-info); }
11556
14291
 
11557
14292
  .toast-msg {
11558
14293
  flex: 1;
@@ -11593,9 +14328,10 @@ button.tag,
11593
14328
  color: var(--token-text-faint);
11594
14329
  }
11595
14330
  .toast-icon svg { width: 16px; height: 16px; }
11596
- .toast.ok .toast-icon { color: var(--token-success); }
11597
- .toast.err .toast-icon { color: var(--token-error); }
11598
- .toast.warn .toast-icon { color: var(--token-warning); }
14331
+ .toast--success .toast-icon { color: var(--token-success); }
14332
+ .toast--error .toast-icon { color: var(--token-error); }
14333
+ .toast--warning .toast-icon { color: var(--token-warning); }
14334
+ .toast--info .toast-icon { color: var(--token-info); }
11599
14335
 
11600
14336
  /* 关闭按钮 */
11601
14337
  .toast-close {
@@ -11654,16 +14390,15 @@ button.tag,
11654
14390
  transform-origin: left;
11655
14391
  animation: toast-countdown linear forwards;
11656
14392
  }
11657
- .toast.ok .toast-progress-fill { background: var(--token-success); }
11658
- .toast.err .toast-progress-fill { background: var(--token-error); }
11659
- .toast.warn .toast-progress-fill { background: var(--token-warning); }
14393
+ .toast--success .toast-progress-fill { background: var(--token-success); }
14394
+ .toast--error .toast-progress-fill { background: var(--token-error); }
14395
+ .toast--warning .toast-progress-fill { background: var(--token-warning); }
14396
+ .toast--info .toast-progress-fill { background: var(--token-info); }
11660
14397
  @keyframes toast-countdown {
11661
14398
  from { transform: scaleX(1); }
11662
14399
  to { transform: scaleX(0); }
11663
14400
  }
11664
14401
 
11665
- .toast { position: relative; overflow: hidden; }
11666
-
11667
14402
  /* 容器定位变体 */
11668
14403
  .toast-container[data-position='top-left'] {
11669
14404
  right: auto;
@@ -11696,7 +14431,7 @@ button.tag,
11696
14431
  align-items: center;
11697
14432
  }
11698
14433
 
11699
- .toast.leaving {
14434
+ .toast.is-leaving {
11700
14435
  opacity: 0;
11701
14436
  transform: translateY(8px);
11702
14437
  transition:
@@ -11833,7 +14568,7 @@ button.tag,
11833
14568
  * 无障碍:键盘 :focus-visible 立即出现(无延迟),鼠标 hover 延迟 400ms。
11834
14569
  */
11835
14570
 
11836
- [data-tooltip] { position: relative; }
14571
+ [data-tooltip] { position: relative; --tooltip-delay: 400ms; }
11837
14572
 
11838
14573
  [data-tooltip]::after {
11839
14574
  content: attr(data-tooltip);
@@ -11858,7 +14593,7 @@ button.tag,
11858
14593
  transition:
11859
14594
  opacity var(--icen-tx-fast),
11860
14595
  transform var(--icen-tx-fast);
11861
- transition-delay: 400ms;
14596
+ transition-delay: var(--tooltip-delay);
11862
14597
 
11863
14598
  /* 默认:上方、水平居中 */
11864
14599
  bottom: calc(100% + 6px);