@icen.ai/ui 0.4.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 (155) 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 +101 -0
  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 +189 -0
  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 +296 -0
  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 +184 -8
  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 +908 -0
  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 +268 -34
  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 +102 -0
  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 +205 -17
  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/back-top.css +88 -0
  86. package/dist/components/back-top.mjs +3 -0
  87. package/dist/components/badge.css +127 -0
  88. package/dist/components/badge.mjs +2 -0
  89. package/dist/components/breadcrumb.css +53 -0
  90. package/dist/components/btn.css +4 -7
  91. package/dist/components/card.css +66 -1
  92. package/dist/components/chart-bar.css +10 -0
  93. package/dist/components/chart-calendar.mjs +5 -0
  94. package/dist/components/chart-gauge.css +1 -1
  95. package/dist/components/chart-heatmap.css +40 -0
  96. package/dist/components/chart-scatter.css +24 -0
  97. package/dist/components/chart-scatter.mjs +4 -0
  98. package/dist/components/charts.css +86 -1
  99. package/dist/components/charts.mjs +1 -0
  100. package/dist/components/command-palette.css +246 -0
  101. package/dist/components/command-palette.mjs +3 -0
  102. package/dist/components/content.css +221 -8
  103. package/dist/components/copy.css +93 -0
  104. package/dist/components/copy.mjs +3 -0
  105. package/dist/components/datatable.css +4 -8
  106. package/dist/components/date-picker.css +240 -0
  107. package/dist/components/date-picker.mjs +3 -0
  108. package/dist/components/empty.css +69 -3
  109. package/dist/components/feedback.css +241 -10
  110. package/dist/components/form.css +129 -11
  111. package/dist/components/form.mjs +2 -0
  112. package/dist/components/input.css +25 -2
  113. package/dist/components/layout.css +36 -36
  114. package/dist/components/media.css +153 -6
  115. package/dist/components/menu.css +2 -2
  116. package/dist/components/modal.css +1 -1
  117. package/dist/components/nav.css +106 -2
  118. package/dist/components/notification.css +387 -0
  119. package/dist/components/notification.mjs +3 -0
  120. package/dist/components/pagination.css +1 -1
  121. package/dist/components/panel.css +104 -9
  122. package/dist/components/pill.css +82 -12
  123. package/dist/components/scroll-area.css +112 -0
  124. package/dist/components/scroll-area.mjs +2 -0
  125. package/dist/components/segmented.css +80 -5
  126. package/dist/components/segmented.mjs +1 -0
  127. package/dist/components/select.css +104 -17
  128. package/dist/components/sidebar.css +119 -1
  129. package/dist/components/slider.css +113 -0
  130. package/dist/components/split-pane.css +111 -0
  131. package/dist/components/split-pane.mjs +3 -0
  132. package/dist/components/stat.css +131 -12
  133. package/dist/components/steps.css +98 -1
  134. package/dist/components/switch.css +145 -7
  135. package/dist/components/table.css +111 -29
  136. package/dist/components/tabs.css +132 -22
  137. package/dist/components/tag-input.css +4 -4
  138. package/dist/components/tag.css +164 -0
  139. package/dist/components/tag.mjs +2 -0
  140. package/dist/components/toast.css +122 -5
  141. package/dist/components/toolbar.css +101 -0
  142. package/dist/components/toolbar.mjs +2 -0
  143. package/dist/components/tooltip.css +2 -2
  144. package/dist/components/upload.css +61 -4
  145. package/dist/index.d.mts +37 -0
  146. package/dist/index.mjs +9409 -929
  147. package/dist/registry.json +176 -4
  148. package/dist/retro-effects.css +21 -17
  149. package/dist/tokens/colors.css +4 -1
  150. package/dist/tokens/retro-effects.css +21 -17
  151. package/dist/tokens/style-profiles.css +21 -0
  152. package/dist/tokens.css +25 -1
  153. package/dist/ui.css +9616 -2839
  154. package/docs/spec/ai-native.md +659 -0
  155. package/package.json +15 -5
@@ -114,7 +114,126 @@
114
114
  }
115
115
  .form-field[data-invalid='true'] .form-label { color: var(--token-error); }
116
116
 
117
- /* ── 页面级横幅:操作结果 / 引导提示 ── */
117
+ /* ── 表单增强 ── */
118
+
119
+ /* 水平布局(label 与控件同行) */
120
+ .form-field--horizontal {
121
+ display: flex;
122
+ flex-direction: row;
123
+ align-items: flex-start;
124
+ gap: var(--density-space-md);
125
+ }
126
+ .form-field--horizontal .form-label {
127
+ flex-shrink: 0;
128
+ width: 120px;
129
+ text-align: right;
130
+ padding-top: calc((var(--density-input-height-md) - 1lh) / 2);
131
+ }
132
+ .form-field--horizontal .form-field-control {
133
+ flex: 1;
134
+ min-width: 0;
135
+ }
136
+
137
+ /* label 带帮助提示图标 */
138
+ .form-label-help {
139
+ display: inline-flex;
140
+ align-items: center;
141
+ margin-left: 4px;
142
+ width: 14px;
143
+ height: 14px;
144
+ color: var(--token-text-faint);
145
+ cursor: help;
146
+ }
147
+ .form-label-help svg { width: 12px; height: 12px; }
148
+
149
+ /* label 带可选标记 */
150
+ .form-label-optional {
151
+ font-size: var(--density-font-size-2xs);
152
+ font-weight: 400;
153
+ color: var(--token-text-faint);
154
+ margin-left: 4px;
155
+ }
156
+
157
+ /* 表单区块标题 */
158
+ .form-section {
159
+ padding: var(--density-space-sm) 0;
160
+ }
161
+ .form-section-title {
162
+ font-size: var(--density-font-size-md);
163
+ font-weight: 600;
164
+ color: var(--token-text);
165
+ margin: 0 0 var(--density-space-sm);
166
+ padding-bottom: var(--density-space-xs);
167
+ border-bottom: 1px solid var(--token-outline-soft);
168
+ }
169
+ .form-section-desc {
170
+ font-size: var(--density-font-size-sm);
171
+ color: var(--token-text-muted);
172
+ margin: calc(-1 * var(--density-space-xs)) 0 var(--density-space-sm);
173
+ }
174
+
175
+ /* 表单操作区(底部按钮行) */
176
+ .form-actions {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: var(--density-space-sm);
180
+ padding-top: var(--density-space-sm);
181
+ border-top: 1px solid var(--token-outline-soft);
182
+ }
183
+ .form-actions--right { justify-content: flex-end; }
184
+ .form-actions--center { justify-content: center; }
185
+ .form-actions--split { justify-content: space-between; }
186
+
187
+ /* 行内表单(紧凑,单行多字段) */
188
+ .form--inline {
189
+ flex-direction: row;
190
+ align-items: flex-end;
191
+ gap: var(--density-space-sm);
192
+ flex-wrap: wrap;
193
+ }
194
+ .form--inline .form-field {
195
+ flex: 1;
196
+ min-width: 160px;
197
+ }
198
+
199
+ /* 字段网格(多列布局) */
200
+ .form-grid {
201
+ display: grid;
202
+ grid-template-columns: repeat(var(--form-cols, 2), 1fr);
203
+ gap: var(--density-space-md);
204
+ }
205
+ .form-grid .form-field--full { grid-column: 1 / -1; }
206
+
207
+ /* 字段成功态 */
208
+ .form-field[data-valid='true'] .form-label::after {
209
+ content: '✓';
210
+ margin-left: 4px;
211
+ color: var(--token-success);
212
+ font-weight: 700;
213
+ }
214
+
215
+ /* 表单加载遮罩 */
216
+ .form-loading {
217
+ position: relative;
218
+ pointer-events: none;
219
+ opacity: 0.6;
220
+ }
221
+ .form-loading::after {
222
+ content: '';
223
+ position: absolute;
224
+ top: 50%;
225
+ left: 50%;
226
+ width: 24px;
227
+ height: 24px;
228
+ margin: -12px 0 0 -12px;
229
+ border: 2px solid color-mix(in srgb, var(--token-accent) 30%, transparent);
230
+ border-top-color: var(--token-accent);
231
+ border-radius: 50%;
232
+ animation: icen-spin var(--form-loading-duration, 0.8s) linear infinite;
233
+ }
234
+
235
+ /* ── 页面级横幅:操作结果 / 引导提示 ──
236
+ 与 feedback.css 的 alert 共享同一套视觉 token(--banner-* 仅在底色/文字/描边上分流) */
118
237
  .banner-ok,
119
238
  .banner-err {
120
239
  display: flex;
@@ -126,23 +245,22 @@
126
245
  font-size: var(--density-font-size-sm);
127
246
  line-height: 1.5;
128
247
  border: 1px solid transparent;
248
+ background: var(--banner-bg);
249
+ color: var(--banner-fg);
250
+ border-color: var(--banner-border);
129
251
  }
130
252
  .banner-ok {
131
- background: var(--token-success-muted);
132
- color: var(--token-success);
133
- border-color: color-mix(in srgb, var(--token-success) 25%, transparent);
253
+ --banner-bg: var(--token-success-muted);
254
+ --banner-fg: var(--token-success);
255
+ --banner-border: color-mix(in srgb, var(--token-success) 25%, transparent);
134
256
  }
135
257
  .banner-err {
136
- background: var(--token-error-muted);
137
- color: var(--token-error);
138
- border-color: color-mix(in srgb, var(--token-error) 25%, transparent);
258
+ --banner-bg: var(--token-error-muted);
259
+ --banner-fg: var(--token-error);
260
+ --banner-border: color-mix(in srgb, var(--token-error) 25%, transparent);
139
261
  }
140
262
  /* block 模式:横向占满,带图标位 */
141
263
  .banner-block {
142
264
  align-items: center;
143
265
  padding: var(--density-space-md);
144
266
  }
145
-
146
- @media (prefers-reduced-motion: reduce) {
147
- .banner-ok, .banner-err { transition: none; }
148
- }
@@ -0,0 +1,2 @@
1
+ // @icen.ai/ui — kit: form(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './form.css';
@@ -19,6 +19,7 @@
19
19
 
20
20
  /* ── 单行 ── */
21
21
  .input-wrap {
22
+ position: relative;
22
23
  display: flex;
23
24
  align-items: center;
24
25
  gap: var(--density-space-xs);
@@ -140,6 +141,7 @@
140
141
 
141
142
  /* ── 多行:同一 wrapper 视觉,内部 textarea 裸奔 ── */
142
143
  .textarea-wrap {
144
+ position: relative;
143
145
  display: flex;
144
146
  width: 100%;
145
147
  background: var(--token-control);
@@ -188,7 +190,18 @@
188
190
  color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
189
191
  }
190
192
 
191
- /* 字符计数(behavior 可挂到 .textarea-wrap 内尾部) */
193
+ /* 字符计数(behavior 可挂到 .input-wrap 或 .textarea-wrap 内尾部) */
194
+ .input-wrap--has-counter { padding-right: calc(var(--density-space-sm) + 48px); }
195
+ .input-wrap--has-counter .input-counter {
196
+ position: absolute;
197
+ right: var(--density-space-sm);
198
+ top: 50%;
199
+ transform: translateY(-50%);
200
+ font-size: var(--density-font-size-xs);
201
+ color: var(--token-text-faint);
202
+ font-variant-numeric: tabular-nums;
203
+ pointer-events: none;
204
+ }
192
205
  .input-counter {
193
206
  position: absolute;
194
207
  right: var(--density-space-sm);
@@ -197,6 +210,16 @@
197
210
  color: var(--token-text-faint);
198
211
  font-variant-numeric: tabular-nums;
199
212
  pointer-events: none;
213
+ transition: color var(--icen-tx-fast);
200
214
  }
201
- .textarea-wrap { position: relative; }
215
+ .input-counter.is-warn { color: var(--token-warning); }
216
+ .input-counter.is-error { color: var(--token-error); font-weight: 600; }
217
+
202
218
  .textarea-wrap .textarea { padding-bottom: 24px; }
219
+
220
+ /* 有计数器时的 textarea 底部留空 */
221
+ .textarea-wrap .input-counter {
222
+ position: absolute;
223
+ right: var(--density-space-sm);
224
+ bottom: var(--density-space-xs);
225
+ }
@@ -1,23 +1,23 @@
1
1
  /*
2
2
  * @icen.ai/ui — Component: layout(布局原语,全部是工具类,无 JS)
3
3
  * stack / divider / surface / text / grid / center / container / aspect / visually-hidden
4
- * 全部消费 --token-*;档位类名与 opengal primitives/layout(-containers) 一一对应。
4
+ * 全部消费 --token-* 与 --density-*;档位类名与 opengal primitives/layout(-containers) 一一对应。
5
5
  */
6
6
 
7
- /* ── Stack:flex 堆叠(默认列向,gap 8px) ── */
8
- .stack { display: flex; flex-direction: column; gap: 8px; }
7
+ /* ── Stack:flex 堆叠(默认列向,gap = density-space-sm) ── */
8
+ .stack { display: flex; flex-direction: column; gap: var(--density-space-sm); }
9
9
  .stack--row { flex-direction: row; }
10
10
  .stack--inline { display: inline-flex; }
11
11
  .stack--wrap { flex-wrap: wrap; }
12
12
  .stack--gap-0 { gap: 0; }
13
- .stack--gap-1 { gap: 4px; }
14
- .stack--gap-2 { gap: 8px; }
15
- .stack--gap-3 { gap: 12px; }
16
- .stack--gap-4 { gap: 16px; }
17
- .stack--gap-6 { gap: 24px; }
18
- .stack--gap-8 { gap: 32px; }
19
- .stack--gap-12 { gap: 48px; }
20
- .stack--gap-16 { gap: 64px; }
13
+ .stack--gap-1 { gap: var(--density-space-xs); }
14
+ .stack--gap-2 { gap: var(--density-space-sm); }
15
+ .stack--gap-3 { gap: var(--density-space-md); }
16
+ .stack--gap-4 { gap: var(--density-space-lg); }
17
+ .stack--gap-6 { gap: var(--density-space-xl); }
18
+ .stack--gap-8 { gap: var(--density-space-2xl); }
19
+ .stack--gap-12 { gap: var(--density-space-3xl); }
20
+ .stack--gap-16 { gap: var(--density-space-4xl); }
21
21
  .stack--items-start { align-items: flex-start; }
22
22
  .stack--items-center { align-items: center; }
23
23
  .stack--items-end { align-items: flex-end; }
@@ -34,13 +34,13 @@
34
34
  .divider { background: var(--token-outline-soft); height: 1px; width: 100%; }
35
35
  .divider--vertical { width: 1px; height: 100%; align-self: stretch; }
36
36
 
37
- /* ── Surface:卡片容器(默认 inset 阴影 + 16px padding) ── */
37
+ /* ── Surface:卡片容器(默认 inset 阴影 + density-space-lg padding) ── */
38
38
  .surface {
39
39
  border-radius: var(--radius-lg);
40
40
  background: var(--token-card);
41
41
  border: 1px solid var(--token-outline-soft);
42
42
  box-shadow: var(--token-shadow-inset);
43
- padding: 16px;
43
+ padding: var(--density-space-lg);
44
44
  }
45
45
  .surface--elevated { box-shadow: var(--token-shadow-card); }
46
46
  .surface--overlay {
@@ -49,18 +49,18 @@
49
49
  box-shadow: var(--token-shadow-overlay);
50
50
  }
51
51
  .surface--p-none { padding: 0; }
52
- .surface--p-sm { padding: 12px; }
53
- .surface--p-md { padding: 16px; }
54
- .surface--p-lg { padding: 20px; }
52
+ .surface--p-sm { padding: var(--density-space-md); }
53
+ .surface--p-md { padding: var(--density-space-lg); }
54
+ .surface--p-lg { padding: calc(var(--density-space-lg) + var(--density-space-xs)); }
55
55
 
56
56
  /* ── Text:字号 / 语义色 / 截断 ── */
57
- .text--xs { font-size: 11px; }
58
- .text--sm { font-size: 12px; }
59
- .text--base { font-size: 13px; }
60
- .text--md { font-size: 14px; }
61
- .text--lg { font-size: 15px; }
62
- .text--xl { font-size: 18px; }
63
- .text--2xl { font-size: 22px; }
57
+ .text--xs { font-size: var(--density-font-size-xs); }
58
+ .text--sm { font-size: var(--density-font-size-sm); }
59
+ .text--base { font-size: var(--density-font-size-md); }
60
+ .text--md { font-size: var(--density-font-size-lg); }
61
+ .text--lg { font-size: var(--density-font-size-lg); }
62
+ .text--xl { font-size: var(--density-font-size-xl); }
63
+ .text--2xl { font-size: var(--density-font-size-2xl); }
64
64
  .text--muted { color: var(--token-text-muted); }
65
65
  .text--faint { color: var(--token-text-faint); }
66
66
  .text--accent { color: var(--token-accent); }
@@ -69,8 +69,8 @@
69
69
  .text--error { color: var(--token-error); }
70
70
  .text--truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
71
71
 
72
- /* ── Grid:栅格(默认 gap 12px) ── */
73
- .grid { display: grid; gap: 12px; }
72
+ /* ── Grid:栅格(默认 gap = density-space-md) ── */
73
+ .grid { display: grid; gap: var(--density-space-md); }
74
74
  .grid--cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
75
75
  .grid--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
76
76
  .grid--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@@ -78,28 +78,28 @@
78
78
  .grid--cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
79
79
  .grid--cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
80
80
  .grid--gap-0 { gap: 0; }
81
- .grid--gap-4 { gap: 4px; }
82
- .grid--gap-8 { gap: 8px; }
83
- .grid--gap-12 { gap: 12px; }
84
- .grid--gap-16 { gap: 16px; }
85
- .grid--gap-24 { gap: 24px; }
81
+ .grid--gap-4 { gap: var(--density-space-xs); }
82
+ .grid--gap-8 { gap: var(--density-space-sm); }
83
+ .grid--gap-12 { gap: var(--density-space-md); }
84
+ .grid--gap-16 { gap: var(--density-space-lg); }
85
+ .grid--gap-24 { gap: var(--density-space-xl); }
86
86
 
87
87
  /* ── Center:flex 居中盒 ── */
88
88
  .center { display: flex; align-items: center; justify-content: center; }
89
89
 
90
90
  /* ── Container:限宽容器(默认 960px) ── */
91
- .container { width: 100%; margin-inline: auto; padding-inline: 16px; max-width: 960px; }
91
+ .container { width: 100%; margin-inline: auto; padding-inline: var(--density-space-lg); max-width: 960px; }
92
92
  .container--sm { max-width: 480px; }
93
93
  .container--md { max-width: 720px; }
94
94
  .container--lg { max-width: 960px; }
95
95
  .container--xl { max-width: 1200px; }
96
96
  .container--full { max-width: none; }
97
97
 
98
- /* ── Aspect:固定比例盒(padding-bottom hack,内容 absolute inset:0) ── */
99
- .aspect { position: relative; width: 100%; overflow: hidden; padding-bottom: 56.25%; }
100
- .aspect--square { padding-bottom: 100%; }
101
- .aspect--4-3 { padding-bottom: 75%; }
102
- .aspect--2-3 { padding-bottom: 150%; }
98
+ /* ── Aspect:固定比例盒(aspect-ratio,内容 absolute inset:0 铺满) ── */
99
+ .aspect { position: relative; width: 100%; overflow: hidden; aspect-ratio: 16 / 9; }
100
+ .aspect--square { aspect-ratio: 1 / 1; }
101
+ .aspect--4-3 { aspect-ratio: 4 / 3; }
102
+ .aspect--2-3 { aspect-ratio: 2 / 3; }
103
103
  .aspect > * { position: absolute; inset: 0; }
104
104
 
105
105
  /* ── VisuallyHidden:屏幕阅读器专用文本(sr-only 等价) ── */
@@ -4,6 +4,10 @@
4
4
  * 包含:avatar(头像)/ media-card(封面卡,滑出 overlay)/ rating(星级评分)/ carousel(轮播,与 behaviors/carousel 配套)
5
5
  * 规格源自 opengal data/display.tsx 的 Avatar、data/cartridge-card.tsx 的 hover overlay、
6
6
  * data/exhibit.tsx 的 Carousel 与 Rating。
7
+ *
8
+ * 例外声明(deliberate exception):carousel-thumbs 隐藏滚动条是有意设计
9
+ * (缩略图条视觉纯净,滚动仍可用)。违反「组件不写滚动条样式」纪律仅此处,
10
+ * 先例参照 scroll-area.css 的尺寸变体。
7
11
  */
8
12
 
9
13
  /* ══════════════ avatar · 头像 ══════════════ */
@@ -23,8 +27,8 @@
23
27
  flex-shrink: 0;
24
28
  user-select: none;
25
29
  }
26
- .avatar--xs { width: 20px; height: 20px; font-size: 9px; }
27
- .avatar--sm { width: 28px; height: 28px; font-size: 10px; }
30
+ .avatar--xs { width: 20px; height: 20px; font-size: var(--density-font-size-2xs); }
31
+ .avatar--sm { width: 28px; height: 28px; font-size: var(--density-font-size-2xs); }
28
32
  .avatar--lg { width: 48px; height: 48px; font-size: var(--density-font-size-md); }
29
33
  .avatar--xl { width: 80px; height: 80px; font-size: var(--density-font-size-lg); }
30
34
  .avatar img {
@@ -59,6 +63,42 @@
59
63
  .avatar-status--busy { background: var(--token-error); }
60
64
  .avatar-status--away { background: var(--token-warning); }
61
65
 
66
+ /* 头像色彩变体(文字头像底色) */
67
+ .avatar--info { background: color-mix(in srgb, var(--token-info) 15%, transparent); color: var(--token-info); }
68
+ .avatar--success { background: color-mix(in srgb, var(--token-success) 15%, transparent); color: var(--token-success); }
69
+ .avatar--warning { background: color-mix(in srgb, var(--token-warning) 15%, transparent); color: var(--token-warning); }
70
+ .avatar--error { background: color-mix(in srgb, var(--token-error) 15%, transparent); color: var(--token-error); }
71
+
72
+ /* 渐变底头像 */
73
+ .avatar--gradient {
74
+ background: linear-gradient(135deg, var(--token-accent), var(--token-accent-deep, var(--token-accent)));
75
+ color: var(--token-on-accent);
76
+ }
77
+
78
+ /* 方形头像 */
79
+ .avatar--square { border-radius: var(--radius-md); }
80
+
81
+ /* 头像通知环(红圈外框) */
82
+ .avatar--ring {
83
+ box-shadow: 0 0 0 2px var(--token-bg), 0 0 0 4px var(--token-error);
84
+ }
85
+
86
+ /* 头像组溢出计数 */
87
+ .avatar-group-count {
88
+ display: inline-flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ width: 28px;
92
+ height: 28px;
93
+ font-size: var(--density-font-size-2xs);
94
+ font-weight: 600;
95
+ color: var(--token-text-muted);
96
+ background: var(--token-fill-2);
97
+ border: 2px solid var(--token-bg);
98
+ border-radius: var(--radius-full);
99
+ margin-left: -8px;
100
+ }
101
+
62
102
  /* ══════════════ media-card · 封面卡 ══════════════ */
63
103
  .media-card {
64
104
  position: relative;
@@ -72,7 +112,7 @@
72
112
  .media-card:hover {
73
113
  transform: translateY(-6px);
74
114
  box-shadow:
75
- 0 8px 32px color-mix(in srgb, var(--token-text) 18%, transparent),
115
+ var(--token-shadow-overlay),
76
116
  0 0 20px var(--token-accent-glow);
77
117
  }
78
118
  .media-card-cover {
@@ -100,7 +140,7 @@
100
140
  }
101
141
  .media-card:hover .media-card-overlay {
102
142
  transform: translateY(0);
103
- transition-delay: 80ms;
143
+ transition-delay: var(--media-overlay-delay, 80ms);
104
144
  }
105
145
  .media-card-overlay-title {
106
146
  font-size: var(--density-font-size-md);
@@ -164,6 +204,53 @@
164
204
  font-variant-numeric: tabular-nums;
165
205
  }
166
206
 
207
+ /* 只读模式(不可交互) */
208
+ .rating--readonly .rating-star {
209
+ cursor: default;
210
+ }
211
+ .rating--readonly .rating-star:hover {
212
+ transform: none;
213
+ }
214
+
215
+ /* 清除评分钮 */
216
+ .rating-clear {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ width: 20px;
221
+ height: 20px;
222
+ margin-left: 4px;
223
+ padding: 0;
224
+ border: 0;
225
+ border-radius: var(--radius-xs);
226
+ background: transparent;
227
+ color: var(--token-text-faint);
228
+ cursor: pointer;
229
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
230
+ }
231
+ .rating-clear:hover {
232
+ color: var(--token-text);
233
+ background: var(--token-fill-1);
234
+ }
235
+ .rating-clear svg { width: 12px; height: 12px; }
236
+
237
+ /* 评分数(评价人数) */
238
+ .rating-count {
239
+ margin-left: var(--density-space-xs);
240
+ font-size: var(--density-font-size-xs);
241
+ color: var(--token-text-faint);
242
+ font-variant-numeric: tabular-nums;
243
+ }
244
+
245
+ /* 评分 + 文字摘要(如「4.5 · 128 条评价」) */
246
+ .rating-summary {
247
+ display: inline-flex;
248
+ align-items: center;
249
+ gap: var(--density-space-xs);
250
+ font-size: var(--density-font-size-sm);
251
+ color: var(--token-text-muted);
252
+ }
253
+
167
254
  /* ══════════════ carousel · 轮播(behaviors/carousel 配套) ══════════════
168
255
  * DOM 契约:
169
256
  * <div class="carousel" data-carousel>
@@ -258,7 +345,7 @@
258
345
  outline: none;
259
346
  box-shadow: var(--icen-ring);
260
347
  }
261
- .carousel-dot.active,
348
+ .carousel-dot.is-active,
262
349
  .carousel-dot[aria-current='true'] {
263
350
  width: 20px;
264
351
  background: var(--token-accent);
@@ -274,7 +361,67 @@
274
361
  transition: width var(--icen-tx-slow);
275
362
  }
276
363
 
364
+ /* 缩略图导航条(deliberate exception:隐藏滚动条是设计意图,见文件头注释) */
365
+ .carousel-thumbs {
366
+ display: flex;
367
+ gap: var(--density-space-xs);
368
+ margin-top: var(--density-space-sm);
369
+ padding: var(--density-space-xs);
370
+ overflow-x: auto;
371
+ scrollbar-width: none;
372
+ }
373
+ .carousel-thumbs::-webkit-scrollbar { display: none; }
374
+ .carousel-thumb {
375
+ flex-shrink: 0;
376
+ width: 56px;
377
+ height: 40px;
378
+ border-radius: var(--radius-sm);
379
+ overflow: hidden;
380
+ border: 2px solid transparent;
381
+ cursor: pointer;
382
+ opacity: 0.6;
383
+ transition: opacity var(--icen-tx-fast), border-color var(--icen-tx-fast);
384
+ }
385
+ .carousel-thumb:hover { opacity: 1; }
386
+ .carousel-thumb.is-active,
387
+ .carousel-thumb[aria-current='true'] {
388
+ opacity: 1;
389
+ border-color: var(--token-accent);
390
+ }
391
+ .carousel-thumb img,
392
+ .carousel-thumb video {
393
+ width: 100%;
394
+ height: 100%;
395
+ object-fit: cover;
396
+ }
397
+
398
+ /* 轮播索引指示器(1 / 5) */
399
+ .carousel-index {
400
+ position: absolute;
401
+ top: var(--density-space-sm);
402
+ right: var(--density-space-sm);
403
+ padding: 2px var(--density-space-sm);
404
+ font-size: var(--density-font-size-xs);
405
+ font-variant-numeric: tabular-nums;
406
+ color: var(--token-bg);
407
+ background: color-mix(in srgb, var(--token-text) 60%, transparent);
408
+ border-radius: var(--radius-full);
409
+ backdrop-filter: blur(4px);
410
+ -webkit-backdrop-filter: blur(4px);
411
+ z-index: 2;
412
+ }
413
+
414
+ /* 渐入动画 */
415
+ .carousel-slide {
416
+ animation: carousel-slide-fade var(--icen-tx-normal) var(--ease-out);
417
+ }
418
+ @keyframes carousel-slide-fade {
419
+ from { opacity: 0.6; }
420
+ to { opacity: 1; }
421
+ }
422
+
277
423
  @media (prefers-reduced-motion: reduce) {
278
424
  .carousel-track { transition: none; }
279
- .media-card, .rating-star, .carousel-arrow { transition: none; }
425
+ .carousel-slide { animation: none; }
426
+ .media-card, .rating-star, .carousel-arrow, .carousel-thumb { transition: none; }
280
427
  }
@@ -41,7 +41,7 @@
41
41
  border: 1px solid var(--token-outline);
42
42
  box-shadow: var(--token-shadow-overlay);
43
43
  border-radius: var(--radius-lg);
44
- animation: icen-menu-in var(--icen-tx-fast);
44
+ animation: icen-menu-in var(--menu-in-duration, var(--icen-tx-fast));
45
45
  }
46
46
  .menu[data-side='top'] { animation-name: icen-menu-in-top; }
47
47
  .menu[data-side='left'] { animation-name: icen-menu-in-left; }
@@ -52,7 +52,7 @@
52
52
  min-width: 218px;
53
53
  max-width: 286px;
54
54
  z-index: var(--z-tooltip); /* 上下文菜单高于普通 popover,与 tooltip 平齐 */
55
- animation-duration: 100ms;
55
+ --menu-in-duration: 100ms;
56
56
  }
57
57
 
58
58
  /* ── 分组标签 / 头(context-menu 的 header 项) ── */
@@ -102,7 +102,7 @@
102
102
  border: 0;
103
103
  cursor: pointer;
104
104
  color: var(--token-text-faint);
105
- font-size: 16px;
105
+ font-size: var(--density-font-size-lg);
106
106
  line-height: 1;
107
107
  border-radius: var(--radius-sm);
108
108
  transition: