@icen.ai/ui 0.4.0 → 0.5.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 (55) hide show
  1. package/dist/behaviors/back-top.mjs +84 -0
  2. package/dist/behaviors/command-palette.mjs +177 -0
  3. package/dist/behaviors/date-picker.mjs +228 -0
  4. package/dist/behaviors/input.mjs +170 -1
  5. package/dist/behaviors/notification.mjs +754 -0
  6. package/dist/behaviors/select.mjs +151 -22
  7. package/dist/behaviors/split-pane.mjs +102 -0
  8. package/dist/behaviors/upload.mjs +165 -14
  9. package/dist/components/back-top.css +88 -0
  10. package/dist/components/back-top.mjs +3 -0
  11. package/dist/components/badge.css +131 -0
  12. package/dist/components/badge.mjs +2 -0
  13. package/dist/components/breadcrumb.css +57 -0
  14. package/dist/components/card.css +68 -0
  15. package/dist/components/command-palette.css +246 -0
  16. package/dist/components/command-palette.mjs +3 -0
  17. package/dist/components/content.css +214 -0
  18. package/dist/components/copy.css +93 -0
  19. package/dist/components/copy.mjs +3 -0
  20. package/dist/components/date-picker.css +244 -0
  21. package/dist/components/date-picker.mjs +3 -0
  22. package/dist/components/empty.css +73 -3
  23. package/dist/components/feedback.css +239 -1
  24. package/dist/components/form.css +119 -0
  25. package/dist/components/form.mjs +2 -0
  26. package/dist/components/input.css +24 -1
  27. package/dist/components/media.css +144 -1
  28. package/dist/components/nav.css +99 -0
  29. package/dist/components/notification.css +387 -0
  30. package/dist/components/notification.mjs +3 -0
  31. package/dist/components/panel.css +100 -5
  32. package/dist/components/pill.css +70 -0
  33. package/dist/components/scroll-area.css +112 -0
  34. package/dist/components/scroll-area.mjs +2 -0
  35. package/dist/components/segmented.css +75 -0
  36. package/dist/components/select.css +97 -2
  37. package/dist/components/sidebar.css +118 -0
  38. package/dist/components/slider.css +113 -0
  39. package/dist/components/split-pane.css +112 -0
  40. package/dist/components/split-pane.mjs +3 -0
  41. package/dist/components/stat.css +129 -6
  42. package/dist/components/steps.css +97 -0
  43. package/dist/components/switch.css +142 -0
  44. package/dist/components/table.css +86 -3
  45. package/dist/components/tabs.css +122 -1
  46. package/dist/components/tag.css +164 -0
  47. package/dist/components/tag.mjs +2 -0
  48. package/dist/components/toast.css +114 -0
  49. package/dist/components/toolbar.css +101 -0
  50. package/dist/components/toolbar.mjs +2 -0
  51. package/dist/components/upload.css +61 -4
  52. package/dist/index.mjs +1858 -69
  53. package/dist/registry.json +64 -1
  54. package/dist/ui.css +4320 -278
  55. package/package.json +1 -1
package/dist/ui.css CHANGED
@@ -2315,6 +2315,231 @@ html {
2315
2315
  }
2316
2316
 
2317
2317
 
2318
+ /* ── components/back-top.css ── */
2319
+ /*
2320
+ * @icen.ai/ui — Component: back-top(回到顶部按钮,与 behaviors/back-top 配套)
2321
+ *
2322
+ * 契约(两种用法):
2323
+ * 1. 手写 + init:behavior 给 .back-top[data-back-top] 加 .is-shown 类
2324
+ * <button class="back-top" data-back-top type="button" aria-label="回到顶部">…svg…</button>
2325
+ *
2326
+ * 2. 全自动:initBackTop() 后任何 [data-back-top-auto] 会被自动 portal 到 body
2327
+ * 并接管滚到顶部逻辑,无需手写按钮
2328
+ * initBackTop({ autoCreate: true }) // 自动创建一个默认样式的按钮
2329
+ *
2330
+ * 默认滚动超过 320px 后出现;点击后 window.scrollTo({ top: 0, behavior: 'smooth' })。
2331
+ */
2332
+
2333
+ .back-top {
2334
+ position: fixed;
2335
+ right: var(--density-space-lg);
2336
+ bottom: var(--density-space-lg);
2337
+ z-index: var(--z-sticky);
2338
+ display: inline-flex;
2339
+ align-items: center;
2340
+ justify-content: center;
2341
+ width: 40px;
2342
+ height: 40px;
2343
+ padding: 0;
2344
+ background: var(--token-card-strong);
2345
+ color: var(--token-text-muted);
2346
+ border: 1px solid var(--token-outline-soft);
2347
+ border-radius: var(--radius-full);
2348
+ box-shadow: var(--token-shadow-overlay);
2349
+ cursor: pointer;
2350
+ -webkit-tap-highlight-color: transparent;
2351
+ opacity: 0;
2352
+ transform: translateY(12px) scale(0.9);
2353
+ pointer-events: none;
2354
+ transition:
2355
+ opacity var(--icen-tx-normal),
2356
+ transform var(--icen-tx-normal),
2357
+ color var(--icen-tx-fast),
2358
+ border-color var(--icen-tx-fast);
2359
+ }
2360
+ .back-top.is-shown {
2361
+ opacity: 1;
2362
+ transform: translateY(0) scale(1);
2363
+ pointer-events: auto;
2364
+ }
2365
+ .back-top:hover {
2366
+ color: var(--token-accent);
2367
+ border-color: var(--token-accent);
2368
+ }
2369
+ .back-top:focus-visible {
2370
+ outline: none;
2371
+ box-shadow: var(--token-shadow-overlay), var(--icen-ring);
2372
+ }
2373
+ .back-top svg {
2374
+ width: 18px;
2375
+ height: 18px;
2376
+ flex-shrink: 0;
2377
+ }
2378
+
2379
+ /* 尺寸变体 */
2380
+ .back-top--sm {
2381
+ width: 32px;
2382
+ height: 32px;
2383
+ }
2384
+ .back-top--sm svg { width: 14px; height: 14px; }
2385
+ .back-top--lg {
2386
+ width: 48px;
2387
+ height: 48px;
2388
+ }
2389
+ .back-top--lg svg { width: 22px; height: 22px; }
2390
+
2391
+ /* 方向变体:箭头方向(默认上) */
2392
+ .back-top[data-dir='left'] svg { transform: rotate(90deg); }
2393
+ .back-top[data-dir='right'] svg { transform: rotate(-90deg); }
2394
+ .back-top[data-dir='down'] svg { transform: rotate(180deg); }
2395
+
2396
+ @media (max-width: 480px) {
2397
+ .back-top {
2398
+ right: var(--density-space-sm);
2399
+ bottom: var(--density-space-sm);
2400
+ }
2401
+ }
2402
+
2403
+ @media (prefers-reduced-motion: reduce) {
2404
+ .back-top { transition: opacity var(--icen-tx-fast); transform: none !important; }
2405
+ .back-top.is-shown { transform: none !important; }
2406
+ }
2407
+
2408
+
2409
+ /* ── components/badge.css ── */
2410
+ /*
2411
+ * @icen.ai/ui — Component: badge(数字徽标 / 小红点)
2412
+ *
2413
+ * 与 pill 的差异:
2414
+ * - pill = 行内状态徽章,有文字内容
2415
+ * - badge = 角标式徽章,挂在外层元素的右上角,强调"数量/新动态"
2416
+ *
2417
+ * 完整契约(外层必须 position: relative 或 inline-flex):
2418
+ * <span class="badge-host">
2419
+ * <button class="btn">…</button>
2420
+ * <span class="badge">9</span> <!-- 数字徽标 -->
2421
+ * </span>
2422
+ *
2423
+ * <span class="badge-host">
2424
+ * <button class="btn">…</button>
2425
+ * <span class="badge badge--dot"></span> <!-- 纯圆点(无数字) -->
2426
+ * </span>
2427
+ *
2428
+ * <span class="badge-host">
2429
+ * <span class="avatar">…</span>
2430
+ * <span class="badge badge--bottom badge--success badge--dot"></span> <!-- 头像右下角在线点 -->
2431
+ * </span>
2432
+ *
2433
+ * 变体:--dot 纯圆点 / --sm 小号 / --lg 大号 / --bottom 底部角
2434
+ * 语义色:--success(在线)/ --warning / --error / --accent
2435
+ */
2436
+
2437
+ .badge-host {
2438
+ position: relative;
2439
+ display: inline-flex;
2440
+ align-items: center;
2441
+ justify-content: center;
2442
+ }
2443
+
2444
+ .badge {
2445
+ position: absolute;
2446
+ top: 0;
2447
+ right: 0;
2448
+ transform: translate(50%, -50%);
2449
+ z-index: var(--z-raised);
2450
+ display: inline-flex;
2451
+ align-items: center;
2452
+ justify-content: center;
2453
+ min-width: 16px;
2454
+ height: 16px;
2455
+ padding: 0 4px;
2456
+ font: inherit;
2457
+ font-size: 10px;
2458
+ font-weight: 600;
2459
+ line-height: 1;
2460
+ font-variant-numeric: tabular-nums;
2461
+ color: var(--token-on-accent, #fff);
2462
+ background: var(--token-error);
2463
+ border: 2px solid var(--token-surface);
2464
+ border-radius: var(--radius-full);
2465
+ box-shadow: var(--token-shadow-inset);
2466
+ pointer-events: none;
2467
+ white-space: nowrap;
2468
+ transform-origin: center;
2469
+ }
2470
+
2471
+ /* 纯圆点(无数字) */
2472
+ .badge--dot {
2473
+ width: 8px;
2474
+ height: 8px;
2475
+ min-width: 0;
2476
+ padding: 0;
2477
+ font-size: 0;
2478
+ }
2479
+
2480
+ /* 尺寸 */
2481
+ .badge--sm {
2482
+ min-width: 14px;
2483
+ height: 14px;
2484
+ font-size: 9px;
2485
+ padding: 0 3px;
2486
+ }
2487
+ .badge--sm.badge--dot {
2488
+ width: 6px;
2489
+ height: 6px;
2490
+ }
2491
+ .badge--lg {
2492
+ min-width: 20px;
2493
+ height: 20px;
2494
+ font-size: var(--density-font-size-xs);
2495
+ padding: 0 5px;
2496
+ }
2497
+ .badge--lg.badge--dot {
2498
+ width: 10px;
2499
+ height: 10px;
2500
+ }
2501
+
2502
+ /* 底部角(头像在线指示等) */
2503
+ .badge--bottom {
2504
+ top: auto;
2505
+ bottom: 0;
2506
+ transform: translate(50%, 50%);
2507
+ }
2508
+
2509
+ /* 语义色变体 */
2510
+ .badge--accent {
2511
+ background: var(--token-accent);
2512
+ }
2513
+ .badge--success {
2514
+ background: var(--token-success);
2515
+ }
2516
+ .badge--warning {
2517
+ background: var(--token-warning);
2518
+ }
2519
+ .badge--error {
2520
+ background: var(--token-error);
2521
+ }
2522
+ .badge--info {
2523
+ background: var(--token-info);
2524
+ }
2525
+
2526
+ /* 挂在实底色按钮上时用更浅的边框 */
2527
+ .btn-primary .badge,
2528
+ .btn-danger .badge {
2529
+ border-color: var(--token-on-accent, #fff);
2530
+ }
2531
+
2532
+ /* 数字超过 99 显示 99+(消费方决定是否使用,这里只保证宽度容纳) */
2533
+ .badge[data-overflow] {
2534
+ min-width: 20px;
2535
+ padding: 0 5px;
2536
+ }
2537
+
2538
+ @media (prefers-reduced-motion: reduce) {
2539
+ .badge { transition: none; }
2540
+ }
2541
+
2542
+
2318
2543
  /* ── components/breadcrumb.css ── */
2319
2544
  /*
2320
2545
  * @icen.ai/ui — Component: breadcrumb(面包屑)
@@ -2414,6 +2639,63 @@ html {
2414
2639
  padding: 0 2px;
2415
2640
  color: var(--token-text-faint);
2416
2641
  user-select: none;
2642
+ cursor: pointer;
2643
+ border-radius: var(--radius-xs);
2644
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
2645
+ }
2646
+ .breadcrumb-ellipsis:hover {
2647
+ color: var(--token-accent);
2648
+ background: var(--token-fill-1);
2649
+ }
2650
+
2651
+ /* ── 溢出下拉(behavior 可选:点击 … 展开被折叠的中间路径) ── */
2652
+ .breadcrumb-dropdown {
2653
+ position: relative;
2654
+ }
2655
+ .breadcrumb-dropdown-menu {
2656
+ position: absolute;
2657
+ top: calc(100% + 4px);
2658
+ left: 0;
2659
+ min-width: 160px;
2660
+ padding: 4px;
2661
+ background: var(--token-card-strong);
2662
+ border: 1px solid var(--token-outline);
2663
+ border-radius: var(--radius-md);
2664
+ box-shadow: var(--token-shadow-overlay);
2665
+ z-index: var(--z-popover);
2666
+ animation: breadcrumb-drop-in var(--icen-tx-fast);
2667
+ }
2668
+ .breadcrumb-dropdown-menu[hidden] { display: none; }
2669
+ .breadcrumb-dropdown-menu a {
2670
+ display: block;
2671
+ padding: 4px var(--density-space-sm);
2672
+ font-size: var(--density-font-size-sm);
2673
+ color: var(--token-text-muted);
2674
+ text-decoration: none;
2675
+ border-radius: var(--radius-sm);
2676
+ transition: background-color var(--icen-tx-fast), color var(--icen-tx-fast);
2677
+ }
2678
+ .breadcrumb-dropdown-menu a:hover {
2679
+ background: var(--token-fill-1);
2680
+ color: var(--token-text);
2681
+ }
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
+
2687
+ /* ── 面包屑分隔符变体 ── */
2688
+ .breadcrumb--slash .breadcrumb-sep::after { content: '/'; }
2689
+ .breadcrumb--arrow .breadcrumb-sep::after { content: '→'; }
2690
+ .breadcrumb--dot .breadcrumb-sep::after { content: '·'; font-size: 1.2em; }
2691
+
2692
+ /* ── 面包屑尺寸变体 ── */
2693
+ .breadcrumb--sm {
2694
+ font-size: var(--density-font-size-sm);
2695
+ }
2696
+ .breadcrumb--sm .breadcrumb-link,
2697
+ .breadcrumb--sm .breadcrumb-current {
2698
+ max-width: 140px;
2417
2699
  }
2418
2700
 
2419
2701
  @media (max-width: 480px) {
@@ -2691,6 +2973,20 @@ html {
2691
2973
  overflow: hidden;
2692
2974
  background: var(--token-bg-soft);
2693
2975
  }
2976
+ /* 封面比例变体 */
2977
+ .card-media--square { aspect-ratio: 1 / 1; }
2978
+ .card-media--tall { aspect-ratio: 3 / 4; }
2979
+ .card-media--wide { aspect-ratio: 21 / 9; }
2980
+ .card-media--auto { aspect-ratio: auto; }
2981
+ /* 封面 hover 放大(配合 .card--interactive) */
2982
+ .card--interactive:hover .card-media > img,
2983
+ .card--interactive:hover .card-media > video {
2984
+ transform: scale(1.04);
2985
+ }
2986
+ .card-media > img,
2987
+ .card-media > video {
2988
+ transition: transform var(--icen-tx-slow) var(--ease-out);
2989
+ }
2694
2990
  .card-media > img,
2695
2991
  .card-media > video {
2696
2992
  width: 100%;
@@ -2766,6 +3062,60 @@ html {
2766
3062
  .card--interactive { transition: none; }
2767
3063
  }
2768
3064
 
3065
+ /* ── 卡片网格(card-group):自适应多列 ── */
3066
+ .card-group {
3067
+ display: grid;
3068
+ gap: var(--density-space-md);
3069
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
3070
+ }
3071
+ .card-group--2 { grid-template-columns: repeat(2, 1fr); }
3072
+ .card-group--3 { grid-template-columns: repeat(3, 1fr); }
3073
+ .card-group--4 { grid-template-columns: repeat(4, 1fr); }
3074
+
3075
+ /* ── 卡片列表(横向铺开,常用于搜索结果/消息流) ── */
3076
+ .card-list {
3077
+ display: flex;
3078
+ flex-direction: column;
3079
+ gap: var(--density-space-sm);
3080
+ }
3081
+ .card-list > .card {
3082
+ display: flex;
3083
+ flex-direction: row;
3084
+ align-items: stretch;
3085
+ }
3086
+ .card-list > .card .card-media {
3087
+ width: 120px;
3088
+ flex-shrink: 0;
3089
+ aspect-ratio: auto;
3090
+ }
3091
+ .card-list > .card .card-head,
3092
+ .card-list > .card .card-body,
3093
+ .card-list > .card .card-foot {
3094
+ flex: 1;
3095
+ }
3096
+
3097
+ /* ── 卡片头副标题 ── */
3098
+ .card-subtitle {
3099
+ font-size: var(--density-font-size-xs);
3100
+ color: var(--token-text-faint);
3101
+ margin: 1px 0 0;
3102
+ line-height: 1.4;
3103
+ }
3104
+
3105
+ /* ── 卡片状态色边 ── */
3106
+ .card--success { border-color: color-mix(in srgb, var(--token-success) 35%, transparent); }
3107
+ .card--warning { border-color: color-mix(in srgb, var(--token-warning) 35%, transparent); }
3108
+ .card--error { border-color: color-mix(in srgb, var(--token-error) 35%, transparent); }
3109
+
3110
+ /* ── 卡片右上角徽标位 ── */
3111
+ .card-badge {
3112
+ position: absolute;
3113
+ top: var(--density-space-sm);
3114
+ right: var(--density-space-sm);
3115
+ z-index: 1;
3116
+ }
3117
+ .card { position: relative; }
3118
+
2769
3119
 
2770
3120
  /* ── components/chart-area.css ── */
2771
3121
  /*
@@ -3172,50 +3522,299 @@ html {
3172
3522
  .chart-tone-6 { --chart-tone: color-mix(in srgb, var(--token-text) 50%, transparent); }
3173
3523
 
3174
3524
 
3175
- /* ── components/content.css ── */
3525
+ /* ── components/command-palette.css ── */
3176
3526
  /*
3177
- * @icen.ai/ui — Component: content(内容展示族)
3527
+ * @icen.ai/ui — Component: command-palette(命令面板 ⌘K,与 behaviors/command-palette 配套)
3178
3528
  *
3179
- * 包含:kbd(按键)/ code-inline(行内代码)/ list(分隔列表)/ accordion(手风琴,behaviors/accordion 配套)/
3180
- * timeline(时间线)/ desc(描述列表)/ tree(树形,behaviors/tree 配套)
3181
- * 规格源自 opengal src/presentation/ui/data/exhibit.tsx 与 data/display.tsx。
3529
+ * 契约:
3530
+ * <div class="command-palette-backdrop" data-command-palette="id" hidden>
3531
+ * <div class="command-palette">
3532
+ * <div class="command-palette-search">
3533
+ * <span class="command-palette-search-icon">…放大镜 svg…</span>
3534
+ * <input class="command-palette-input" placeholder="搜索命令…" />
3535
+ * <kbd class="command-palette-esc">ESC</kbd>
3536
+ * </div>
3537
+ * <div class="command-palette-body">
3538
+ * <div class="command-palette-group">
3539
+ * <p class="command-palette-group-label">分组名</p>
3540
+ * <button class="command-palette-item [.is-active]" type="button">
3541
+ * <span class="command-palette-item-icon">…svg…</span>
3542
+ * <span class="command-palette-item-label">命令名</span>
3543
+ * <span class="command-palette-item-hint">副标题</span>
3544
+ * <kbd class="command-palette-item-kbd">⌘S</kbd>
3545
+ * </button>
3546
+ * </div>
3547
+ * <div class="command-palette-empty" hidden>无匹配命令</div>
3548
+ * </div>
3549
+ * <div class="command-palette-foot">
3550
+ * <span class="command-palette-foot-hint">
3551
+ * <kbd>↑↓</kbd> 导航 <kbd>↵</kbd> 执行 <kbd>esc</kbd> 关闭
3552
+ * </span>
3553
+ * </div>
3554
+ * </div>
3555
+ * </div>
3556
+ *
3557
+ * behavior 接管:搜索过滤、↑↓ 导航、Enter 执行、ESC / 外点 / backdrop 关闭、
3558
+ * 焦点陷阱、按 ⌘K / Ctrl+K 全局打开(可选 data-command-palette-key)。
3182
3559
  */
3183
3560
 
3184
- /* ══════════════ kbd · 按键 ══════════════ */
3185
- .kbd {
3186
- display: inline-flex;
3187
- align-items: center;
3561
+ .command-palette-backdrop {
3562
+ position: fixed;
3563
+ inset: 0;
3564
+ z-index: var(--z-dialog);
3565
+ display: flex;
3566
+ align-items: flex-start;
3188
3567
  justify-content: center;
3189
- vertical-align: middle;
3190
- font-family: var(--font-mono);
3191
- font-weight: 500;
3192
- border-radius: var(--radius-xs);
3193
- border: 1px solid var(--token-outline);
3194
- background: var(--token-control);
3195
- color: var(--token-text-muted);
3196
- box-shadow: var(--token-shadow-inset);
3197
- height: 20px;
3198
- min-width: 20px;
3199
- padding: 0 var(--density-space-xs);
3200
- font-size: var(--density-font-size-xs);
3201
- }
3202
- .kbd--sm {
3203
- height: 16px;
3204
- min-width: 16px;
3205
- padding: 0 4px;
3206
- font-size: 10px;
3207
- }
3208
- .kbd-group {
3209
- display: inline-flex;
3210
- align-items: center;
3211
- gap: 2px;
3212
- font-family: var(--font-mono);
3213
- font-size: var(--density-font-size-xs);
3214
- color: var(--token-text-muted);
3568
+ padding-top: 12vh;
3569
+ padding-left: var(--density-space-md);
3570
+ padding-right: var(--density-space-md);
3571
+ background: color-mix(in srgb, var(--token-text) 40%, transparent);
3572
+ backdrop-filter: blur(4px);
3573
+ -webkit-backdrop-filter: blur(4px);
3574
+ animation: icen-cp-fade-in var(--icen-tx-normal);
3215
3575
  }
3216
- .kbd-group .kbd { font-size: inherit; }
3576
+ .command-palette-backdrop[hidden] { display: none; }
3217
3577
 
3218
- /* ══════════════ code-inline · 行内代码 ══════════════ */
3578
+ .command-palette {
3579
+ display: flex;
3580
+ flex-direction: column;
3581
+ width: 100%;
3582
+ max-width: 560px;
3583
+ max-height: 70vh;
3584
+ overflow: hidden;
3585
+ background: var(--token-card-strong);
3586
+ border: 1px solid var(--token-outline);
3587
+ border-radius: var(--radius-lg);
3588
+ box-shadow: var(--token-shadow-overlay);
3589
+ animation: icen-cp-scale-in var(--icen-tx-slow);
3590
+ }
3591
+ .command-palette:focus { outline: none; }
3592
+
3593
+ /* ── 搜索栏 ── */
3594
+ .command-palette-search {
3595
+ display: flex;
3596
+ align-items: center;
3597
+ gap: var(--density-space-sm);
3598
+ padding: var(--density-space-md);
3599
+ border-bottom: 1px solid var(--token-line-soft);
3600
+ }
3601
+ .command-palette-search-icon {
3602
+ display: inline-flex;
3603
+ align-items: center;
3604
+ justify-content: center;
3605
+ color: var(--token-text-faint);
3606
+ flex-shrink: 0;
3607
+ }
3608
+ .command-palette-search-icon svg { width: 16px; height: 16px; }
3609
+ .command-palette-input {
3610
+ flex: 1;
3611
+ min-width: 0;
3612
+ border: 0;
3613
+ outline: none;
3614
+ background: transparent;
3615
+ color: var(--token-text);
3616
+ font: inherit;
3617
+ font-size: var(--density-font-size-md);
3618
+ }
3619
+ .command-palette-input::placeholder {
3620
+ color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
3621
+ }
3622
+ .command-palette-esc {
3623
+ font-family: var(--font-mono);
3624
+ font-size: var(--density-font-size-xs);
3625
+ color: var(--token-text-faint);
3626
+ padding: 2px 6px;
3627
+ border: 1px solid var(--token-line-soft);
3628
+ border-radius: var(--radius-xs);
3629
+ flex-shrink: 0;
3630
+ }
3631
+
3632
+ /* ── 列表区 ── */
3633
+ .command-palette-body {
3634
+ flex: 1;
3635
+ min-height: 0;
3636
+ overflow-y: auto;
3637
+ overscroll-behavior: contain;
3638
+ padding: var(--density-space-xs);
3639
+ }
3640
+
3641
+ .command-palette-group {
3642
+ display: flex;
3643
+ flex-direction: column;
3644
+ gap: 1px;
3645
+ margin-bottom: var(--density-space-sm);
3646
+ }
3647
+ .command-palette-group:last-child { margin-bottom: 0; }
3648
+ .command-palette-group-label {
3649
+ margin: var(--density-space-sm) var(--density-space-sm) var(--density-space-xs);
3650
+ font-size: var(--density-font-size-xs);
3651
+ font-weight: 600;
3652
+ color: var(--token-text-faint);
3653
+ letter-spacing: 0.06em;
3654
+ text-transform: uppercase;
3655
+ }
3656
+ .command-palette-group:first-child .command-palette-group-label { margin-top: 0; }
3657
+
3658
+ .command-palette-item {
3659
+ display: flex;
3660
+ align-items: center;
3661
+ gap: var(--density-space-sm);
3662
+ width: 100%;
3663
+ padding: 8px var(--density-space-sm);
3664
+ border: 0;
3665
+ border-radius: var(--radius-sm);
3666
+ background: transparent;
3667
+ color: var(--token-text);
3668
+ font: inherit;
3669
+ font-size: var(--density-font-size-md);
3670
+ text-align: left;
3671
+ cursor: pointer;
3672
+ -webkit-tap-highlight-color: transparent;
3673
+ transition: background-color var(--icen-tx-fast);
3674
+ }
3675
+ .command-palette-item:hover { background: var(--token-fill-1); }
3676
+ .command-palette-item.is-active {
3677
+ background: var(--token-accent-subtle);
3678
+ color: var(--token-accent-deep, var(--token-accent));
3679
+ }
3680
+ .command-palette-item:focus-visible {
3681
+ outline: none;
3682
+ box-shadow: var(--icen-ring);
3683
+ }
3684
+
3685
+ .command-palette-item-icon {
3686
+ display: inline-flex;
3687
+ align-items: center;
3688
+ justify-content: center;
3689
+ flex-shrink: 0;
3690
+ width: 18px;
3691
+ height: 18px;
3692
+ color: var(--token-text-faint);
3693
+ }
3694
+ .command-palette-item.is-active .command-palette-item-icon { color: var(--token-accent); }
3695
+ .command-palette-item-icon svg { width: 16px; height: 16px; }
3696
+
3697
+ .command-palette-item-label {
3698
+ flex: 1;
3699
+ min-width: 0;
3700
+ overflow: hidden;
3701
+ text-overflow: ellipsis;
3702
+ white-space: nowrap;
3703
+ }
3704
+ .command-palette-item-hint {
3705
+ font-size: var(--density-font-size-xs);
3706
+ color: var(--token-text-faint);
3707
+ margin-left: var(--density-space-xs);
3708
+ overflow: hidden;
3709
+ text-overflow: ellipsis;
3710
+ white-space: nowrap;
3711
+ }
3712
+ .command-palette-item.is-active .command-palette-item-hint { color: color-mix(in srgb, var(--token-accent) 70%, var(--token-text-faint)); }
3713
+
3714
+ .command-palette-item-kbd {
3715
+ font-family: var(--font-mono);
3716
+ font-size: 10px;
3717
+ color: var(--token-text-faint);
3718
+ padding: 1px 5px;
3719
+ border: 1px solid var(--token-line-soft);
3720
+ border-radius: var(--radius-xs);
3721
+ flex-shrink: 0;
3722
+ }
3723
+
3724
+ /* ── 空态 ── */
3725
+ .command-palette-empty {
3726
+ padding: 32px var(--density-space-md);
3727
+ text-align: center;
3728
+ color: var(--token-text-faint);
3729
+ font-size: var(--density-font-size-sm);
3730
+ }
3731
+
3732
+ /* ── 底栏 ── */
3733
+ .command-palette-foot {
3734
+ padding: var(--density-space-xs) var(--density-space-md);
3735
+ border-top: 1px solid var(--token-line-soft);
3736
+ background: var(--token-bg-soft);
3737
+ }
3738
+ .command-palette-foot-hint {
3739
+ display: inline-flex;
3740
+ align-items: center;
3741
+ gap: var(--density-space-sm);
3742
+ font-size: var(--density-font-size-xs);
3743
+ color: var(--token-text-faint);
3744
+ }
3745
+ .command-palette-foot-hint kbd {
3746
+ font-family: var(--font-mono);
3747
+ font-size: 10px;
3748
+ padding: 1px 4px;
3749
+ border: 1px solid var(--token-line-soft);
3750
+ border-radius: var(--radius-xs);
3751
+ background: var(--token-surface);
3752
+ }
3753
+
3754
+ @keyframes icen-cp-fade-in {
3755
+ from { opacity: 0; }
3756
+ to { opacity: 1; }
3757
+ }
3758
+ @keyframes icen-cp-scale-in {
3759
+ from { opacity: 0; transform: translateY(-8px) scale(0.98); }
3760
+ to { opacity: 1; transform: translateY(0) scale(1); }
3761
+ }
3762
+
3763
+ @media (max-width: 480px) {
3764
+ .command-palette-backdrop { padding-top: 6vh; }
3765
+ .command-palette { max-height: 80vh; }
3766
+ }
3767
+
3768
+ @media (prefers-reduced-motion: reduce) {
3769
+ .command-palette-backdrop { animation: none; }
3770
+ .command-palette { animation: none; }
3771
+ }
3772
+
3773
+
3774
+ /* ── components/content.css ── */
3775
+ /*
3776
+ * @icen.ai/ui — Component: content(内容展示族)
3777
+ *
3778
+ * 包含:kbd(按键)/ code-inline(行内代码)/ list(分隔列表)/ accordion(手风琴,behaviors/accordion 配套)/
3779
+ * timeline(时间线)/ desc(描述列表)/ tree(树形,behaviors/tree 配套)
3780
+ * 规格源自 opengal src/presentation/ui/data/exhibit.tsx 与 data/display.tsx。
3781
+ */
3782
+
3783
+ /* ══════════════ kbd · 按键 ══════════════ */
3784
+ .kbd {
3785
+ display: inline-flex;
3786
+ align-items: center;
3787
+ justify-content: center;
3788
+ vertical-align: middle;
3789
+ font-family: var(--font-mono);
3790
+ font-weight: 500;
3791
+ border-radius: var(--radius-xs);
3792
+ border: 1px solid var(--token-outline);
3793
+ background: var(--token-control);
3794
+ color: var(--token-text-muted);
3795
+ box-shadow: var(--token-shadow-inset);
3796
+ height: 20px;
3797
+ min-width: 20px;
3798
+ padding: 0 var(--density-space-xs);
3799
+ font-size: var(--density-font-size-xs);
3800
+ }
3801
+ .kbd--sm {
3802
+ height: 16px;
3803
+ min-width: 16px;
3804
+ padding: 0 4px;
3805
+ font-size: 10px;
3806
+ }
3807
+ .kbd-group {
3808
+ display: inline-flex;
3809
+ align-items: center;
3810
+ gap: 2px;
3811
+ font-family: var(--font-mono);
3812
+ font-size: var(--density-font-size-xs);
3813
+ color: var(--token-text-muted);
3814
+ }
3815
+ .kbd-group .kbd { font-size: inherit; }
3816
+
3817
+ /* ══════════════ code-inline · 行内代码 ══════════════ */
3219
3818
  .code-inline {
3220
3819
  padding: 2px var(--density-space-xs);
3221
3820
  border-radius: var(--radius-xs);
@@ -3551,67 +4150,377 @@ html {
3551
4150
  }
3552
4151
  }
3553
4152
 
4153
+ /* ── list 增强 ── */
3554
4154
 
3555
- /* ── components/datatable.css ── */
3556
- /*
3557
- * @icen.ai/ui — Component: datatable(数据表格,与 behaviors/datatable 配套)
3558
- *
3559
- * 结构(全部由 createTable 渲染,div + ARIA role 网格):
3560
- * .datatable [--striped|--virtual]
3561
- * ├─ .dt-toolbar > .dt-search > svg + input.dt-search-input
3562
- * ├─ .dt-scroll > .dt-canvas[role=grid]
3563
- * │ ├─ .dt-head > .dt-row--head > .dt-cell--head(.is-sortable / .dt-filter-btn / .dt-filter-panel)
3564
- * │ └─ .dt-body > .dt-row[.is-selected] > .dt-cell;.dt-expand-row;.dt-empty;.dt-skeleton-row
3565
- * └─ .dt-foot > .dt-stat(含 .dt-clear-sel)+ .dt-pager(.dt-page-size / .dt-page-btn / .dt-page-dots)
3566
- *
3567
- * 行布局 = grid,grid-template-columns 由 behavior 按列宽计算;行内容溢出省略号。
3568
- * 右键菜单面板样式在 menu.css(kit 入口自动带上)。
3569
- */
3570
-
3571
- .datatable {
3572
- border: 1px solid var(--token-line-soft);
3573
- border-radius: var(--radius-lg);
3574
- background: var(--token-surface);
3575
- overflow: hidden;
3576
- font-size: var(--density-font-size-md);
3577
- }
3578
-
3579
- /* ── 工具栏(搜索) ── */
3580
- .dt-toolbar {
3581
- display: flex;
3582
- align-items: center;
3583
- gap: var(--density-space-sm);
3584
- padding: 10px 14px;
3585
- border-bottom: 1px solid var(--token-line-soft);
3586
- }
3587
- .dt-search {
3588
- display: flex;
4155
+ /* 列表项带前置图标/头像 */
4156
+ .list-item-leading {
4157
+ display: inline-flex;
3589
4158
  align-items: center;
3590
- gap: 8px;
3591
- height: 32px;
3592
- width: 260px;
3593
- max-width: 100%;
3594
- padding: 0 10px;
3595
- color: var(--token-text-faint);
3596
- background: var(--token-control);
3597
- border: 1px solid var(--token-outline-soft);
3598
- border-radius: var(--radius-md);
3599
- box-shadow: var(--token-shadow-inset);
3600
- transition: border-color var(--icen-tx-fast), box-shadow var(--icen-tx-fast);
3601
- }
3602
- .dt-search:focus-within {
3603
- border-color: var(--icen-control-focus-border, var(--token-accent));
3604
- box-shadow: var(--icen-control-focus-shadow);
4159
+ justify-content: center;
4160
+ flex-shrink: 0;
4161
+ margin-right: var(--density-space-sm);
3605
4162
  }
3606
- .dt-search-input {
4163
+ .list-item-leading svg { width: 16px; height: 16px; color: var(--token-text-faint); }
4164
+ .list-item-content {
3607
4165
  flex: 1;
3608
4166
  min-width: 0;
3609
- border: 0;
3610
- outline: none;
3611
- background: transparent;
4167
+ display: flex;
4168
+ flex-direction: column;
4169
+ gap: 1px;
4170
+ }
4171
+ .list-item-title {
4172
+ font-size: var(--density-font-size-md);
3612
4173
  color: var(--token-text);
3613
- font: inherit;
3614
- font-size: var(--density-font-size-sm);
4174
+ overflow: hidden;
4175
+ text-overflow: ellipsis;
4176
+ white-space: nowrap;
4177
+ }
4178
+ .list-item-meta {
4179
+ font-size: var(--density-font-size-xs);
4180
+ color: var(--token-text-faint);
4181
+ }
4182
+ .list-item-trailing {
4183
+ flex-shrink: 0;
4184
+ margin-left: var(--density-space-sm);
4185
+ font-size: var(--density-font-size-xs);
4186
+ color: var(--token-text-faint);
4187
+ }
4188
+ .list-item--selectable {
4189
+ cursor: pointer;
4190
+ }
4191
+ .list-item.is-selected {
4192
+ background: var(--token-accent-subtle);
4193
+ }
4194
+ .list-item.is-selected .list-item-title { color: var(--token-accent); font-weight: 500; }
4195
+
4196
+ /* 列表项 flex 布局(leading + content + trailing) */
4197
+ .list-item--flex {
4198
+ display: flex;
4199
+ align-items: center;
4200
+ }
4201
+
4202
+ /* 斑马列表 */
4203
+ .list--striped .list-item:nth-child(odd) {
4204
+ background: color-mix(in srgb, var(--token-bg-soft) 40%, transparent);
4205
+ }
4206
+
4207
+ /* ── timeline 增强 ── */
4208
+
4209
+ /* 右侧时间线(dot 在右) */
4210
+ .timeline--right .timeline-item { flex-direction: row-reverse; }
4211
+ .timeline--right .timeline-item:not(:last-child)::after {
4212
+ left: auto;
4213
+ right: 13.5px;
4214
+ }
4215
+ .timeline--right .timeline-body { text-align: right; }
4216
+ .timeline--right .timeline-time { margin-left: 0; margin-right: auto; }
4217
+
4218
+ /* 交替时间线(奇左偶右) */
4219
+ .timeline--alternate .timeline-item:nth-child(even) { flex-direction: row-reverse; }
4220
+ .timeline--alternate .timeline-item:nth-child(even):not(:last-child)::after {
4221
+ left: auto; right: 13.5px;
4222
+ }
4223
+ .timeline--alternate .timeline-item:nth-child(even) .timeline-body { text-align: right; }
4224
+ .timeline--alternate .timeline-item:nth-child(even) .timeline-time {
4225
+ margin-left: 0; margin-right: auto;
4226
+ }
4227
+
4228
+ /* 自定义 dot 尺寸 */
4229
+ .timeline-dot--sm { width: 20px; height: 20px; }
4230
+ .timeline-dot--sm svg { width: 10px; height: 10px; }
4231
+ .timeline-dot--sm ~ .timeline-item:not(:last-child)::after,
4232
+ .timeline--sm .timeline-item:not(:last-child)::after { left: 10px; top: 24px; }
4233
+
4234
+ /* 时间线自定义内容槽(放标签、卡片等) */
4235
+ .timeline-extra {
4236
+ margin-top: var(--density-space-xs);
4237
+ padding: var(--density-space-sm);
4238
+ background: var(--token-bg-soft);
4239
+ border-radius: var(--radius-sm);
4240
+ font-size: var(--density-font-size-sm);
4241
+ }
4242
+
4243
+ /* ── desc 增强 ── */
4244
+
4245
+ /* 行内描述(label: value 在同一行) */
4246
+ .desc--inline .desc-item {
4247
+ display: flex;
4248
+ align-items: baseline;
4249
+ gap: var(--density-space-sm);
4250
+ padding: 4px 0;
4251
+ }
4252
+ .desc--inline .desc-item dt {
4253
+ flex-shrink: 0;
4254
+ min-width: 80px;
4255
+ }
4256
+ .desc--inline .desc-item dd {
4257
+ flex: 1;
4258
+ }
4259
+
4260
+ /* 垂直描述(label 在上,value 在下) */
4261
+ .desc--vertical .desc-item {
4262
+ display: flex;
4263
+ flex-direction: column;
4264
+ gap: 2px;
4265
+ }
4266
+
4267
+ /* 可复制描述值 */
4268
+ .desc-copy {
4269
+ display: inline-flex;
4270
+ align-items: center;
4271
+ gap: var(--density-space-xs);
4272
+ }
4273
+ .desc-copy-btn {
4274
+ display: inline-flex;
4275
+ align-items: center;
4276
+ padding: 2px;
4277
+ border: 0;
4278
+ border-radius: var(--radius-xs);
4279
+ background: transparent;
4280
+ color: var(--token-text-faint);
4281
+ cursor: pointer;
4282
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
4283
+ }
4284
+ .desc-copy-btn:hover { color: var(--token-accent); background: var(--token-fill-1); }
4285
+ .desc-copy-btn svg { width: 12px; height: 12px; }
4286
+
4287
+ /* ── tree 增强 ── */
4288
+
4289
+ /* 带连接线的树 */
4290
+ .tree--lined ul {
4291
+ position: relative;
4292
+ margin-left: 7px;
4293
+ padding-left: 0;
4294
+ border-left: 1px solid var(--token-outline-soft);
4295
+ }
4296
+ .tree--lined .tree-node {
4297
+ position: relative;
4298
+ }
4299
+ .tree--lined ul > li > .tree-node::before {
4300
+ content: '';
4301
+ position: absolute;
4302
+ left: -8px;
4303
+ top: 50%;
4304
+ width: 8px;
4305
+ height: 1px;
4306
+ background: var(--token-outline-soft);
4307
+ }
4308
+
4309
+ /* 树节点复选框(checkbox tree) */
4310
+ .tree-checkbox {
4311
+ width: 14px;
4312
+ height: 14px;
4313
+ flex-shrink: 0;
4314
+ margin-right: 2px;
4315
+ }
4316
+
4317
+ /* 树拖拽视觉态 */
4318
+ .tree-node.is-dragging {
4319
+ opacity: 0.5;
4320
+ }
4321
+ .tree-node.is-drop-target {
4322
+ box-shadow: inset 0 -2px var(--token-accent);
4323
+ }
4324
+
4325
+ /* 树搜索高亮 */
4326
+ .tree-label mark {
4327
+ background: color-mix(in srgb, var(--token-warning) 30%, transparent);
4328
+ color: inherit;
4329
+ border-radius: 2px;
4330
+ padding: 0 1px;
4331
+ }
4332
+
4333
+ /* ── kbd 增强 ── */
4334
+
4335
+ /* 大号 kbd(展示用) */
4336
+ .kbd--lg {
4337
+ height: 28px;
4338
+ min-width: 28px;
4339
+ padding: 0 var(--density-space-sm);
4340
+ font-size: var(--density-font-size-md);
4341
+ }
4342
+
4343
+ /* kbd 按下态(动画反馈) */
4344
+ .kbd.is-pressed {
4345
+ transform: translateY(1px);
4346
+ box-shadow: none;
4347
+ }
4348
+
4349
+ /* ── code-inline 增强 ── */
4350
+ .code-inline--success { color: var(--token-success); }
4351
+ .code-inline--warning { color: var(--token-warning); }
4352
+ .code-inline--error { color: var(--token-error); }
4353
+
4354
+ /* 代码块(多行,非行内) */
4355
+ .code-block {
4356
+ padding: var(--density-space-sm) var(--density-space-md);
4357
+ background: var(--token-bg-soft);
4358
+ border: 1px solid var(--token-outline-soft);
4359
+ border-radius: var(--radius-md);
4360
+ font-family: var(--font-mono);
4361
+ font-size: var(--density-font-size-sm);
4362
+ color: var(--token-text);
4363
+ overflow-x: auto;
4364
+ line-height: 1.6;
4365
+ }
4366
+
4367
+
4368
+ /* ── components/copy.css ── */
4369
+ /*
4370
+ * @icen.ai/ui — Component: copy(复制按钮,与 behaviors/copy 配套)
4371
+ *
4372
+ * 完整契约:
4373
+ * 文本变体(成功后文本 1.4s 内变「已复制」再还原):
4374
+ * <button class="copy-btn" data-copy="复制的文本">复制</button>
4375
+ * <button class="copy-btn copy-btn--ghost" data-copy="…">复制链接</button>
4376
+ *
4377
+ * 图标变体(带 data-copy-icon,仅加 .done 类;由 CSS 显隐子 svg):
4378
+ * <button class="copy-btn copy-btn--icon" data-copy="…" data-copy-icon aria-label="复制">
4379
+ * <svg class="copy-icon-clipboard">…</svg>
4380
+ * <svg class="copy-icon-check" hidden>…</svg>
4381
+ * </button>
4382
+ *
4383
+ * behavior 自动处理:
4384
+ * - 文本变体:成功后 textContent = '已复制' + 加 .done 类,1.4s 后还原
4385
+ * - 图标变体(带 data-copy-icon):仅加 .done 类(不改文本),由 CSS 切换 svg 显隐
4386
+ *
4387
+ * 与 toast.ok 区别:copy 是"原地反馈"(按钮自身闪烁),toast 是"全局通知"。
4388
+ */
4389
+
4390
+ .copy-btn {
4391
+ display: inline-flex;
4392
+ align-items: center;
4393
+ justify-content: center;
4394
+ gap: var(--density-space-xs);
4395
+ height: var(--density-input-height-sm);
4396
+ padding: 0 var(--density-space-sm);
4397
+ background: var(--token-control);
4398
+ color: var(--token-text-muted);
4399
+ border: 1px solid var(--token-outline-soft);
4400
+ border-radius: var(--radius-sm);
4401
+ font: inherit;
4402
+ font-size: var(--density-font-size-sm);
4403
+ cursor: pointer;
4404
+ -webkit-tap-highlight-color: transparent;
4405
+ transition:
4406
+ color var(--icen-tx-fast),
4407
+ border-color var(--icen-tx-fast),
4408
+ background-color var(--icen-tx-fast);
4409
+ }
4410
+ .copy-btn:hover {
4411
+ color: var(--token-text);
4412
+ border-color: var(--icen-hover-border);
4413
+ }
4414
+ .copy-btn:focus-visible {
4415
+ outline: none;
4416
+ box-shadow: var(--icen-ring);
4417
+ }
4418
+ .copy-btn:disabled,
4419
+ .copy-btn[aria-disabled='true'] {
4420
+ opacity: var(--icen-disabled-opacity);
4421
+ cursor: not-allowed;
4422
+ pointer-events: none;
4423
+ }
4424
+
4425
+ /* ghost 变体:透明底,纯描边 / hover 才显底 */
4426
+ .copy-btn--ghost {
4427
+ background: transparent;
4428
+ border-color: var(--token-line-soft);
4429
+ }
4430
+ .copy-btn--ghost:hover {
4431
+ background: var(--token-fill-1);
4432
+ border-color: var(--token-accent);
4433
+ }
4434
+
4435
+ /* ── 成功闪烁态(behavior 1.4s 后移除)── */
4436
+ .copy-btn.done {
4437
+ color: var(--token-success);
4438
+ border-color: color-mix(in srgb, var(--token-success) 40%, transparent);
4439
+ background: color-mix(in srgb, var(--token-success) 10%, transparent);
4440
+ }
4441
+
4442
+ /* ── 图标变体:方形按钮,仅放 svg ── */
4443
+ .copy-btn--icon {
4444
+ width: 28px;
4445
+ height: 28px;
4446
+ padding: 0;
4447
+ }
4448
+ .copy-btn--icon svg {
4449
+ width: 14px;
4450
+ height: 14px;
4451
+ flex-shrink: 0;
4452
+ }
4453
+
4454
+ /* .done 时显隐切换:剪贴板 → 对勾 */
4455
+ .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; }
4458
+
4459
+ @media (prefers-reduced-motion: reduce) {
4460
+ .copy-btn { transition: none; }
4461
+ }
4462
+
4463
+
4464
+ /* ── components/datatable.css ── */
4465
+ /*
4466
+ * @icen.ai/ui — Component: datatable(数据表格,与 behaviors/datatable 配套)
4467
+ *
4468
+ * 结构(全部由 createTable 渲染,div + ARIA role 网格):
4469
+ * .datatable [--striped|--virtual]
4470
+ * ├─ .dt-toolbar > .dt-search > svg + input.dt-search-input
4471
+ * ├─ .dt-scroll > .dt-canvas[role=grid]
4472
+ * │ ├─ .dt-head > .dt-row--head > .dt-cell--head(.is-sortable / .dt-filter-btn / .dt-filter-panel)
4473
+ * │ └─ .dt-body > .dt-row[.is-selected] > .dt-cell;.dt-expand-row;.dt-empty;.dt-skeleton-row
4474
+ * └─ .dt-foot > .dt-stat(含 .dt-clear-sel)+ .dt-pager(.dt-page-size / .dt-page-btn / .dt-page-dots)
4475
+ *
4476
+ * 行布局 = grid,grid-template-columns 由 behavior 按列宽计算;行内容溢出省略号。
4477
+ * 右键菜单面板样式在 menu.css(kit 入口自动带上)。
4478
+ */
4479
+
4480
+ .datatable {
4481
+ border: 1px solid var(--token-line-soft);
4482
+ border-radius: var(--radius-lg);
4483
+ background: var(--token-surface);
4484
+ overflow: hidden;
4485
+ font-size: var(--density-font-size-md);
4486
+ }
4487
+
4488
+ /* ── 工具栏(搜索) ── */
4489
+ .dt-toolbar {
4490
+ display: flex;
4491
+ align-items: center;
4492
+ gap: var(--density-space-sm);
4493
+ padding: 10px 14px;
4494
+ border-bottom: 1px solid var(--token-line-soft);
4495
+ }
4496
+ .dt-search {
4497
+ display: flex;
4498
+ align-items: center;
4499
+ gap: 8px;
4500
+ height: 32px;
4501
+ width: 260px;
4502
+ max-width: 100%;
4503
+ padding: 0 10px;
4504
+ color: var(--token-text-faint);
4505
+ background: var(--token-control);
4506
+ border: 1px solid var(--token-outline-soft);
4507
+ border-radius: var(--radius-md);
4508
+ box-shadow: var(--token-shadow-inset);
4509
+ transition: border-color var(--icen-tx-fast), box-shadow var(--icen-tx-fast);
4510
+ }
4511
+ .dt-search:focus-within {
4512
+ border-color: var(--icen-control-focus-border, var(--token-accent));
4513
+ box-shadow: var(--icen-control-focus-shadow);
4514
+ }
4515
+ .dt-search-input {
4516
+ flex: 1;
4517
+ min-width: 0;
4518
+ border: 0;
4519
+ outline: none;
4520
+ background: transparent;
4521
+ color: var(--token-text);
4522
+ font: inherit;
4523
+ font-size: var(--density-font-size-sm);
3615
4524
  }
3616
4525
  .dt-search-input::placeholder { color: var(--token-text-faint); }
3617
4526
  .dt-search-input::-webkit-search-cancel-button { -webkit-appearance: none; }
@@ -3984,26 +4893,282 @@ html {
3984
4893
  .dt-row--success { background: color-mix(in srgb, var(--token-success) 6%, transparent) !important; }
3985
4894
 
3986
4895
 
3987
- /* ── components/empty.css ── */
4896
+ /* ── components/date-picker.css ── */
3988
4897
  /*
3989
- * @icen.ai/ui — Component: empty(空状态 / 无数据 / 404 占位)
4898
+ * @icen.ai/ui — Component: date-picker(日期选择,与 behaviors/date-picker 配套)
3990
4899
  *
3991
- * 完整契约:
3992
- * <div class="empty [.empty--sm|.empty--lg]">
3993
- * <div class="empty-icon">…svg…</div> ← 可选,缺省显示默认占位
3994
- * <h3 class="empty-title">暂无数据</h3>
3995
- * <p class="empty-desc">描述文案(可选)</p>
3996
- * <div class="empty-action"> ← 可选:操作按钮位
3997
- * <button class="btn btn-primary">…</button>
4900
+ * 契约:
4901
+ * <div class="date-picker" data-date-picker data-date-picker-input="id">
4902
+ * <button class="date-picker-trigger" type="button" aria-haspopup="dialog">
4903
+ * <span class="date-picker-value">2026-07-29</span>
4904
+ * <svg class="date-picker-icon">…日历 svg…</svg>
4905
+ * </button>
4906
+ * <input type="hidden" id="id" name="date" value="2026-07-29" />
4907
+ * </div>
4908
+ *
4909
+ * 或直接挂到普通 input(behavior 自动包裹触发器外观):
4910
+ * <input class="date-picker-input" data-date-picker value="2026-07-29" />
4911
+ *
4912
+ * behavior 渲染面板(portal 到 body):
4913
+ * <div class="date-picker-panel" role="dialog">
4914
+ * <div class="date-picker-head">
4915
+ * <button class="date-picker-nav" data-prev>‹</button>
4916
+ * <span class="date-picker-title">2026 年 7 月</span>
4917
+ * <button class="date-picker-nav" data-next>›</button>
4918
+ * </div>
4919
+ * <div class="date-picker-weekdays">
4920
+ * <span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span>
4921
+ * </div>
4922
+ * <div class="date-picker-grid">
4923
+ * <button class="date-picker-cell [.is-outside] [.is-today] [.is-selected] [.is-disabled]">15</button>
4924
+ * …
4925
+ * </div>
4926
+ * <div class="date-picker-foot">
4927
+ * <button class="date-picker-today">今天</button>
4928
+ * <button class="date-picker-clear">清除</button>
3998
4929
  * </div>
3999
4930
  * </div>
4000
4931
  *
4001
- * 规格源自 opengal src/presentation/ui/data/display.tsx 的 EmptyState。
4932
+ * 可选 data-*:
4933
+ * data-date-picker-format="yyyy-mm-dd" 显示格式
4934
+ * data-date-picker-min="2026-01-01" 最小可选日期
4935
+ * data-date-picker-max="2026-12-31" 最大可选日期
4936
+ * data-date-picker-week-start="0" 周首日(0=周日,1=周一,默认 1)
4002
4937
  */
4003
4938
 
4004
- .empty {
4005
- display: flex;
4006
- flex-direction: column;
4939
+ .date-picker {
4940
+ display: inline-block;
4941
+ position: relative;
4942
+ }
4943
+
4944
+ .date-picker-trigger {
4945
+ display: inline-flex;
4946
+ align-items: center;
4947
+ gap: var(--density-space-xs);
4948
+ width: 100%;
4949
+ height: var(--density-input-height-md);
4950
+ padding: 0 var(--density-space-sm);
4951
+ background: var(--token-control);
4952
+ border: 1px solid var(--token-outline-soft);
4953
+ border-radius: var(--radius-md);
4954
+ box-shadow: var(--token-shadow-inset);
4955
+ color: var(--token-text);
4956
+ font: inherit;
4957
+ font-size: var(--density-font-size-md);
4958
+ cursor: pointer;
4959
+ -webkit-tap-highlight-color: transparent;
4960
+ transition: border-color var(--icen-tx-fast), box-shadow var(--icen-tx-fast);
4961
+ }
4962
+ .date-picker-trigger:hover { border-color: var(--icen-hover-border); }
4963
+ .date-picker-trigger.is-open {
4964
+ border-color: var(--icen-control-focus-border);
4965
+ box-shadow: var(--icen-control-focus-shadow);
4966
+ }
4967
+ .date-picker-trigger:focus-visible {
4968
+ outline: none;
4969
+ border-color: var(--icen-control-focus-border);
4970
+ box-shadow: var(--icen-control-focus-shadow);
4971
+ }
4972
+ .date-picker-value {
4973
+ flex: 1;
4974
+ min-width: 0;
4975
+ text-align: left;
4976
+ overflow: hidden;
4977
+ text-overflow: ellipsis;
4978
+ white-space: nowrap;
4979
+ font-variant-numeric: tabular-nums;
4980
+ }
4981
+ .date-picker-value.is-empty { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
4982
+ .date-picker-icon { display: inline-flex; color: var(--token-text-faint); flex-shrink: 0; }
4983
+ .date-picker-icon svg { width: 14px; height: 14px; }
4984
+
4985
+ /* 尺寸 */
4986
+ .date-picker-trigger.date-picker-trigger--sm { height: var(--density-input-height-sm); font-size: var(--density-font-size-sm); }
4987
+ .date-picker-trigger.date-picker-trigger--lg { height: var(--density-input-height-lg); font-size: var(--density-font-size-lg); }
4988
+
4989
+ /* ── 面板 ── */
4990
+ .date-picker-panel {
4991
+ position: fixed;
4992
+ z-index: var(--z-popover);
4993
+ width: 280px;
4994
+ padding: var(--density-space-sm);
4995
+ background: var(--token-card-strong);
4996
+ border: 1px solid var(--token-outline);
4997
+ border-radius: var(--radius-md);
4998
+ box-shadow: var(--token-shadow-overlay);
4999
+ 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); }
5005
+ }
5006
+
5007
+ .date-picker-head {
5008
+ display: flex;
5009
+ align-items: center;
5010
+ justify-content: space-between;
5011
+ margin-bottom: var(--density-space-sm);
5012
+ }
5013
+ .date-picker-title {
5014
+ font-weight: 600;
5015
+ color: var(--token-text);
5016
+ font-variant-numeric: tabular-nums;
5017
+ flex: 1;
5018
+ text-align: center;
5019
+ }
5020
+ .date-picker-nav {
5021
+ display: inline-flex;
5022
+ align-items: center;
5023
+ justify-content: center;
5024
+ width: 24px;
5025
+ height: 24px;
5026
+ padding: 0;
5027
+ border: 0;
5028
+ border-radius: var(--radius-xs);
5029
+ background: transparent;
5030
+ color: var(--token-text-muted);
5031
+ font-size: var(--density-font-size-md);
5032
+ cursor: pointer;
5033
+ -webkit-tap-highlight-color: transparent;
5034
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
5035
+ }
5036
+ .date-picker-nav:hover { color: var(--token-text); background: var(--token-fill-1); }
5037
+ .date-picker-nav:focus-visible {
5038
+ outline: none;
5039
+ box-shadow: var(--icen-ring);
5040
+ }
5041
+
5042
+ .date-picker-weekdays {
5043
+ display: grid;
5044
+ grid-template-columns: repeat(7, 1fr);
5045
+ gap: 2px;
5046
+ margin-bottom: 4px;
5047
+ }
5048
+ .date-picker-weekdays > span {
5049
+ display: flex;
5050
+ align-items: center;
5051
+ justify-content: center;
5052
+ height: 24px;
5053
+ font-size: var(--density-font-size-xs);
5054
+ color: var(--token-text-faint);
5055
+ font-weight: 500;
5056
+ }
5057
+
5058
+ .date-picker-grid {
5059
+ display: grid;
5060
+ grid-template-columns: repeat(7, 1fr);
5061
+ gap: 2px;
5062
+ }
5063
+ .date-picker-cell {
5064
+ display: flex;
5065
+ align-items: center;
5066
+ justify-content: center;
5067
+ height: 30px;
5068
+ padding: 0;
5069
+ border: 0;
5070
+ border-radius: var(--radius-sm);
5071
+ background: transparent;
5072
+ color: var(--token-text);
5073
+ font: inherit;
5074
+ font-size: var(--density-font-size-sm);
5075
+ font-variant-numeric: tabular-nums;
5076
+ cursor: pointer;
5077
+ -webkit-tap-highlight-color: transparent;
5078
+ transition: background-color var(--icen-tx-fast), color var(--icen-tx-fast);
5079
+ }
5080
+ .date-picker-cell:hover { background: var(--token-fill-1); }
5081
+ .date-picker-cell.is-today {
5082
+ border: 1px solid var(--token-accent);
5083
+ color: var(--token-accent);
5084
+ font-weight: 600;
5085
+ }
5086
+ .date-picker-cell.is-selected {
5087
+ background: var(--token-accent);
5088
+ color: var(--token-on-accent, #fff);
5089
+ font-weight: 600;
5090
+ }
5091
+ .date-picker-cell.is-selected.is-today { border-color: var(--token-on-accent, #fff); }
5092
+ .date-picker-cell.is-outside {
5093
+ color: var(--token-text-faint);
5094
+ opacity: 0.55;
5095
+ }
5096
+ .date-picker-cell.is-disabled {
5097
+ color: var(--token-text-faint);
5098
+ opacity: 0.35;
5099
+ cursor: not-allowed;
5100
+ pointer-events: none;
5101
+ }
5102
+ .date-picker-cell:focus-visible {
5103
+ outline: none;
5104
+ box-shadow: var(--icen-ring);
5105
+ }
5106
+
5107
+ .date-picker-foot {
5108
+ display: flex;
5109
+ justify-content: space-between;
5110
+ gap: var(--density-space-xs);
5111
+ margin-top: var(--density-space-sm);
5112
+ padding-top: var(--density-space-sm);
5113
+ border-top: 1px solid var(--token-line-soft);
5114
+ }
5115
+ .date-picker-today,
5116
+ .date-picker-clear {
5117
+ padding: 4px var(--density-space-sm);
5118
+ border: 0;
5119
+ border-radius: var(--radius-xs);
5120
+ background: transparent;
5121
+ color: var(--token-accent);
5122
+ font: inherit;
5123
+ font-size: var(--density-font-size-xs);
5124
+ cursor: pointer;
5125
+ -webkit-tap-highlight-color: transparent;
5126
+ transition: background-color var(--icen-tx-fast);
5127
+ }
5128
+ .date-picker-today:hover,
5129
+ .date-picker-clear:hover { background: var(--token-fill-1); }
5130
+ .date-picker-today:focus-visible,
5131
+ .date-picker-clear:focus-visible {
5132
+ outline: none;
5133
+ box-shadow: var(--icen-ring);
5134
+ }
5135
+ .date-picker-clear { color: var(--token-text-muted); }
5136
+
5137
+ @media (prefers-reduced-motion: reduce) {
5138
+ .date-picker-panel { animation: none; }
5139
+ .date-picker-cell { transition: none; }
5140
+ }
5141
+
5142
+
5143
+ /* ── components/empty.css ── */
5144
+ /*
5145
+ * @icen.ai/ui — Component: empty(空状态 / 无数据 / 404 / 错误 / 维护中)
5146
+ *
5147
+ * 完整契约:
5148
+ * <div class="empty [.empty--sm|.empty--lg]
5149
+ * [.empty--error|.empty--404|.empty--search|.empty--maintenance|.empty--network]">
5150
+ * <div class="empty-icon">…svg…</div> ← 可选,缺省显示默认占位
5151
+ * <h3 class="empty-title">暂无数据</h3>
5152
+ * <p class="empty-desc">描述文案(可选)</p>
5153
+ * <div class="empty-action"> ← 可选:操作按钮位
5154
+ * <button class="btn btn-primary">…</button>
5155
+ * <button class="btn">…</button>
5156
+ * </div>
5157
+ * </div>
5158
+ *
5159
+ * 类型预设(改图标底色与基调):
5160
+ * .empty--error 错误(红底图标)
5161
+ * .empty--404 404(中性大字)
5162
+ * .empty--search 搜索无结果
5163
+ * .empty--maintenance 维护中(warning 底)
5164
+ * .empty--network 网络错误(info 底)
5165
+ *
5166
+ * 规格源自 opengal src/presentation/ui/data/display.tsx 的 EmptyState。
5167
+ */
5168
+
5169
+ .empty {
5170
+ display: flex;
5171
+ flex-direction: column;
4007
5172
  align-items: center;
4008
5173
  justify-content: center;
4009
5174
  text-align: center;
@@ -4012,6 +5177,7 @@ html {
4012
5177
  .empty--sm { padding: var(--density-space-md); }
4013
5178
  .empty--lg { padding: 64px var(--density-space-md); }
4014
5179
 
5180
+ /* ── 图标 ── */
4015
5181
  .empty-icon {
4016
5182
  width: 48px;
4017
5183
  height: 48px;
@@ -4031,6 +5197,28 @@ html {
4031
5197
  .empty--lg .empty-icon svg { width: 32px; height: 32px; }
4032
5198
  .empty--sm .empty-icon svg { width: 18px; height: 18px; }
4033
5199
 
5200
+ /* ── 类型预设 ── */
5201
+ .empty--error .empty-icon {
5202
+ background: color-mix(in srgb, var(--token-error) 10%, transparent);
5203
+ border-color: color-mix(in srgb, var(--token-error) 20%, transparent);
5204
+ color: var(--token-error);
5205
+ }
5206
+ .empty--maintenance .empty-icon {
5207
+ background: color-mix(in srgb, var(--token-warning) 10%, transparent);
5208
+ border-color: color-mix(in srgb, var(--token-warning) 20%, transparent);
5209
+ color: var(--token-warning);
5210
+ }
5211
+ .empty--network .empty-icon {
5212
+ background: color-mix(in srgb, var(--token-info) 10%, transparent);
5213
+ border-color: color-mix(in srgb, var(--token-info) 20%, transparent);
5214
+ color: var(--token-info);
5215
+ }
5216
+ .empty--search .empty-icon {
5217
+ background: var(--token-fill-1);
5218
+ color: var(--token-text-muted);
5219
+ }
5220
+
5221
+ /* ── 标题与描述 ── */
4034
5222
  .empty-title {
4035
5223
  font-size: var(--density-font-size-md);
4036
5224
  font-weight: 500;
@@ -4038,17 +5226,55 @@ html {
4038
5226
  margin: 0;
4039
5227
  line-height: 1.4;
4040
5228
  }
5229
+ .empty--error .empty-title { color: var(--token-error); }
4041
5230
  .empty-desc {
4042
5231
  font-size: var(--density-font-size-sm);
4043
5232
  color: var(--token-text-muted);
4044
5233
  margin: 4px 0 0;
4045
- max-width: 240px;
5234
+ max-width: 280px;
4046
5235
  line-height: 1.5;
4047
5236
  }
5237
+
5238
+ /* ── 操作区 ── */
4048
5239
  .empty-action {
4049
5240
  margin-top: var(--density-space-md);
4050
5241
  display: flex;
4051
5242
  gap: var(--density-space-xs);
5243
+ flex-wrap: wrap;
5244
+ justify-content: center;
5245
+ }
5246
+
5247
+ /* ── 404 大数字 ── */
5248
+ .empty--404 .empty-title {
5249
+ font-size: 72px;
5250
+ font-weight: 800;
5251
+ font-family: var(--font-heading);
5252
+ line-height: 1;
5253
+ letter-spacing: -0.02em;
5254
+ color: var(--token-text-faint);
5255
+ margin-bottom: var(--density-space-sm);
5256
+ }
5257
+ .empty--404 .empty-icon { display: none; }
5258
+
5259
+ /* ── 插画背景(使用方可用渐变背景替代图标) ── */
5260
+ .empty--illustrated {
5261
+ background: radial-gradient(ellipse at center,
5262
+ color-mix(in srgb, var(--token-accent) 6%, transparent) 0%,
5263
+ transparent 70%);
5264
+ border-radius: var(--radius-lg);
5265
+ }
5266
+
5267
+ /* ── 内嵌模式(empty 嵌入卡片/面板内时无 padding) ── */
5268
+ .empty--inline {
5269
+ padding: var(--density-space-md) var(--density-space-sm);
5270
+ }
5271
+
5272
+ @media (max-width: 480px) {
5273
+ .empty--404 .empty-title { font-size: 56px; }
5274
+ }
5275
+
5276
+ @media (prefers-reduced-motion: reduce) {
5277
+ .empty { animation: none; }
4052
5278
  }
4053
5279
 
4054
5280
 
@@ -4070,6 +5296,8 @@ html {
4070
5296
  box-shadow: var(--token-shadow-inset);
4071
5297
  border: 1px solid transparent;
4072
5298
  font-size: var(--density-font-size-sm);
5299
+ position: relative;
5300
+ overflow: hidden;
4073
5301
  }
4074
5302
  .alert--info { background: var(--token-info-muted); border-color: color-mix(in srgb, var(--token-info) 18%, transparent); }
4075
5303
  .alert--success { background: var(--token-success-muted); border-color: color-mix(in srgb, var(--token-success) 18%, transparent); }
@@ -4264,54 +5492,290 @@ html {
4264
5492
  50% { opacity: 0.5; }
4265
5493
  }
4266
5494
 
4267
- @media (prefers-reduced-motion: reduce) {
4268
- .progress-bar { transition: none; }
4269
- .progress--indeterminate .progress-bar { animation: none; width: 100% !important; }
4270
- .spinner { animation-duration: 2s; }
4271
- .skeleton { animation: none; opacity: 0.6; }
4272
- }
4273
-
4274
-
4275
- /* ── components/form.css ── */
4276
- /*
4277
- * @icen.ai/ui — Component: form(表单结构:字段组 / label / hint / 横幅提示)
4278
- *
4279
- * 与 input.css 的 wrapper 基元互补:
4280
- * - input.css 处理"单个 input 的视觉外壳"
4281
- * - form.css 处理"字段→label→hint 的纵向布局"和"页面级结果横幅"
4282
- *
4283
- * 契约:
4284
- * <form class="form">
4285
- * <fieldset class="form-field">
4286
- * <label class="form-label" for="x">姓名</label>
4287
- * <span class="control"><input id="x" class="form-input" /></span>
4288
- * <span class="form-hint">必填,3-20 个字符</span> <!-- 可选 -->
4289
- * <span class="form-error">姓名长度不足</span> <!-- 可选,aria-live=polite -->
4290
- * </fieldset>
4291
- * </form>
4292
- *
4293
- * <div class="banner-ok [.banner-block]">已保存</div>
4294
- * <div class="banner-err [.banner-block]">提交失败:网络错误</div>
4295
- *
4296
- * form-input 同时支持 :invalid 与 [data-invalid=true],behavior 二选一驱动。
4297
- */
4298
-
4299
- .form { display: flex; flex-direction: column; gap: var(--density-space-md); }
5495
+ /* ── alert 增强 ── */
4300
5496
 
4301
- .form-field {
5497
+ /* 可操作横幅(含按钮) */
5498
+ .alert-actions {
4302
5499
  display: flex;
4303
- flex-direction: column;
4304
5500
  gap: var(--density-space-xs);
4305
- margin: 0;
4306
- padding: 0;
4307
- border: 0;
5501
+ margin-top: var(--density-space-xs);
5502
+ }
5503
+ .alert--banner {
5504
+ border-radius: 0;
5505
+ border-left: none;
5506
+ border-right: none;
5507
+ border-top: none;
5508
+ }
5509
+ .alert--inline {
5510
+ display: inline-flex;
5511
+ padding: 2px var(--density-space-sm);
5512
+ font-size: var(--density-font-size-xs);
4308
5513
  }
5514
+ .alert--inline .alert-close { width: 16px; height: 16px; }
4309
5515
 
4310
- .form-label {
5516
+ /* ── result 增强 ── */
5517
+ .result-list {
5518
+ margin: var(--density-space-sm) 0 0;
5519
+ padding: var(--density-space-sm);
5520
+ background: var(--token-bg-soft);
5521
+ border-radius: var(--radius-md);
4311
5522
  font-size: var(--density-font-size-sm);
4312
5523
  color: var(--token-text-muted);
4313
- letter-spacing: 0.02em;
4314
- font-weight: 500;
5524
+ text-align: left;
5525
+ max-width: 400px;
5526
+ max-height: 200px;
5527
+ overflow-y: auto;
5528
+ }
5529
+ .result-list-item {
5530
+ display: flex;
5531
+ align-items: flex-start;
5532
+ gap: var(--density-space-xs);
5533
+ padding: 2px 0;
5534
+ font-variant-numeric: tabular-nums;
5535
+ word-break: break-all;
5536
+ }
5537
+
5538
+ /* ── progress 环形 ── */
5539
+ .progress-ring-wrap {
5540
+ display: inline-flex;
5541
+ align-items: center;
5542
+ justify-content: center;
5543
+ position: relative;
5544
+ }
5545
+ .progress-ring {
5546
+ transform: rotate(-90deg);
5547
+ }
5548
+ .progress-ring-track {
5549
+ fill: none;
5550
+ stroke: var(--token-fill-2);
5551
+ stroke-width: 4;
5552
+ }
5553
+ .progress-ring-fill {
5554
+ fill: none;
5555
+ stroke: var(--token-accent);
5556
+ stroke-width: 4;
5557
+ stroke-linecap: round;
5558
+ transition: stroke-dashoffset var(--icen-tx-slow) var(--ease-out);
5559
+ }
5560
+ .progress-ring-text {
5561
+ position: absolute;
5562
+ font-size: var(--density-font-size-sm);
5563
+ font-weight: 600;
5564
+ font-variant-numeric: tabular-nums;
5565
+ color: var(--token-text);
5566
+ }
5567
+ .progress-ring--sm .progress-ring-track,
5568
+ .progress-ring--sm .progress-ring-fill { stroke-width: 3; }
5569
+ .progress-ring--lg .progress-ring-track,
5570
+ .progress-ring--lg .progress-ring-fill { stroke-width: 6; }
5571
+ .progress-ring--success .progress-ring-fill { stroke: var(--token-success); }
5572
+ .progress-ring--warning .progress-ring-fill { stroke: var(--token-warning); }
5573
+ .progress-ring--error .progress-ring-fill { stroke: var(--token-error); }
5574
+
5575
+ /* progress 条纹动画 */
5576
+ .progress--striped .progress-bar {
5577
+ background-image: linear-gradient(
5578
+ 45deg,
5579
+ color-mix(in srgb, var(--token-card-strong) 20%, transparent) 25%,
5580
+ transparent 25%,
5581
+ transparent 50%,
5582
+ color-mix(in srgb, var(--token-card-strong) 20%, transparent) 50%,
5583
+ color-mix(in srgb, var(--token-card-strong) 20%, transparent) 75%,
5584
+ transparent 75%,
5585
+ transparent
5586
+ );
5587
+ background-size: 20px 20px;
5588
+ animation: progress-stripe 1s linear infinite;
5589
+ }
5590
+ @keyframes progress-stripe {
5591
+ from { background-position: 0 0; }
5592
+ to { background-position: 20px 0; }
5593
+ }
5594
+
5595
+ /* progress 渐变 */
5596
+ .progress--gradient .progress-bar {
5597
+ background: linear-gradient(90deg,
5598
+ var(--token-accent),
5599
+ color-mix(in srgb, var(--token-accent) 60%, var(--token-info)));
5600
+ }
5601
+
5602
+ /* ── spinner 变体 ── */
5603
+
5604
+ /* 点阵 spinner */
5605
+ .spinner--dots {
5606
+ width: auto;
5607
+ height: auto;
5608
+ border: none;
5609
+ display: inline-flex;
5610
+ gap: 3px;
5611
+ background: none;
5612
+ animation: none;
5613
+ }
5614
+ .spinner--dots span {
5615
+ display: block;
5616
+ width: 6px;
5617
+ height: 6px;
5618
+ border-radius: var(--radius-full);
5619
+ background: var(--token-accent);
5620
+ animation: spinner-dot 1.4s ease-in-out infinite;
5621
+ }
5622
+ .spinner--dots span:nth-child(2) { animation-delay: 0.2s; }
5623
+ .spinner--dots span:nth-child(3) { animation-delay: 0.4s; }
5624
+ @keyframes spinner-dot {
5625
+ 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
5626
+ 40% { opacity: 1; transform: scale(1); }
5627
+ }
5628
+
5629
+ /* 条形 spinner */
5630
+ .spinner--bars {
5631
+ width: auto;
5632
+ height: auto;
5633
+ border: none;
5634
+ display: inline-flex;
5635
+ align-items: flex-end;
5636
+ gap: 2px;
5637
+ background: none;
5638
+ animation: none;
5639
+ }
5640
+ .spinner--bars span {
5641
+ display: block;
5642
+ width: 3px;
5643
+ height: 100%;
5644
+ background: var(--token-accent);
5645
+ border-radius: 1px;
5646
+ animation: spinner-bar 1s ease-in-out infinite;
5647
+ }
5648
+ .spinner--bars span:nth-child(2) { animation-delay: 0.1s; }
5649
+ .spinner--bars span:nth-child(3) { animation-delay: 0.2s; }
5650
+ .spinner--bars span:nth-child(4) { animation-delay: 0.3s; }
5651
+ @keyframes spinner-bar {
5652
+ 0%, 100% { transform: scaleY(0.4); }
5653
+ 50% { transform: scaleY(1); }
5654
+ }
5655
+
5656
+ /* 全屏遮罩 spinner */
5657
+ .spinner-overlay {
5658
+ position: fixed;
5659
+ inset: 0;
5660
+ z-index: var(--z-overlay, 9999);
5661
+ display: flex;
5662
+ align-items: center;
5663
+ justify-content: center;
5664
+ background: color-mix(in srgb, var(--token-card-strong) 70%, transparent);
5665
+ backdrop-filter: blur(2px);
5666
+ -webkit-backdrop-filter: blur(2px);
5667
+ flex-direction: column;
5668
+ gap: var(--density-space-md);
5669
+ }
5670
+ .spinner-overlay-text {
5671
+ font-size: var(--density-font-size-sm);
5672
+ color: var(--token-text-muted);
5673
+ }
5674
+
5675
+ /* ── skeleton 复合体 ── */
5676
+
5677
+ /* 骨架文本(多行模拟) */
5678
+ .skeleton-text-group {
5679
+ display: flex;
5680
+ flex-direction: column;
5681
+ gap: var(--density-space-xs);
5682
+ flex: 1;
5683
+ }
5684
+
5685
+ /* 头像 + 文本行组合(评论/消息占位) */
5686
+ .skeleton-media {
5687
+ display: flex;
5688
+ align-items: center;
5689
+ gap: var(--density-space-sm);
5690
+ }
5691
+
5692
+ /* 表格骨架 */
5693
+ .skeleton-table {
5694
+ display: flex;
5695
+ flex-direction: column;
5696
+ gap: var(--density-space-sm);
5697
+ }
5698
+ .skeleton-table-row {
5699
+ display: flex;
5700
+ gap: var(--density-space-sm);
5701
+ }
5702
+ .skeleton-table-row > * {
5703
+ height: 12px;
5704
+ border-radius: var(--radius-xs);
5705
+ background: color-mix(in srgb, var(--token-text) 8%, transparent);
5706
+ animation: icen-skeleton-pulse 2s ease-in-out infinite;
5707
+ }
5708
+
5709
+ /* 骨架波纹变体(shimmer,区别于 pulse) */
5710
+ .skeleton--shimmer {
5711
+ background: linear-gradient(
5712
+ 90deg,
5713
+ color-mix(in srgb, var(--token-text) 6%, transparent) 0%,
5714
+ color-mix(in srgb, var(--token-text) 12%, transparent) 50%,
5715
+ color-mix(in srgb, var(--token-text) 6%, transparent) 100%
5716
+ );
5717
+ background-size: 200% 100%;
5718
+ animation: skeleton-shimmer 1.5s ease-in-out infinite;
5719
+ }
5720
+ @keyframes skeleton-shimmer {
5721
+ from { background-position: 200% 0; }
5722
+ to { background-position: -200% 0; }
5723
+ }
5724
+
5725
+ @media (prefers-reduced-motion: reduce) {
5726
+ .progress-bar { transition: none; }
5727
+ .progress--indeterminate .progress-bar { animation: none; width: 100% !important; }
5728
+ .progress--striped .progress-bar { animation: none; }
5729
+ .progress-ring-fill { transition: none; }
5730
+ .spinner { animation-duration: 2s; }
5731
+ .spinner--dots span,
5732
+ .spinner--bars span { animation: none; }
5733
+ .skeleton,
5734
+ .skeleton-table-row > * { animation: none; opacity: 0.6; }
5735
+ .skeleton--shimmer { animation: none; }
5736
+ }
5737
+
5738
+
5739
+ /* ── components/form.css ── */
5740
+ /*
5741
+ * @icen.ai/ui — Component: form(表单结构:字段组 / label / hint / 横幅提示)
5742
+ *
5743
+ * 与 input.css 的 wrapper 基元互补:
5744
+ * - input.css 处理"单个 input 的视觉外壳"
5745
+ * - form.css 处理"字段→label→hint 的纵向布局"和"页面级结果横幅"
5746
+ *
5747
+ * 契约:
5748
+ * <form class="form">
5749
+ * <fieldset class="form-field">
5750
+ * <label class="form-label" for="x">姓名</label>
5751
+ * <span class="control"><input id="x" class="form-input" /></span>
5752
+ * <span class="form-hint">必填,3-20 个字符</span> <!-- 可选 -->
5753
+ * <span class="form-error">姓名长度不足</span> <!-- 可选,aria-live=polite -->
5754
+ * </fieldset>
5755
+ * </form>
5756
+ *
5757
+ * <div class="banner-ok [.banner-block]">已保存</div>
5758
+ * <div class="banner-err [.banner-block]">提交失败:网络错误</div>
5759
+ *
5760
+ * form-input 同时支持 :invalid 与 [data-invalid=true],behavior 二选一驱动。
5761
+ */
5762
+
5763
+ .form { display: flex; flex-direction: column; gap: var(--density-space-md); }
5764
+
5765
+ .form-field {
5766
+ display: flex;
5767
+ flex-direction: column;
5768
+ gap: var(--density-space-xs);
5769
+ margin: 0;
5770
+ padding: 0;
5771
+ border: 0;
5772
+ }
5773
+
5774
+ .form-label {
5775
+ font-size: var(--density-font-size-sm);
5776
+ color: var(--token-text-muted);
5777
+ letter-spacing: 0.02em;
5778
+ font-weight: 500;
4315
5779
  }
4316
5780
  .form-label .req { color: var(--token-error); margin-left: 2px; }
4317
5781
 
@@ -4389,6 +5853,125 @@ html {
4389
5853
  }
4390
5854
  .form-field[data-invalid='true'] .form-label { color: var(--token-error); }
4391
5855
 
5856
+ /* ── 表单增强 ── */
5857
+
5858
+ /* 水平布局(label 与控件同行) */
5859
+ .form-field--horizontal {
5860
+ display: flex;
5861
+ flex-direction: row;
5862
+ align-items: flex-start;
5863
+ gap: var(--density-space-md);
5864
+ }
5865
+ .form-field--horizontal .form-label {
5866
+ flex-shrink: 0;
5867
+ width: 120px;
5868
+ text-align: right;
5869
+ padding-top: calc((var(--density-input-height-md) - 1lh) / 2);
5870
+ }
5871
+ .form-field--horizontal .form-field-control {
5872
+ flex: 1;
5873
+ min-width: 0;
5874
+ }
5875
+
5876
+ /* label 带帮助提示图标 */
5877
+ .form-label-help {
5878
+ display: inline-flex;
5879
+ align-items: center;
5880
+ margin-left: 4px;
5881
+ width: 14px;
5882
+ height: 14px;
5883
+ color: var(--token-text-faint);
5884
+ cursor: help;
5885
+ }
5886
+ .form-label-help svg { width: 12px; height: 12px; }
5887
+
5888
+ /* label 带可选标记 */
5889
+ .form-label-optional {
5890
+ font-size: 10px;
5891
+ font-weight: 400;
5892
+ color: var(--token-text-faint);
5893
+ margin-left: 4px;
5894
+ }
5895
+
5896
+ /* 表单区块标题 */
5897
+ .form-section {
5898
+ padding: var(--density-space-sm) 0;
5899
+ }
5900
+ .form-section-title {
5901
+ font-size: var(--density-font-size-md);
5902
+ font-weight: 600;
5903
+ color: var(--token-text);
5904
+ margin: 0 0 var(--density-space-sm);
5905
+ padding-bottom: var(--density-space-xs);
5906
+ border-bottom: 1px solid var(--token-outline-soft);
5907
+ }
5908
+ .form-section-desc {
5909
+ font-size: var(--density-font-size-sm);
5910
+ color: var(--token-text-muted);
5911
+ margin: calc(-1 * var(--density-space-xs)) 0 var(--density-space-sm);
5912
+ }
5913
+
5914
+ /* 表单操作区(底部按钮行) */
5915
+ .form-actions {
5916
+ display: flex;
5917
+ align-items: center;
5918
+ gap: var(--density-space-sm);
5919
+ padding-top: var(--density-space-sm);
5920
+ border-top: 1px solid var(--token-outline-soft);
5921
+ }
5922
+ .form-actions--right { justify-content: flex-end; }
5923
+ .form-actions--center { justify-content: center; }
5924
+ .form-actions--split { justify-content: space-between; }
5925
+
5926
+ /* 行内表单(紧凑,单行多字段) */
5927
+ .form--inline {
5928
+ flex-direction: row;
5929
+ align-items: flex-end;
5930
+ gap: var(--density-space-sm);
5931
+ flex-wrap: wrap;
5932
+ }
5933
+ .form--inline .form-field {
5934
+ flex: 1;
5935
+ min-width: 160px;
5936
+ }
5937
+
5938
+ /* 字段网格(多列布局) */
5939
+ .form-grid {
5940
+ display: grid;
5941
+ grid-template-columns: repeat(var(--form-cols, 2), 1fr);
5942
+ gap: var(--density-space-md);
5943
+ }
5944
+ .form-grid .form-field--full { grid-column: 1 / -1; }
5945
+
5946
+ /* 字段成功态 */
5947
+ .form-field[data-valid='true'] .form-label::after {
5948
+ content: '✓';
5949
+ margin-left: 4px;
5950
+ color: var(--token-success);
5951
+ font-weight: 700;
5952
+ }
5953
+
5954
+ /* 表单加载遮罩 */
5955
+ .form-loading {
5956
+ position: relative;
5957
+ pointer-events: none;
5958
+ opacity: 0.6;
5959
+ }
5960
+ .form-loading::after {
5961
+ content: '';
5962
+ position: absolute;
5963
+ top: 50%;
5964
+ left: 50%;
5965
+ width: 24px;
5966
+ height: 24px;
5967
+ margin: -12px 0 0 -12px;
5968
+ border: 2px solid color-mix(in srgb, var(--token-accent) 30%, transparent);
5969
+ border-top-color: var(--token-accent);
5970
+ border-radius: 50%;
5971
+ animation: form-loading-spin 0.8s linear infinite;
5972
+ }
5973
+ @keyframes form-loading-spin { to { transform: rotate(360deg); } }
5974
+
4392
5975
  /* ── 页面级横幅:操作结果 / 引导提示 ── */
4393
5976
  .banner-ok,
4394
5977
  .banner-err {
@@ -4614,7 +6197,19 @@ html {
4614
6197
  color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
4615
6198
  }
4616
6199
 
4617
- /* 字符计数(behavior 可挂到 .textarea-wrap 内尾部) */
6200
+ /* 字符计数(behavior 可挂到 .input-wrap 或 .textarea-wrap 内尾部) */
6201
+ .input-wrap { position: relative; }
6202
+ .input-wrap--has-counter { padding-right: calc(var(--density-space-sm) + 48px); }
6203
+ .input-wrap--has-counter .input-counter {
6204
+ position: absolute;
6205
+ right: var(--density-space-sm);
6206
+ top: 50%;
6207
+ transform: translateY(-50%);
6208
+ font-size: var(--density-font-size-xs);
6209
+ color: var(--token-text-faint);
6210
+ font-variant-numeric: tabular-nums;
6211
+ pointer-events: none;
6212
+ }
4618
6213
  .input-counter {
4619
6214
  position: absolute;
4620
6215
  right: var(--density-space-sm);
@@ -4623,10 +6218,21 @@ html {
4623
6218
  color: var(--token-text-faint);
4624
6219
  font-variant-numeric: tabular-nums;
4625
6220
  pointer-events: none;
6221
+ transition: color var(--icen-tx-fast);
4626
6222
  }
6223
+ .input-counter.is-warn { color: var(--token-warning); }
6224
+ .input-counter.is-error { color: var(--token-error); font-weight: 600; }
6225
+
4627
6226
  .textarea-wrap { position: relative; }
4628
6227
  .textarea-wrap .textarea { padding-bottom: 24px; }
4629
6228
 
6229
+ /* 有计数器时的 textarea 底部留空 */
6230
+ .textarea-wrap .input-counter {
6231
+ position: absolute;
6232
+ right: var(--density-space-sm);
6233
+ bottom: var(--density-space-xs);
6234
+ }
6235
+
4630
6236
 
4631
6237
  /* ── components/layout.css ── */
4632
6238
  /*
@@ -4802,21 +6408,57 @@ html {
4802
6408
  .avatar-status--busy { background: var(--token-error); }
4803
6409
  .avatar-status--away { background: var(--token-warning); }
4804
6410
 
4805
- /* ══════════════ media-card · 封面卡 ══════════════ */
4806
- .media-card {
4807
- position: relative;
4808
- overflow: hidden;
4809
- border-radius: var(--radius-lg);
4810
- background: var(--token-bg-soft);
4811
- transition:
4812
- transform var(--icen-tx-slow),
4813
- box-shadow var(--icen-tx-slow);
4814
- }
4815
- .media-card:hover {
4816
- transform: translateY(-6px);
4817
- box-shadow:
4818
- 0 8px 32px color-mix(in srgb, var(--token-text) 18%, transparent),
4819
- 0 0 20px var(--token-accent-glow);
6411
+ /* 头像色彩变体(文字头像底色) */
6412
+ .avatar--info { background: color-mix(in srgb, var(--token-info) 15%, transparent); color: var(--token-info); }
6413
+ .avatar--success { background: color-mix(in srgb, var(--token-success) 15%, transparent); color: var(--token-success); }
6414
+ .avatar--warning { background: color-mix(in srgb, var(--token-warning) 15%, transparent); color: var(--token-warning); }
6415
+ .avatar--error { background: color-mix(in srgb, var(--token-error) 15%, transparent); color: var(--token-error); }
6416
+
6417
+ /* 渐变底头像 */
6418
+ .avatar--gradient {
6419
+ background: linear-gradient(135deg, var(--token-accent), var(--token-accent-deep, var(--token-accent)));
6420
+ color: var(--token-on-accent, #fff);
6421
+ }
6422
+
6423
+ /* 方形头像 */
6424
+ .avatar--square { border-radius: var(--radius-md); }
6425
+
6426
+ /* 头像通知环(红圈外框) */
6427
+ .avatar--ring {
6428
+ box-shadow: 0 0 0 2px var(--token-bg), 0 0 0 4px var(--token-error);
6429
+ }
6430
+
6431
+ /* 头像组溢出计数 */
6432
+ .avatar-group-count {
6433
+ display: inline-flex;
6434
+ align-items: center;
6435
+ justify-content: center;
6436
+ width: 28px;
6437
+ height: 28px;
6438
+ font-size: 10px;
6439
+ font-weight: 600;
6440
+ color: var(--token-text-muted);
6441
+ background: var(--token-fill-2);
6442
+ border: 2px solid var(--token-bg);
6443
+ border-radius: var(--radius-full);
6444
+ margin-left: -8px;
6445
+ }
6446
+
6447
+ /* ══════════════ media-card · 封面卡 ══════════════ */
6448
+ .media-card {
6449
+ position: relative;
6450
+ overflow: hidden;
6451
+ border-radius: var(--radius-lg);
6452
+ background: var(--token-bg-soft);
6453
+ transition:
6454
+ transform var(--icen-tx-slow),
6455
+ box-shadow var(--icen-tx-slow);
6456
+ }
6457
+ .media-card:hover {
6458
+ transform: translateY(-6px);
6459
+ box-shadow:
6460
+ 0 8px 32px color-mix(in srgb, var(--token-text) 18%, transparent),
6461
+ 0 0 20px var(--token-accent-glow);
4820
6462
  }
4821
6463
  .media-card-cover {
4822
6464
  display: block;
@@ -4907,6 +6549,53 @@ html {
4907
6549
  font-variant-numeric: tabular-nums;
4908
6550
  }
4909
6551
 
6552
+ /* 只读模式(不可交互) */
6553
+ .rating--readonly .rating-star {
6554
+ cursor: default;
6555
+ }
6556
+ .rating--readonly .rating-star:hover {
6557
+ transform: none;
6558
+ }
6559
+
6560
+ /* 清除评分钮 */
6561
+ .rating-clear {
6562
+ display: inline-flex;
6563
+ align-items: center;
6564
+ justify-content: center;
6565
+ width: 20px;
6566
+ height: 20px;
6567
+ margin-left: 4px;
6568
+ padding: 0;
6569
+ border: 0;
6570
+ border-radius: var(--radius-xs);
6571
+ background: transparent;
6572
+ color: var(--token-text-faint);
6573
+ cursor: pointer;
6574
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
6575
+ }
6576
+ .rating-clear:hover {
6577
+ color: var(--token-text);
6578
+ background: var(--token-fill-1);
6579
+ }
6580
+ .rating-clear svg { width: 12px; height: 12px; }
6581
+
6582
+ /* 评分数(评价人数) */
6583
+ .rating-count {
6584
+ margin-left: var(--density-space-xs);
6585
+ font-size: var(--density-font-size-xs);
6586
+ color: var(--token-text-faint);
6587
+ font-variant-numeric: tabular-nums;
6588
+ }
6589
+
6590
+ /* 评分 + 文字摘要(如「4.5 · 128 条评价」) */
6591
+ .rating-summary {
6592
+ display: inline-flex;
6593
+ align-items: center;
6594
+ gap: var(--density-space-xs);
6595
+ font-size: var(--density-font-size-sm);
6596
+ color: var(--token-text-muted);
6597
+ }
6598
+
4910
6599
  /* ══════════════ carousel · 轮播(behaviors/carousel 配套) ══════════════
4911
6600
  * DOM 契约:
4912
6601
  * <div class="carousel" data-carousel>
@@ -5017,9 +6706,69 @@ html {
5017
6706
  transition: width var(--icen-tx-slow);
5018
6707
  }
5019
6708
 
6709
+ /* 缩略图导航条 */
6710
+ .carousel-thumbs {
6711
+ display: flex;
6712
+ gap: var(--density-space-xs);
6713
+ margin-top: var(--density-space-sm);
6714
+ padding: var(--density-space-xs);
6715
+ overflow-x: auto;
6716
+ scrollbar-width: none;
6717
+ }
6718
+ .carousel-thumbs::-webkit-scrollbar { display: none; }
6719
+ .carousel-thumb {
6720
+ flex-shrink: 0;
6721
+ width: 56px;
6722
+ height: 40px;
6723
+ border-radius: var(--radius-sm);
6724
+ overflow: hidden;
6725
+ border: 2px solid transparent;
6726
+ cursor: pointer;
6727
+ opacity: 0.6;
6728
+ transition: opacity var(--icen-tx-fast), border-color var(--icen-tx-fast);
6729
+ }
6730
+ .carousel-thumb:hover { opacity: 1; }
6731
+ .carousel-thumb.is-active,
6732
+ .carousel-thumb[aria-current='true'] {
6733
+ opacity: 1;
6734
+ border-color: var(--token-accent);
6735
+ }
6736
+ .carousel-thumb img,
6737
+ .carousel-thumb video {
6738
+ width: 100%;
6739
+ height: 100%;
6740
+ object-fit: cover;
6741
+ }
6742
+
6743
+ /* 轮播索引指示器(1 / 5) */
6744
+ .carousel-index {
6745
+ position: absolute;
6746
+ top: var(--density-space-sm);
6747
+ right: var(--density-space-sm);
6748
+ padding: 2px var(--density-space-sm);
6749
+ font-size: var(--density-font-size-xs);
6750
+ font-variant-numeric: tabular-nums;
6751
+ color: var(--token-bg);
6752
+ background: color-mix(in srgb, var(--token-text) 60%, transparent);
6753
+ border-radius: var(--radius-full);
6754
+ backdrop-filter: blur(4px);
6755
+ -webkit-backdrop-filter: blur(4px);
6756
+ z-index: 2;
6757
+ }
6758
+
6759
+ /* 渐入动画 */
6760
+ .carousel-slide {
6761
+ animation: carousel-slide-fade var(--icen-tx-normal) var(--ease-out);
6762
+ }
6763
+ @keyframes carousel-slide-fade {
6764
+ from { opacity: 0.6; }
6765
+ to { opacity: 1; }
6766
+ }
6767
+
5020
6768
  @media (prefers-reduced-motion: reduce) {
5021
6769
  .carousel-track { transition: none; }
5022
- .media-card, .rating-star, .carousel-arrow { transition: none; }
6770
+ .carousel-slide { animation: none; }
6771
+ .media-card, .rating-star, .carousel-arrow, .carousel-thumb { transition: none; }
5023
6772
  }
5024
6773
 
5025
6774
 
@@ -5617,6 +7366,495 @@ html {
5617
7366
  .nav, .nav-btn { transition: none; }
5618
7367
  }
5619
7368
 
7369
+ /* ── 增强 ── */
7370
+
7371
+ /* 导航搜索槽 */
7372
+ .nav-search {
7373
+ display: flex;
7374
+ align-items: center;
7375
+ gap: var(--density-space-xs);
7376
+ padding: 0 var(--density-space-sm);
7377
+ height: 32px;
7378
+ min-width: 180px;
7379
+ background: var(--token-control);
7380
+ border: 1px solid var(--token-outline-soft);
7381
+ border-radius: var(--radius-full);
7382
+ color: var(--token-text-faint);
7383
+ font-size: var(--density-font-size-sm);
7384
+ cursor: pointer;
7385
+ transition: border-color var(--icen-tx-fast);
7386
+ }
7387
+ .nav-search:hover { border-color: var(--token-accent); }
7388
+ .nav-search svg { width: 14px; height: 14px; flex-shrink: 0; }
7389
+ .nav-search-kbd {
7390
+ margin-left: auto;
7391
+ padding: 1px 5px;
7392
+ font-size: 10px;
7393
+ font-family: var(--font-mono);
7394
+ color: var(--token-text-faint);
7395
+ background: var(--token-fill-2);
7396
+ border-radius: var(--radius-xs);
7397
+ }
7398
+
7399
+ /* 导航按钮上的通知徽标 */
7400
+ .nav-btn-badge {
7401
+ position: absolute;
7402
+ top: 2px;
7403
+ right: 2px;
7404
+ min-width: 8px;
7405
+ height: 8px;
7406
+ padding: 0;
7407
+ background: var(--token-error);
7408
+ border: 1.5px solid var(--token-surface-glass);
7409
+ border-radius: var(--radius-full);
7410
+ pointer-events: none;
7411
+ }
7412
+ .nav-btn { position: relative; }
7413
+ .nav-btn-badge--count {
7414
+ min-width: 16px;
7415
+ height: 16px;
7416
+ padding: 0 4px;
7417
+ font-size: 9px;
7418
+ font-weight: 700;
7419
+ color: var(--token-on-accent, #fff);
7420
+ display: inline-flex;
7421
+ align-items: center;
7422
+ justify-content: center;
7423
+ line-height: 1;
7424
+ }
7425
+
7426
+ /* 链接式导航项(a 标签替代 button) */
7427
+ .nav-link {
7428
+ display: inline-flex;
7429
+ align-items: center;
7430
+ gap: var(--density-space-xs);
7431
+ padding: 6px var(--density-space-sm);
7432
+ font-size: var(--density-font-size-md);
7433
+ color: var(--token-text-muted);
7434
+ text-decoration: none;
7435
+ border-radius: var(--radius-full);
7436
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
7437
+ }
7438
+ .nav-link:hover {
7439
+ color: var(--token-text);
7440
+ background: var(--token-control-hover);
7441
+ text-decoration: none;
7442
+ }
7443
+ .nav-link.is-active,
7444
+ .nav-link[aria-current='page'] {
7445
+ color: var(--token-accent);
7446
+ background: var(--token-accent-subtle);
7447
+ }
7448
+
7449
+ /* 导航用户区(头像 + 下拉触发器) */
7450
+ .nav-user {
7451
+ display: inline-flex;
7452
+ align-items: center;
7453
+ gap: var(--density-space-xs);
7454
+ padding: 3px var(--density-space-xs) 3px 3px;
7455
+ border-radius: var(--radius-full);
7456
+ border: 1px solid var(--token-outline-soft);
7457
+ background: var(--token-surface);
7458
+ cursor: pointer;
7459
+ transition: border-color var(--icen-tx-fast);
7460
+ }
7461
+ .nav-user:hover { border-color: var(--token-accent); }
7462
+ .nav-user-name {
7463
+ font-size: var(--density-font-size-sm);
7464
+ color: var(--token-text);
7465
+ }
7466
+ .nav-user svg { width: 12px; height: 12px; color: var(--token-text-faint); }
7467
+
7468
+
7469
+ /* ── components/notification.css ── */
7470
+ /*
7471
+ * @icen.ai/ui — Component: notification(通知栈 v2,与 behaviors/notification 配套)
7472
+ *
7473
+ * 与 toast / alert 的差异(三件正交):
7474
+ * - toast = 瞬时轻提示(2.6s 自动消失),右下角
7475
+ * - alert = 内嵌警示条(页面流内,非浮层)
7476
+ * - notification = 持久通知栈(右上角),工程级:
7477
+ * 进度条 / 倒计时 / 头像 / 多按钮 / 已读未读 / 优先级 / hover 暂停 / 详情链接 / 折叠堆叠
7478
+ *
7479
+ * 完整契约(由 behavior 渲染):
7480
+ * <div class="notification-container" role="region" aria-label="通知" data-position="top-right">
7481
+ * <div class="notification [notif--success|warning|error|info]
7482
+ * [.is-priority] [.is-unread] [.is-progress] [.is-clickable] [.is-paused]
7483
+ * [.has-avatar]" role="alert|button">
7484
+ * <span class="notification-icon">…svg 或 img.notification-avatar…</span>
7485
+ * <div class="notification-content">
7486
+ * <p class="notification-title">标题</p>
7487
+ * <p class="notification-message">正文(支持换行)</p>
7488
+ * <div class="notification-actions">
7489
+ * <button class="notification-action [--primary|--danger]">按钮</button>
7490
+ * </div>
7491
+ * <div class="notification-link-wrap">
7492
+ * <a class="notification-link" href="…">查看详情</a>
7493
+ * </div>
7494
+ * <div class="notification-progress">
7495
+ * <div class="notification-progress-bar" style="width:64%"></div>
7496
+ * <span class="notification-progress-label">64%</span>
7497
+ * </div>
7498
+ * </div>
7499
+ * <button class="notification-close" aria-label="关闭">×</button>
7500
+ * <div class="notification-countdown">
7501
+ * <div class="notification-countdown-fill" style="animation-duration:5000ms"></div>
7502
+ * </div>
7503
+ * </div>
7504
+ * </div>
7505
+ */
7506
+
7507
+ .notification-container {
7508
+ position: fixed;
7509
+ top: var(--density-space-md);
7510
+ right: var(--density-space-md);
7511
+ z-index: var(--z-toast);
7512
+ display: flex;
7513
+ flex-direction: column;
7514
+ gap: var(--density-space-sm);
7515
+ width: 360px;
7516
+ max-width: calc(100vw - var(--density-space-lg));
7517
+ pointer-events: none;
7518
+ }
7519
+ .notification-container[data-position='top-left'] {
7520
+ right: auto;
7521
+ left: var(--density-space-md);
7522
+ }
7523
+ .notification-container[data-position='bottom-left'] {
7524
+ top: auto;
7525
+ bottom: var(--density-space-md);
7526
+ right: auto;
7527
+ left: var(--density-space-md);
7528
+ }
7529
+ .notification-container[data-position='bottom-right'] {
7530
+ top: auto;
7531
+ bottom: var(--density-space-md);
7532
+ }
7533
+
7534
+ .notification {
7535
+ pointer-events: auto;
7536
+ position: relative;
7537
+ display: flex;
7538
+ align-items: flex-start;
7539
+ gap: var(--density-space-sm);
7540
+ background: var(--token-card-strong);
7541
+ color: var(--token-text);
7542
+ border: 1px solid var(--token-outline-soft);
7543
+ border-left: 3px solid var(--token-info);
7544
+ border-radius: var(--radius-md);
7545
+ box-shadow: var(--token-shadow-overlay);
7546
+ padding: var(--density-space-md) var(--density-space-md) var(--density-space-md) var(--density-space-sm);
7547
+ overflow: hidden; /* 倒计时进度条贴底 */
7548
+ animation: icen-notif-in var(--icen-tx-normal);
7549
+ }
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); }
7554
+
7555
+ /* ── 优先级:高优先级强调(左侧加粗 + 更强阴影)── */
7556
+ .notification.is-priority {
7557
+ border-left-width: 4px;
7558
+ box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-accent) 24%, transparent);
7559
+ }
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); }
7563
+
7564
+ /* ── 未读:标题左侧 4px 强调点 + 加粗 ── */
7565
+ .notification.is-unread .notification-title::before {
7566
+ content: '';
7567
+ display: inline-block;
7568
+ width: 6px;
7569
+ height: 6px;
7570
+ margin-right: 6px;
7571
+ margin-bottom: 1px;
7572
+ vertical-align: middle;
7573
+ border-radius: var(--radius-full);
7574
+ background: var(--token-accent);
7575
+ flex-shrink: 0;
7576
+ }
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); }
7580
+
7581
+ /* ── 可点击:整条 hover 抬升 ── */
7582
+ .notification.is-clickable {
7583
+ cursor: pointer;
7584
+ transition:
7585
+ transform var(--icen-tx-fast),
7586
+ border-color var(--icen-tx-fast),
7587
+ box-shadow var(--icen-tx-fast);
7588
+ }
7589
+ .notification.is-clickable:hover {
7590
+ transform: translateY(-1px);
7591
+ border-color: var(--token-accent);
7592
+ }
7593
+
7594
+ /* ── 图标 ── */
7595
+ .notification-icon {
7596
+ display: inline-flex;
7597
+ align-items: center;
7598
+ justify-content: center;
7599
+ flex-shrink: 0;
7600
+ width: 22px;
7601
+ height: 22px;
7602
+ margin-top: 1px;
7603
+ border-radius: var(--radius-full);
7604
+ color: var(--token-text-faint);
7605
+ }
7606
+ .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); }
7611
+
7612
+ /* 头像(圆形图) */
7613
+ .notification-avatar {
7614
+ width: 28px;
7615
+ height: 28px;
7616
+ border-radius: var(--radius-full);
7617
+ object-fit: cover;
7618
+ display: block;
7619
+ }
7620
+ .notification.has-avatar .notification-icon {
7621
+ width: 28px;
7622
+ height: 28px;
7623
+ margin-top: 0;
7624
+ }
7625
+
7626
+ /* 进度通知的图标默认走品牌色 */
7627
+ .notification.is-progress:not(.notif--success):not(.notif--warning):not(.notif--error) .notification-icon {
7628
+ color: var(--token-accent);
7629
+ }
7630
+
7631
+ /* ── 内容区 ── */
7632
+ .notification-content {
7633
+ flex: 1;
7634
+ min-width: 0;
7635
+ display: flex;
7636
+ flex-direction: column;
7637
+ gap: 2px;
7638
+ }
7639
+ .notification-title {
7640
+ margin: 0;
7641
+ font-size: var(--density-font-size-md);
7642
+ font-weight: 600;
7643
+ line-height: 1.4;
7644
+ overflow: hidden;
7645
+ text-overflow: ellipsis;
7646
+ }
7647
+ .notification.is-unread .notification-title {
7648
+ font-weight: 700;
7649
+ }
7650
+ .notification-message {
7651
+ margin: 0;
7652
+ font-size: var(--density-font-size-sm);
7653
+ color: var(--token-text-muted);
7654
+ line-height: 1.5;
7655
+ word-break: break-word;
7656
+ }
7657
+
7658
+ /* ── 多动作按钮 ── */
7659
+ .notification-actions {
7660
+ display: flex;
7661
+ flex-wrap: wrap;
7662
+ gap: var(--density-space-xs);
7663
+ margin-top: var(--density-space-sm);
7664
+ }
7665
+ .notification-action {
7666
+ display: inline-flex;
7667
+ align-items: center;
7668
+ height: 24px;
7669
+ padding: 0 var(--density-space-sm);
7670
+ border: 1px solid var(--token-outline-soft);
7671
+ border-radius: var(--radius-sm);
7672
+ background: var(--token-fill-1);
7673
+ color: var(--token-text-muted);
7674
+ font: inherit;
7675
+ font-size: var(--density-font-size-xs);
7676
+ font-weight: 500;
7677
+ cursor: pointer;
7678
+ -webkit-tap-highlight-color: transparent;
7679
+ transition: background-color var(--icen-tx-fast), color var(--icen-tx-fast), border-color var(--icen-tx-fast);
7680
+ }
7681
+ .notification-action:hover {
7682
+ background: var(--token-fill-2);
7683
+ color: var(--token-text);
7684
+ border-color: var(--token-accent);
7685
+ }
7686
+ .notification-action:focus-visible {
7687
+ outline: none;
7688
+ box-shadow: var(--icen-ring);
7689
+ }
7690
+ /* primary:实底 accent */
7691
+ .notification-action--primary {
7692
+ background: var(--token-accent);
7693
+ color: var(--token-on-accent, #fff);
7694
+ border-color: var(--token-accent);
7695
+ }
7696
+ .notification-action--primary:hover {
7697
+ background: var(--token-accent-deep, var(--token-accent));
7698
+ color: var(--token-on-accent, #fff);
7699
+ border-color: var(--token-accent-deep, var(--token-accent));
7700
+ }
7701
+ /* danger:实底 error */
7702
+ .notification-action--danger {
7703
+ background: var(--token-error);
7704
+ color: var(--token-on-accent, #fff);
7705
+ border-color: var(--token-error);
7706
+ }
7707
+ .notification-action--danger:hover {
7708
+ filter: brightness(0.92);
7709
+ color: var(--token-on-accent, #fff);
7710
+ border-color: var(--token-error);
7711
+ }
7712
+
7713
+ /* ── 详情链接 ── */
7714
+ .notification-link-wrap {
7715
+ margin-top: var(--density-space-xs);
7716
+ }
7717
+ .notification-link {
7718
+ font-size: var(--density-font-size-sm);
7719
+ color: var(--token-accent);
7720
+ text-decoration: none;
7721
+ border-bottom: 1px dashed color-mix(in srgb, var(--token-accent) 40%, transparent);
7722
+ transition: color var(--icen-tx-fast), border-color var(--icen-tx-fast);
7723
+ }
7724
+ .notification-link:hover {
7725
+ color: var(--token-accent-deep, var(--token-accent));
7726
+ border-bottom-style: solid;
7727
+ }
7728
+ .notification-link:focus-visible {
7729
+ outline: none;
7730
+ border-radius: var(--radius-xs);
7731
+ box-shadow: var(--icen-ring);
7732
+ }
7733
+
7734
+ /* ── 进度条 ── */
7735
+ .notification-progress {
7736
+ display: flex;
7737
+ align-items: center;
7738
+ gap: var(--density-space-sm);
7739
+ margin-top: var(--density-space-sm);
7740
+ }
7741
+ .notification-progress-track {
7742
+ flex: 1 1 auto;
7743
+ height: 4px;
7744
+ background: var(--token-fill-2);
7745
+ border-radius: var(--radius-full);
7746
+ overflow: hidden;
7747
+ }
7748
+ .notification-progress-bar {
7749
+ height: 100%;
7750
+ background: var(--token-accent);
7751
+ border-radius: var(--radius-full);
7752
+ transition: width var(--icen-tx-normal) var(--ease-out);
7753
+ }
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); }
7757
+ .notification-progress-label {
7758
+ flex: 0 0 auto;
7759
+ font-size: var(--density-font-size-xs);
7760
+ color: var(--token-text-muted);
7761
+ font-variant-numeric: tabular-nums;
7762
+ }
7763
+
7764
+ /* ── 关闭按钮 ── */
7765
+ .notification-close {
7766
+ display: inline-flex;
7767
+ align-items: center;
7768
+ justify-content: center;
7769
+ flex-shrink: 0;
7770
+ width: 20px;
7771
+ height: 20px;
7772
+ padding: 0;
7773
+ border: 0;
7774
+ border-radius: var(--radius-xs);
7775
+ background: transparent;
7776
+ color: var(--token-text-faint);
7777
+ cursor: pointer;
7778
+ -webkit-tap-highlight-color: transparent;
7779
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
7780
+ }
7781
+ .notification-close:hover {
7782
+ color: var(--token-text);
7783
+ background: var(--token-fill-1);
7784
+ }
7785
+ .notification-close:focus-visible {
7786
+ outline: none;
7787
+ box-shadow: var(--icen-ring);
7788
+ }
7789
+ .notification-close svg { width: 12px; height: 12px; }
7790
+
7791
+ /* ── 倒计时进度条(底部贴边) ── */
7792
+ .notification-countdown {
7793
+ position: absolute;
7794
+ left: 0;
7795
+ right: 0;
7796
+ bottom: 0;
7797
+ height: 2px;
7798
+ background: transparent;
7799
+ overflow: hidden;
7800
+ }
7801
+ .notification-countdown-fill {
7802
+ height: 100%;
7803
+ width: 100%;
7804
+ transform-origin: left;
7805
+ background: var(--token-text-faint);
7806
+ opacity: 0.35;
7807
+ animation-name: icen-notif-countdown;
7808
+ animation-timing-function: linear;
7809
+ animation-fill-mode: forwards;
7810
+ animation-play-state: running;
7811
+ }
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; }
7815
+ .notification.is-paused .notification-countdown-fill {
7816
+ animation-play-state: paused;
7817
+ }
7818
+ @keyframes icen-notif-countdown {
7819
+ from { transform: scaleX(1); }
7820
+ to { transform: scaleX(0); }
7821
+ }
7822
+
7823
+ /* ── 暂停态(hover 时) ── */
7824
+ .notification.is-paused {
7825
+ box-shadow: var(--token-shadow-overlay), 0 0 0 1px color-mix(in srgb, var(--token-accent) 14%, transparent);
7826
+ }
7827
+
7828
+ /* ── 退场动画 ── */
7829
+ .notification.leaving {
7830
+ opacity: 0;
7831
+ transform: translateX(20px);
7832
+ transition:
7833
+ opacity var(--icen-tx-normal),
7834
+ transform var(--icen-tx-normal);
7835
+ }
7836
+
7837
+ @keyframes icen-notif-in {
7838
+ from { opacity: 0; transform: translateX(20px); }
7839
+ to { opacity: 1; transform: translateX(0); }
7840
+ }
7841
+
7842
+ @media (max-width: 480px) {
7843
+ .notification-container {
7844
+ width: auto;
7845
+ left: var(--density-space-sm);
7846
+ right: var(--density-space-sm);
7847
+ }
7848
+ .notification { padding: var(--density-space-sm) var(--density-space-sm) var(--density-space-sm) 10px; }
7849
+ }
7850
+
7851
+ @media (prefers-reduced-motion: reduce) {
7852
+ .notification { animation: none; }
7853
+ .notification.leaving { transform: none; }
7854
+ .notification-countdown-fill { animation: none; transform-origin: left; }
7855
+ .notification-progress-bar { transition: none; }
7856
+ }
7857
+
5620
7858
 
5621
7859
  /* ── components/pagination.css ── */
5622
7860
  /*
@@ -5744,16 +7982,29 @@ html {
5744
7982
  * @icen.ai/ui — Component: panel(白卡片容器 + 标题栏 + 可选 footer)
5745
7983
  *
5746
7984
  * 完整契约:
5747
- * <div class="panel-block [panel-block--flush]">
7985
+ * <div class="panel-block [panel-block--flush] [panel-block--accent]
7986
+ * [panel-block--collapsible] [.is-collapsed]">
5748
7987
  * <div class="panel-title">
5749
- * <span>标题</span>
5750
- * <div class="panel-title-actions"><button class="btn btn-sm">…</button></div>
7988
+ * <div class="panel-title-text">
7989
+ * <span>标题</span>
7990
+ * <span class="panel-subtitle">副标题描述</span> ← 可选
7991
+ * </div>
7992
+ * <div class="panel-title-actions">
7993
+ * <button class="btn btn-sm">…</button>
7994
+ * <button class="panel-toggle" aria-expanded="true"> ← collapsible 模式
7995
+ * <svg class="panel-toggle-icon">…chevron…</svg>
7996
+ * </button>
7997
+ * </div>
5751
7998
  * </div>
5752
7999
  * <div class="panel-body">…</div>
5753
8000
  * <div class="panel-foot">…可选底栏</div>
5754
8001
  * </div>
5755
8002
  *
5756
8003
  * --flush:body 不留 padding,使用方自行处理内层布局。
8004
+ * --collapsible:标题栏出现展开/折叠钮(behavior 可选,也可靠纯 CSS details/summary)。
8005
+ * --accent:强调色标题边。
8006
+ * .panel-subtitle:标题下方的副标题/描述文字。
8007
+ * .panel-block.is-collapsed:折叠态——隐藏 body + foot。
5757
8008
  */
5758
8009
 
5759
8010
  .panel-block {
@@ -5764,6 +8015,8 @@ html {
5764
8015
  margin-bottom: var(--density-space-lg);
5765
8016
  overflow: hidden;
5766
8017
  }
8018
+
8019
+ /* ── 标题栏 ── */
5767
8020
  .panel-title {
5768
8021
  display: flex;
5769
8022
  align-items: center;
@@ -5777,9 +8030,24 @@ html {
5777
8030
  text-transform: uppercase;
5778
8031
  font-weight: 600;
5779
8032
  }
5780
- .panel-title > span:first-child {
5781
- flex: 1;
5782
- min-width: 0;
8033
+ .panel-title-text {
8034
+ flex: 1;
8035
+ min-width: 0;
8036
+ display: flex;
8037
+ flex-direction: column;
8038
+ gap: 1px;
8039
+ }
8040
+ .panel-title-text > span:first-child {
8041
+ overflow: hidden;
8042
+ text-overflow: ellipsis;
8043
+ white-space: nowrap;
8044
+ }
8045
+ .panel-subtitle {
8046
+ font-size: 11px;
8047
+ font-weight: 400;
8048
+ letter-spacing: 0;
8049
+ text-transform: none;
8050
+ color: var(--token-text-faint);
5783
8051
  overflow: hidden;
5784
8052
  text-overflow: ellipsis;
5785
8053
  white-space: nowrap;
@@ -5791,6 +8059,55 @@ html {
5791
8059
  flex-shrink: 0;
5792
8060
  }
5793
8061
 
8062
+ /* ── 可折叠钮 ── */
8063
+ .panel-toggle {
8064
+ display: inline-flex;
8065
+ align-items: center;
8066
+ justify-content: center;
8067
+ width: 24px;
8068
+ height: 24px;
8069
+ padding: 0;
8070
+ border: none;
8071
+ background: transparent;
8072
+ color: var(--token-text-faint);
8073
+ cursor: pointer;
8074
+ border-radius: var(--radius-xs);
8075
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
8076
+ }
8077
+ .panel-toggle:hover {
8078
+ color: var(--token-text);
8079
+ background: var(--token-fill-1);
8080
+ }
8081
+ .panel-toggle:focus-visible {
8082
+ outline: none;
8083
+ box-shadow: var(--icen-ring);
8084
+ }
8085
+ .panel-toggle-icon {
8086
+ width: 14px;
8087
+ height: 14px;
8088
+ transition: transform var(--icen-tx-fast);
8089
+ }
8090
+ .panel-block.is-collapsed .panel-toggle-icon {
8091
+ transform: rotate(-90deg);
8092
+ }
8093
+
8094
+ /* ── 折叠态:隐藏 body + foot,去掉标题栏底边 ── */
8095
+ .panel-block.is-collapsed .panel-body,
8096
+ .panel-block.is-collapsed .panel-foot {
8097
+ display: none;
8098
+ }
8099
+ .panel-block.is-collapsed .panel-title {
8100
+ border-bottom-color: transparent;
8101
+ }
8102
+ .panel-block--collapsible .panel-body {
8103
+ animation: panel-expand var(--icen-tx-normal) var(--ease-out);
8104
+ }
8105
+ @keyframes panel-expand {
8106
+ from { opacity: 0; transform: translateY(-8px); }
8107
+ to { opacity: 1; transform: translateY(0); }
8108
+ }
8109
+
8110
+ /* ── body / foot ── */
5794
8111
  .panel-body {
5795
8112
  padding: var(--density-space-md);
5796
8113
  font-size: var(--density-font-size-md);
@@ -5808,13 +8125,29 @@ html {
5808
8125
  background: color-mix(in srgb, var(--token-bg-soft) 50%, transparent);
5809
8126
  }
5810
8127
 
5811
- /* 强调变体:accent 色标题边(用于警告/告警面板) */
8128
+ /* ── 强调变体 ── */
5812
8129
  .panel-block--accent { border-color: color-mix(in srgb, var(--token-accent) 30%, transparent); }
5813
8130
  .panel-block--accent .panel-title {
5814
8131
  color: var(--token-accent);
5815
8132
  background: var(--token-accent-subtle);
5816
8133
  }
5817
8134
 
8135
+ /* ── 平面变体(无阴影,嵌套时用) ── */
8136
+ .panel-block--flat {
8137
+ box-shadow: none;
8138
+ background: var(--token-bg-soft);
8139
+ }
8140
+
8141
+ /* ── 可折叠模式:标题栏可点(纯 CSS 方案用 details/summary) ── */
8142
+ .panel-block--collapsible .panel-title {
8143
+ cursor: pointer;
8144
+ }
8145
+
8146
+ @media (prefers-reduced-motion: reduce) {
8147
+ .panel-toggle-icon { transition: none; }
8148
+ .panel-block--collapsible .panel-body { animation: none; }
8149
+ }
8150
+
5818
8151
 
5819
8152
  /* ── components/pill.css ── */
5820
8153
  /*
@@ -5906,6 +8239,76 @@ html {
5906
8239
  .pill--solid.bad { background: var(--token-error); border-color: var(--token-error); color: var(--token-on-accent, #fff); }
5907
8240
  .pill--solid.info { background: var(--token-info); border-color: var(--token-info); color: var(--token-on-accent, #fff); }
5908
8241
 
8242
+ /* ── 增强 ── */
8243
+
8244
+ /* 可关闭 pill */
8245
+ .pill-close {
8246
+ display: inline-flex;
8247
+ align-items: center;
8248
+ justify-content: center;
8249
+ width: 14px;
8250
+ height: 14px;
8251
+ margin-left: 2px;
8252
+ margin-right: -4px;
8253
+ padding: 0;
8254
+ border: 0;
8255
+ border-radius: var(--radius-full);
8256
+ background: transparent;
8257
+ color: inherit;
8258
+ opacity: 0.6;
8259
+ cursor: pointer;
8260
+ transition: opacity var(--icen-tx-fast), background-color var(--icen-tx-fast);
8261
+ }
8262
+ .pill-close:hover { opacity: 1; background: color-mix(in srgb, currentColor 15%, transparent); }
8263
+ .pill-close svg { width: 9px; height: 9px; }
8264
+
8265
+ /* 图标 pill */
8266
+ .pill-icon {
8267
+ display: inline-flex;
8268
+ align-items: center;
8269
+ }
8270
+ .pill-icon svg { width: 10px; height: 10px; }
8271
+
8272
+ /* 实时脉冲(用于"直播"/"实时"状态) */
8273
+ .pill--live::before {
8274
+ content: '';
8275
+ display: inline-block;
8276
+ width: 6px;
8277
+ height: 6px;
8278
+ border-radius: var(--radius-full);
8279
+ background: currentColor;
8280
+ margin-right: 2px;
8281
+ animation: pill-pulse 1.5s ease-in-out infinite;
8282
+ }
8283
+ @keyframes pill-pulse {
8284
+ 0%, 100% { opacity: 1; transform: scale(1); }
8285
+ 50% { opacity: 0.4; transform: scale(0.7); }
8286
+ }
8287
+
8288
+ /* 纯数字计数(不带文字) */
8289
+ .pill--count {
8290
+ min-width: 18px;
8291
+ height: 18px;
8292
+ padding: 0 5px;
8293
+ justify-content: center;
8294
+ font-size: 10px;
8295
+ font-weight: 700;
8296
+ font-variant-numeric: tabular-nums;
8297
+ line-height: 1;
8298
+ }
8299
+
8300
+ /* 渐变变体 */
8301
+ .pill--gradient {
8302
+ background: linear-gradient(135deg, var(--token-accent), var(--token-accent-deep, var(--token-accent)));
8303
+ color: var(--token-on-accent, #fff);
8304
+ border-color: transparent;
8305
+ }
8306
+
8307
+ @media (prefers-reduced-motion: reduce) {
8308
+ .pill--live::before { animation: none; }
8309
+ .pill-close { transition: none; }
8310
+ }
8311
+
5909
8312
 
5910
8313
  /* ── components/popover.css ── */
5911
8314
  /*
@@ -6037,6 +8440,121 @@ html {
6037
8440
  }
6038
8441
 
6039
8442
 
8443
+ /* ── components/scroll-area.css ── */
8444
+ /*
8445
+ * @icen.ai/ui — Component: scroll-area(自定义滚动容器)
8446
+ *
8447
+ * 全局 base.css 已经提供细薄滚动条(所有元素默认生效)。
8448
+ * 本组件提供"显式带视觉边界的滚动区"——常用于:
8449
+ * - 侧边栏长列表
8450
+ * - 下拉面板内的滚动区
8451
+ * - 需要限定高度并强调"这里可以滚"的容器
8452
+ *
8453
+ * 完整契约:
8454
+ * <div class="scroll-area [--sm|--lg] [--both]">
8455
+ * 任意超长内容…
8456
+ * </div>
8457
+ *
8458
+ * 横向专用:<div class="scroll-area scroll-area--x">…</div>
8459
+ * 垂直专用:<div class="scroll-area scroll-area--y">…</div>
8460
+ *
8461
+ * 变体:
8462
+ * --x 仅横向滚动(纵向裁剪)
8463
+ * --y 仅纵向滚动(横向裁剪)
8464
+ * --both 双向滚动
8465
+ * --sm 窄滚动条(6px 宽)
8466
+ * --lg 正常滚动条(10px 宽,默认 8px)
8467
+ * --inset 内嵌阴影式(类似 code 编辑器)
8468
+ */
8469
+
8470
+ .scroll-area {
8471
+ overflow: auto;
8472
+ overscroll-behavior: contain;
8473
+ -webkit-overflow-scrolling: touch;
8474
+ scroll-behavior: smooth;
8475
+ }
8476
+
8477
+ .scroll-area--x { overflow-x: auto; overflow-y: hidden; }
8478
+ .scroll-area--y { overflow-x: hidden; overflow-y: auto; }
8479
+ .scroll-area--both { overflow: auto; }
8480
+
8481
+ /* ── 尺寸变体:覆盖全局滚动条宽度 ── */
8482
+ .scroll-area--sm {
8483
+ scrollbar-width: thin;
8484
+ }
8485
+ .scroll-area--sm::-webkit-scrollbar {
8486
+ width: 6px;
8487
+ height: 6px;
8488
+ }
8489
+ .scroll-area--sm::-webkit-scrollbar-thumb {
8490
+ background: color-mix(in srgb, var(--token-text-faint) 28%, transparent);
8491
+ border-radius: 3px;
8492
+ border: 0;
8493
+ }
8494
+
8495
+ .scroll-area--lg {
8496
+ scrollbar-width: auto;
8497
+ }
8498
+ .scroll-area--lg::-webkit-scrollbar {
8499
+ width: 12px;
8500
+ height: 12px;
8501
+ }
8502
+ .scroll-area--lg::-webkit-scrollbar-thumb {
8503
+ background: color-mix(in srgb, var(--token-text-faint) 35%, transparent);
8504
+ border-radius: 6px;
8505
+ border: 2px solid transparent;
8506
+ background-clip: padding-box;
8507
+ }
8508
+ .scroll-area--lg::-webkit-scrollbar-thumb:hover {
8509
+ background: color-mix(in srgb, var(--token-text-faint) 50%, transparent);
8510
+ background-clip: padding-box;
8511
+ }
8512
+
8513
+ /* ── 内嵌变体:带视觉边界 ── */
8514
+ .scroll-area--inset {
8515
+ background: var(--token-bg-soft);
8516
+ border: 1px solid var(--token-line-soft);
8517
+ border-radius: var(--radius-md);
8518
+ box-shadow: var(--token-shadow-inset);
8519
+ padding: var(--density-space-sm);
8520
+ }
8521
+ .scroll-area--inset:focus-visible {
8522
+ outline: none;
8523
+ border-color: var(--icen-control-focus-border);
8524
+ box-shadow: var(--icen-control-focus-shadow);
8525
+ }
8526
+
8527
+ /* ── 渐隐遮罩(可选):上下边缘渐隐,暗示可滚动 ── */
8528
+ .scroll-area--fade {
8529
+ -webkit-mask-image: linear-gradient(
8530
+ to bottom,
8531
+ transparent 0,
8532
+ transparent 0,
8533
+ #000 var(--density-space-sm),
8534
+ #000 calc(100% - var(--density-space-sm)),
8535
+ transparent 100%
8536
+ );
8537
+ mask-image: linear-gradient(
8538
+ to bottom,
8539
+ transparent 0,
8540
+ #000 var(--density-space-sm),
8541
+ #000 calc(100% - var(--density-space-sm)),
8542
+ transparent 100%
8543
+ );
8544
+ }
8545
+
8546
+ /* ── 可被键盘聚焦的滚动容器 ── */
8547
+ .scroll-area[tabindex='0']:focus-visible {
8548
+ outline: none;
8549
+ box-shadow: var(--icen-ring-soft);
8550
+ border-radius: var(--radius-md);
8551
+ }
8552
+
8553
+ @media (prefers-reduced-motion: reduce) {
8554
+ .scroll-area { scroll-behavior: auto; }
8555
+ }
8556
+
8557
+
6040
8558
  /* ── components/segmented.css ── */
6041
8559
  /*
6042
8560
  * @icen.ai/ui — Component: segmented(分段控件 / 切换组 / 数字步进器 / OTP)
@@ -6300,6 +8818,81 @@ html {
6300
8818
  .segmented-item, .toggle-item, .stepper-btn, .stepper, .otp-cell { transition: none; }
6301
8819
  }
6302
8820
 
8821
+ /* ── 增强 ── */
8822
+
8823
+ /* 全宽分段(每项等宽) */
8824
+ .segmented--block,
8825
+ .toggle-group--block {
8826
+ display: flex;
8827
+ width: 100%;
8828
+ }
8829
+ .segmented--block .segmented-item,
8830
+ .toggle-group--block .toggle-item {
8831
+ flex: 1;
8832
+ justify-content: center;
8833
+ }
8834
+
8835
+ /* 分段底色变体(幽灵底,用于次要分组) */
8836
+ .segmented--ghost,
8837
+ .toggle-group--ghost {
8838
+ background: transparent;
8839
+ border-color: transparent;
8840
+ box-shadow: none;
8841
+ }
8842
+ .segmented--ghost .segmented-item.is-active,
8843
+ .toggle-group--ghost .toggle-item.is-active {
8844
+ background: var(--token-fill-2);
8845
+ color: var(--token-text);
8846
+ box-shadow: none;
8847
+ }
8848
+
8849
+ /* 分段错误态 */
8850
+ .segmented.is-error,
8851
+ .toggle-group.is-error {
8852
+ border-color: var(--token-error);
8853
+ }
8854
+
8855
+ /* 分段禁用整体 */
8856
+ .segmented.is-disabled,
8857
+ .toggle-group.is-disabled {
8858
+ opacity: var(--icen-disabled-opacity);
8859
+ pointer-events: none;
8860
+ }
8861
+
8862
+ /* 步进器带单位后缀 */
8863
+ .stepper-unit {
8864
+ display: inline-flex;
8865
+ align-items: center;
8866
+ padding-right: var(--density-space-sm);
8867
+ color: var(--token-text-faint);
8868
+ font-size: 0.85em;
8869
+ }
8870
+
8871
+ /* OTP 分组分隔符(如 4-4 分两组时中间加横线) */
8872
+ .otp-separator {
8873
+ display: inline-flex;
8874
+ align-items: center;
8875
+ padding: 0 var(--density-space-xs);
8876
+ color: var(--token-text-faint);
8877
+ font-size: var(--density-font-size-lg);
8878
+ font-weight: 300;
8879
+ }
8880
+
8881
+ /* OTP 错误态 */
8882
+ .otp.is-error .otp-cell { border-color: var(--token-error); }
8883
+ .otp.is-error .otp-cell.is-filled {
8884
+ background: var(--token-error-muted);
8885
+ border-color: var(--token-error);
8886
+ }
8887
+
8888
+ /* OTP 完成(全部填满)态 */
8889
+ .otp.is-complete .otp-cell {
8890
+ border-color: var(--token-success);
8891
+ }
8892
+ .otp.is-complete .otp-cell.is-filled {
8893
+ background: color-mix(in srgb, var(--token-success) 8%, transparent);
8894
+ }
8895
+
6303
8896
 
6304
8897
  /* ── components/select.css ── */
6305
8898
  /*
@@ -6307,15 +8900,24 @@ html {
6307
8900
  * 规格源自 opengal primitives/input.tsx 的 Select;与 behaviors/select.ts 配套。
6308
8901
  *
6309
8902
  * 完整契约:
6310
- * <div class="select [data-disabled] [.is-open]" data-select>
8903
+ * <div class="select [data-disabled] [.is-open]" data-select
8904
+ * [data-select-search] [data-select-multiple] [data-select-max="3"]>
6311
8905
  * <button class="select-trigger pressable focus-ring [--sm|--lg]" aria-haspopup="listbox"
6312
8906
  * aria-expanded="false" [disabled]>
6313
8907
  * <span class="select-leading">…</span> 可选前缀
6314
8908
  * <span class="select-value is-empty">占位</span>
8909
+ * <button class="select-clear" aria-label="清除">×</button> ← 多选时可选
6315
8910
  * <svg class="select-chevron">…</svg>
6316
8911
  * </button>
6317
8912
  * <div class="select-panel" hidden role="listbox">
6318
- * <button class="select-option [.is-focused|.is-selected|.is-disabled]" data-value="x">…</button>
8913
+ * <div class="select-search"> ← data-select-search 时 behavior 自动注入
8914
+ * <input class="select-search-input" placeholder="搜索…" />
8915
+ * </div>
8916
+ * <div class="select-group"> ← 可选分组
8917
+ * <p class="select-group-label">组标题</p>
8918
+ * <button class="select-option [.is-focused|.is-selected|.is-disabled]" data-value="x">…</button>
8919
+ * </div>
8920
+ * <div class="select-empty">无匹配项</div> ← 搜索无结果时 behavior 显示
6319
8921
  * </div>
6320
8922
  * <input type="hidden" data-select-value /> 可选,behavior 同步 value
6321
8923
  * </div>
@@ -6323,6 +8925,8 @@ html {
6323
8925
  * behavior 维护:
6324
8926
  * - .select.is-open ↔ aria-expanded;panel [hidden] 切换;ESC/外部点击关闭
6325
8927
  * - 选项 .is-focused 跟随键盘箭头;Enter 选中 → 写回 .select-value / .is-selected
8928
+ * - data-select-search:面板顶部自动插入搜索框,实时过滤(含 data-keywords),空分组自动隐藏
8929
+ * - data-select-multiple:点击切选不关面板,trigger 显示计数,hidden input 同步逗号分隔值
6326
8930
  */
6327
8931
 
6328
8932
  .select {
@@ -6462,25 +9066,109 @@ html {
6462
9066
  cursor: not-allowed;
6463
9067
  }
6464
9068
 
6465
- /* 分组(行为可选渲染 optgroup) */
6466
- .select-group {
6467
- padding: 0 4px;
6468
- margin-top: 4px;
9069
+ /* 分组(行为可选渲染 optgroup) */
9070
+ .select-group {
9071
+ padding: 0 4px;
9072
+ margin-top: 4px;
9073
+ }
9074
+ .select-group-label {
9075
+ padding: 6px var(--density-space-sm) 2px;
9076
+ font-size: var(--density-font-size-xs);
9077
+ color: var(--token-text-faint);
9078
+ text-transform: uppercase;
9079
+ letter-spacing: 0.05em;
9080
+ }
9081
+
9082
+ /* 空状态(behavior 渲染 no-match) */
9083
+ .select-empty {
9084
+ padding: var(--density-space-md);
9085
+ font-size: var(--density-font-size-sm);
9086
+ color: var(--token-text-faint);
9087
+ text-align: center;
9088
+ }
9089
+
9090
+ /* ── 搜索框(data-select-search 时 behavior 自动注入) ── */
9091
+ .select-search {
9092
+ padding: 4px 4px 6px;
9093
+ border-bottom: 1px solid var(--token-outline-soft);
9094
+ margin-bottom: 4px;
9095
+ position: sticky;
9096
+ top: 0;
9097
+ background: var(--token-card-strong);
9098
+ z-index: 1;
9099
+ }
9100
+ .select-search-input {
9101
+ width: 100%;
9102
+ height: 30px;
9103
+ padding: 0 var(--density-space-sm);
9104
+ font-size: var(--density-font-size-sm);
9105
+ color: var(--token-text);
9106
+ background: var(--token-control);
9107
+ border: 1px solid var(--token-outline-soft);
9108
+ border-radius: var(--radius-sm);
9109
+ transition: border-color var(--icen-tx-fast), box-shadow var(--icen-tx-fast);
9110
+ }
9111
+ .select-search-input:focus {
9112
+ outline: none;
9113
+ border-color: var(--icen-control-focus-border);
9114
+ box-shadow: var(--icen-control-focus-shadow);
9115
+ }
9116
+
9117
+ /* ── 多选清除按钮 ── */
9118
+ .select-clear {
9119
+ display: inline-flex;
9120
+ align-items: center;
9121
+ justify-content: center;
9122
+ flex-shrink: 0;
9123
+ width: 18px;
9124
+ height: 18px;
9125
+ padding: 0;
9126
+ border: 0;
9127
+ border-radius: var(--radius-xs);
9128
+ background: transparent;
9129
+ color: var(--token-text-faint);
9130
+ font-size: 15px;
9131
+ line-height: 1;
9132
+ cursor: pointer;
9133
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
9134
+ }
9135
+ .select-clear:hover {
9136
+ color: var(--token-text);
9137
+ background: var(--token-fill-1);
9138
+ }
9139
+
9140
+ /* ── 多选 trigger 高度自适应(chips 可能换行) ── */
9141
+ .select[data-select-multiple] .select-trigger {
9142
+ min-height: var(--density-input-height-md);
9143
+ height: auto;
9144
+ padding-top: 4px;
9145
+ padding-bottom: 4px;
9146
+ flex-wrap: wrap;
9147
+ }
9148
+
9149
+ /* 多选项 checkbox 指示器(选项左侧) */
9150
+ .select[data-select-multiple] .select-option::before {
9151
+ content: '';
9152
+ width: 14px;
9153
+ height: 14px;
9154
+ flex-shrink: 0;
9155
+ border: 1.5px solid var(--token-outline);
9156
+ border-radius: var(--radius-xs);
9157
+ transition: background-color var(--icen-tx-fast), border-color var(--icen-tx-fast);
6469
9158
  }
6470
- .select-group-label {
6471
- padding: 6px var(--density-space-sm) 2px;
6472
- font-size: var(--density-font-size-xs);
6473
- color: var(--token-text-faint);
6474
- text-transform: uppercase;
6475
- letter-spacing: 0.05em;
9159
+ .select[data-select-multiple] .select-option.is-selected::before {
9160
+ background: var(--token-accent);
9161
+ border-color: var(--token-accent);
9162
+ }
9163
+ .select[data-select-multiple] .select-option.is-selected::after {
9164
+ content: '✓';
9165
+ margin-left: auto;
6476
9166
  }
6477
9167
 
6478
- /* 空状态(behavior 渲染 no-match) */
6479
- .select-empty {
6480
- padding: var(--density-space-md);
6481
- font-size: var(--density-font-size-sm);
6482
- color: var(--token-text-faint);
6483
- text-align: center;
9168
+ @media (prefers-reduced-motion: reduce) {
9169
+ .select-panel { animation: none; }
9170
+ .select-search-input,
9171
+ .select-clear { transition: none; }
6484
9172
  }
6485
9173
 
6486
9174
 
@@ -6668,6 +9356,124 @@ html {
6668
9356
  .sidebar-group-title::after { transition: none; }
6669
9357
  }
6670
9358
 
9359
+ /* ── 增强 ── */
9360
+
9361
+ /* 侧栏头部(品牌/标题) */
9362
+ .sidebar-header {
9363
+ display: flex;
9364
+ align-items: center;
9365
+ gap: var(--density-space-sm);
9366
+ padding: var(--density-space-sm);
9367
+ border-bottom: 1px solid var(--token-outline-soft);
9368
+ margin-bottom: var(--density-space-sm);
9369
+ }
9370
+ .sidebar-header-brand {
9371
+ display: inline-flex;
9372
+ align-items: center;
9373
+ gap: var(--density-space-xs);
9374
+ font-family: var(--font-heading);
9375
+ font-size: var(--density-font-size-md);
9376
+ font-weight: 600;
9377
+ color: var(--token-text);
9378
+ text-decoration: none;
9379
+ }
9380
+ .sidebar-header-brand > svg,
9381
+ .sidebar-header-brand > img {
9382
+ width: 24px;
9383
+ height: 24px;
9384
+ }
9385
+ .sidebar-header-toggle {
9386
+ margin-left: auto;
9387
+ display: inline-flex;
9388
+ align-items: center;
9389
+ justify-content: center;
9390
+ width: 28px;
9391
+ height: 28px;
9392
+ border: 0;
9393
+ border-radius: var(--radius-sm);
9394
+ background: transparent;
9395
+ color: var(--token-text-faint);
9396
+ cursor: pointer;
9397
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
9398
+ }
9399
+ .sidebar-header-toggle:hover {
9400
+ color: var(--token-text);
9401
+ background: var(--token-fill-1);
9402
+ }
9403
+
9404
+ /* 侧栏底部 */
9405
+ .sidebar-footer {
9406
+ padding: var(--density-space-sm);
9407
+ border-top: 1px solid var(--token-outline-soft);
9408
+ margin-top: auto;
9409
+ }
9410
+
9411
+ /* 激活态左侧指示条 */
9412
+ .sidebar-item.is-active,
9413
+ .sidebar-item[aria-current='page'] {
9414
+ position: relative;
9415
+ }
9416
+ .sidebar-item.is-active::before,
9417
+ .sidebar-item[aria-current='page']::before {
9418
+ content: '';
9419
+ position: absolute;
9420
+ left: 0;
9421
+ top: 50%;
9422
+ transform: translateY(-50%);
9423
+ width: 3px;
9424
+ height: 60%;
9425
+ border-radius: var(--radius-full);
9426
+ background: var(--token-accent);
9427
+ }
9428
+
9429
+ /* 折叠/轨道模式(icon-only mini sidebar) */
9430
+ .sidebar--collapsed {
9431
+ width: 56px;
9432
+ }
9433
+ .sidebar--collapsed .sidebar-item-label,
9434
+ .sidebar--collapsed .sidebar-item-badge,
9435
+ .sidebar--collapsed .sidebar-section-label,
9436
+ .sidebar--collapsed .sidebar-group-title-text,
9437
+ .sidebar--collapsed .sidebar-group-title::after {
9438
+ display: none;
9439
+ }
9440
+ .sidebar--collapsed .sidebar-group-title {
9441
+ justify-content: center;
9442
+ padding: var(--density-space-sm);
9443
+ }
9444
+ .sidebar--collapsed .sidebar-item {
9445
+ justify-content: center;
9446
+ padding: var(--density-space-sm);
9447
+ }
9448
+ .sidebar--collapsed .sidebar-item-icon {
9449
+ width: 18px;
9450
+ height: 18px;
9451
+ }
9452
+
9453
+ /* 侧栏内搜索 */
9454
+ .sidebar-search {
9455
+ margin-bottom: var(--density-space-sm);
9456
+ }
9457
+
9458
+ /* 分组分隔线 */
9459
+ .sidebar-divider {
9460
+ height: 1px;
9461
+ background: var(--token-outline-soft);
9462
+ margin: var(--density-space-sm) 0;
9463
+ }
9464
+
9465
+ /* 徽章变体色 */
9466
+ .sidebar-item-badge--success { background: var(--token-success); }
9467
+ .sidebar-item-badge--warning { background: var(--token-warning); }
9468
+ .sidebar-item-badge--error { background: var(--token-error); }
9469
+ .sidebar-item-badge--dot {
9470
+ width: 8px;
9471
+ min-width: 8px;
9472
+ height: 8px;
9473
+ padding: 0;
9474
+ font-size: 0;
9475
+ }
9476
+
6671
9477
 
6672
9478
  /* ── components/slider.css ── */
6673
9479
  /*
@@ -6846,36 +9652,264 @@ html {
6846
9652
  font-variant-numeric: tabular-nums;
6847
9653
  color: var(--token-text);
6848
9654
  }
6849
-
6850
- .slider-row {
6851
- display: flex;
6852
- align-items: center;
6853
- justify-content: space-between;
6854
- gap: var(--density-space-sm);
6855
- font-size: var(--density-font-size-sm);
6856
- color: var(--token-text-muted);
9655
+
9656
+ .slider-row {
9657
+ display: flex;
9658
+ align-items: center;
9659
+ justify-content: space-between;
9660
+ gap: var(--density-space-sm);
9661
+ font-size: var(--density-font-size-sm);
9662
+ color: var(--token-text-muted);
9663
+ }
9664
+ .slider-label { color: var(--token-text-muted); }
9665
+
9666
+ /* 刻度(behavior 可选渲染) */
9667
+ .slider-ticks {
9668
+ position: absolute;
9669
+ left: 0; right: 0;
9670
+ top: 50%;
9671
+ height: var(--slider-track-height);
9672
+ transform: translateY(-50%);
9673
+ display: flex;
9674
+ justify-content: space-between;
9675
+ pointer-events: none;
9676
+ }
9677
+ .slider-tick {
9678
+ width: 1px;
9679
+ height: 100%;
9680
+ background: color-mix(in srgb, var(--token-text) 12%, transparent);
9681
+ }
9682
+
9683
+ /* ── 带标签的刻度(marks)── */
9684
+ .slider-marks {
9685
+ position: relative;
9686
+ height: 20px;
9687
+ margin-top: 2px;
9688
+ font-size: var(--density-font-size-xs);
9689
+ color: var(--token-text-faint);
9690
+ font-variant-numeric: tabular-nums;
9691
+ }
9692
+ .slider-mark {
9693
+ position: absolute;
9694
+ transform: translateX(-50%);
9695
+ white-space: nowrap;
9696
+ cursor: pointer;
9697
+ transition: color var(--icen-tx-fast);
9698
+ }
9699
+ .slider-mark:hover {
9700
+ color: var(--token-accent);
9701
+ }
9702
+ .slider-mark.is-active {
9703
+ color: var(--token-accent);
9704
+ font-weight: 500;
9705
+ }
9706
+ /* 刻度上的大点(配合 marks 使用) */
9707
+ .slider-mark::before {
9708
+ content: '';
9709
+ position: absolute;
9710
+ left: 50%;
9711
+ top: -8px;
9712
+ width: 3px;
9713
+ height: 3px;
9714
+ border-radius: var(--radius-full);
9715
+ background: var(--token-text-faint);
9716
+ transform: translateX(-50%);
9717
+ transition: background-color var(--icen-tx-fast);
9718
+ }
9719
+ .slider-mark:hover::before,
9720
+ .slider-mark.is-active::before {
9721
+ background: var(--token-accent);
9722
+ }
9723
+
9724
+ /* ── 值提示气泡(拖拽时出现)── */
9725
+ .slider-tooltip {
9726
+ position: absolute;
9727
+ bottom: calc(50% + var(--slider-thumb-size));
9728
+ transform: translateX(-50%);
9729
+ padding: 2px var(--density-space-xs);
9730
+ background: var(--token-text);
9731
+ color: var(--token-card-strong);
9732
+ font-size: var(--density-font-size-xs);
9733
+ font-variant-numeric: tabular-nums;
9734
+ border-radius: var(--radius-xs);
9735
+ white-space: nowrap;
9736
+ pointer-events: none;
9737
+ opacity: 0;
9738
+ transition: opacity var(--icen-tx-fast);
9739
+ z-index: 3;
9740
+ }
9741
+ .slider-tooltip::after {
9742
+ content: '';
9743
+ position: absolute;
9744
+ top: 100%;
9745
+ left: 50%;
9746
+ transform: translateX(-50%);
9747
+ border: 4px solid transparent;
9748
+ border-top-color: var(--token-text);
9749
+ }
9750
+ .slider:active .slider-tooltip,
9751
+ .slider:focus-within .slider-tooltip {
9752
+ opacity: 1;
9753
+ }
9754
+ .slider--vertical .slider-tooltip {
9755
+ bottom: auto;
9756
+ left: calc(50% + var(--slider-thumb-size));
9757
+ top: 50%;
9758
+ transform: translate(0, -50%);
9759
+ }
9760
+ .slider--vertical .slider-tooltip::after {
9761
+ top: 50%;
9762
+ left: auto;
9763
+ right: 100%;
9764
+ transform: translateY(-50%);
9765
+ border-top-color: transparent;
9766
+ border-right-color: var(--token-text);
9767
+ }
9768
+
9769
+ /* ── 滑块拖拽态(active 时放大) ── */
9770
+ .slider:active .slider-thumb {
9771
+ transform: translate(-50%, -50%) scale(1.25);
9772
+ }
9773
+ .slider--vertical:active .slider-thumb {
9774
+ transform: translate(-50%, 50%) scale(1.25);
9775
+ }
9776
+
9777
+ /* ── 填充段带渐变(高级视觉) ── */
9778
+ .slider-fill--gradient,
9779
+ .slider-range--gradient {
9780
+ background: linear-gradient(90deg,
9781
+ color-mix(in srgb, var(--token-accent) 60%, transparent),
9782
+ var(--token-accent));
9783
+ }
9784
+
9785
+ /* ── 禁用态填充色变灰 ── */
9786
+ .slider.is-disabled .slider-fill,
9787
+ .slider.is-disabled .slider-range {
9788
+ background: var(--token-fill-2);
9789
+ }
9790
+ .slider.is-disabled .slider-thumb {
9791
+ border-color: var(--token-text-faint);
9792
+ }
9793
+
9794
+ @media (prefers-reduced-motion: reduce) {
9795
+ .slider-fill, .slider-range, .slider-thumb { transition: none; }
9796
+ .slider-tooltip { transition: none; }
9797
+ .slider-mark, .slider-mark::before { transition: none; }
9798
+ }
9799
+
9800
+
9801
+ /* ── components/split-pane.css ── */
9802
+ /*
9803
+ * @icen.ai/ui — Component: split-pane(分栏拖拽容器,与 behaviors/split-pane 配套)
9804
+ *
9805
+ * 契约:
9806
+ * <div class="split-pane [--vertical]" data-split-pane style="--split: 50%">
9807
+ * <div class="split-pane-first">左侧(或上侧)内容</div>
9808
+ * <div class="split-pane-divider" role="separator" aria-orientation="vertical" aria-valuenow="50"></div>
9809
+ * <div class="split-pane-second">右侧(或下侧)内容</div>
9810
+ * </div>
9811
+ *
9812
+ * behavior 接管 .split-pane-divider 的 pointer 拖拽,按比例更新 --split 变量
9813
+ * (范围 10% – 90%);双击分隔条复位到 50%。
9814
+ *
9815
+ * 常用于:编辑器左右分栏 / 后台主从布局 / 邮件列表+详情。
9816
+ */
9817
+
9818
+ .split-pane {
9819
+ display: flex;
9820
+ align-items: stretch;
9821
+ width: 100%;
9822
+ height: 100%;
9823
+ min-height: 200px;
9824
+ overflow: hidden;
9825
+ background: var(--token-surface);
9826
+ border: 1px solid var(--token-line-soft);
9827
+ border-radius: var(--radius-md);
9828
+ }
9829
+
9830
+ .split-pane-first {
9831
+ flex: 0 0 var(--split, 50%);
9832
+ min-width: 0;
9833
+ overflow: auto;
9834
+ overscroll-behavior: contain;
9835
+ }
9836
+ .split-pane-second {
9837
+ flex: 1 1 auto;
9838
+ min-width: 0;
9839
+ overflow: auto;
9840
+ overscroll-behavior: contain;
9841
+ }
9842
+
9843
+ /* ── 分隔条(默认纵向 col-resize) ── */
9844
+ .split-pane-divider {
9845
+ flex: 0 0 6px;
9846
+ position: relative;
9847
+ background: var(--token-line-soft);
9848
+ cursor: col-resize;
9849
+ user-select: none;
9850
+ touch-action: none;
9851
+ transition: background var(--icen-tx-fast);
9852
+ }
9853
+ .split-pane-divider::after {
9854
+ content: '';
9855
+ position: absolute;
9856
+ top: 50%;
9857
+ left: 50%;
9858
+ width: 2px;
9859
+ height: 28px;
9860
+ border-radius: 1px;
9861
+ background: var(--token-text-faint);
9862
+ opacity: 0.35;
9863
+ transform: translate(-50%, -50%);
9864
+ transition: opacity var(--icen-tx-fast), background-color var(--icen-tx-fast);
9865
+ }
9866
+ .split-pane-divider:hover::after,
9867
+ .split-pane-divider.is-dragging::after {
9868
+ opacity: 1;
9869
+ background: var(--token-accent);
9870
+ }
9871
+ .split-pane-divider:hover,
9872
+ .split-pane-divider.is-dragging {
9873
+ background: color-mix(in srgb, var(--token-accent) 14%, var(--token-line-soft));
9874
+ }
9875
+ .split-pane-divider:focus-visible {
9876
+ outline: none;
9877
+ background: color-mix(in srgb, var(--token-accent) 18%, var(--token-line-soft));
6857
9878
  }
6858
- .slider-label { color: var(--token-text-muted); }
6859
9879
 
6860
- /* 刻度(behavior 可选渲染) */
6861
- .slider-ticks {
6862
- position: absolute;
6863
- left: 0; right: 0;
6864
- top: 50%;
6865
- height: var(--slider-track-height);
6866
- transform: translateY(-50%);
6867
- display: flex;
6868
- justify-content: space-between;
6869
- pointer-events: none;
9880
+ /* ── 纵向布局(上下分栏) ── */
9881
+ .split-pane--vertical {
9882
+ flex-direction: column;
6870
9883
  }
6871
- .slider-tick {
6872
- width: 1px;
6873
- height: 100%;
6874
- background: color-mix(in srgb, var(--token-text) 12%, transparent);
9884
+ .split-pane--vertical .split-pane-first {
9885
+ flex: 0 0 var(--split, 50%);
9886
+ flex-basis: var(--split, 50%);
9887
+ min-height: 0;
9888
+ }
9889
+ .split-pane--vertical .split-pane-second {
9890
+ flex-basis: auto;
9891
+ min-height: 0;
9892
+ }
9893
+ .split-pane--vertical .split-pane-divider {
9894
+ cursor: row-resize;
9895
+ flex-basis: 6px;
9896
+ height: 6px;
9897
+ width: auto;
9898
+ }
9899
+ .split-pane--vertical .split-pane-divider::after {
9900
+ width: 28px;
9901
+ height: 2px;
9902
+ }
9903
+
9904
+ /* ── 嵌套分栏不应再有外边框 ── */
9905
+ .split-pane .split-pane {
9906
+ border: 0;
9907
+ border-radius: 0;
6875
9908
  }
6876
9909
 
6877
9910
  @media (prefers-reduced-motion: reduce) {
6878
- .slider-fill, .slider-range, .slider-thumb { transition: none; }
9911
+ .split-pane-divider { transition: none; }
9912
+ .split-pane-divider::after { transition: none; }
6879
9913
  }
6880
9914
 
6881
9915
 
@@ -6885,18 +9919,31 @@ html {
6885
9919
  *
6886
9920
  * 完整契约:
6887
9921
  * <div class="stat-grid [stat-grid--2|stat-grid--3|stat-grid--4]">
6888
- * <div class="stat-card [.stat-card--interactive]">
9922
+ * <div class="stat-card [.stat-card--interactive] [.stat-card--horizontal]">
6889
9923
  * <div class="stat-head">
6890
9924
  * <span class="stat-label">用户总数</span>
6891
- * <span class="stat-icon">…svg…</span> ← 可选
9925
+ * <span class="stat-icon [.icon--ok|.icon--warn|.icon--bad|.icon--info]>…svg…</span>
9926
+ * </div>
9927
+ * <div class="stat-body">
9928
+ * <div class="stat-num [.accent|.warn|.bad|.info]">0</div>
9929
+ * <div class="stat-delta [.up|.down|.flat]"> ← 可选趋势
9930
+ * <svg>…arrow…</svg> +12.4%
9931
+ * <span class="stat-period">比上周</span>
9932
+ * </div>
6892
9933
  * </div>
6893
- * <div class="stat-num [.accent|.warn|.bad|.info]">0</div>
6894
- * <div class="stat-delta [.up|.down|.flat]"> ← 可选趋势
6895
- * <svg>…arrow…</svg> +12.4%
6896
- * <span class="stat-period">比上周</span>
9934
+ * <div class="stat-spark">…svg mini chart…</div> ← 可选:迷你折线/柱状
9935
+ * <div class="stat-progress"> ← 可选:进度环
9936
+ * <svg class="stat-ring" viewBox="0 0 36 36">
9937
+ * <circle class="stat-ring-track" cx="18" cy="18" r="15.5" />
9938
+ * <circle class="stat-ring-fill" cx="18" cy="18" r="15.5" style="stroke-dasharray:75,100" />
9939
+ * </svg>
6897
9940
  * </div>
6898
9941
  * </div>
6899
9942
  * </div>
9943
+ *
9944
+ * .stat-card--horizontal:横向布局(数字在右,标签在左),适合 dashboard 顶栏。
9945
+ * .stat-spark:内嵌迷你折线/柱状图(消费方可引用 chart-sparkline.css 或自绘 SVG)。
9946
+ * .stat-ring:进度环(SVG stroke-dasharray 控制百分比,0-100)。
6900
9947
  */
6901
9948
 
6902
9949
  .stat-grid {
@@ -6954,8 +10001,19 @@ html {
6954
10001
  background: var(--token-accent-subtle);
6955
10002
  color: var(--token-accent);
6956
10003
  }
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); }
6957
10008
  .stat-icon svg { width: 14px; height: 14px; }
6958
10009
 
10010
+ /* ── 内容体(数字 + 趋势) ── */
10011
+ .stat-body {
10012
+ display: flex;
10013
+ flex-direction: column;
10014
+ gap: 2px;
10015
+ }
10016
+
6959
10017
  .stat-num {
6960
10018
  font-size: 24px;
6961
10019
  font-weight: 700;
@@ -6994,6 +10052,105 @@ html {
6994
10052
  .stat-card--sm .stat-num { font-size: var(--density-font-size-lg); }
6995
10053
  .stat-card--sm .stat-label { font-size: 10px; }
6996
10054
 
10055
+ /* ── 横向布局(dashboard 顶栏:标签左 + 数字右) ── */
10056
+ .stat-card--horizontal {
10057
+ flex-direction: row;
10058
+ align-items: center;
10059
+ justify-content: space-between;
10060
+ gap: var(--density-space-md);
10061
+ }
10062
+ .stat-card--horizontal .stat-head {
10063
+ flex-direction: column;
10064
+ align-items: flex-start;
10065
+ gap: 2px;
10066
+ flex: 1;
10067
+ min-width: 0;
10068
+ }
10069
+ .stat-card--horizontal .stat-body {
10070
+ text-align: right;
10071
+ flex-shrink: 0;
10072
+ }
10073
+ .stat-card--horizontal .stat-delta {
10074
+ justify-content: flex-end;
10075
+ }
10076
+
10077
+ /* ── 迷你图槽(stat-spark)── */
10078
+ .stat-spark {
10079
+ margin-top: var(--density-space-xs);
10080
+ height: 28px;
10081
+ width: 100%;
10082
+ }
10083
+ .stat-spark svg {
10084
+ width: 100%;
10085
+ height: 100%;
10086
+ display: block;
10087
+ }
10088
+ .stat-card--horizontal .stat-spark {
10089
+ margin-top: 0;
10090
+ width: 80px;
10091
+ flex-shrink: 0;
10092
+ }
10093
+
10094
+ /* ── 进度环 ── */
10095
+ .stat-progress {
10096
+ display: flex;
10097
+ align-items: center;
10098
+ justify-content: center;
10099
+ margin-top: var(--density-space-xs);
10100
+ }
10101
+ .stat-card--horizontal .stat-progress {
10102
+ margin-top: 0;
10103
+ flex-shrink: 0;
10104
+ }
10105
+ .stat-ring {
10106
+ width: 48px;
10107
+ height: 48px;
10108
+ transform: rotate(-90deg);
10109
+ }
10110
+ .stat-ring-track {
10111
+ fill: none;
10112
+ stroke: var(--token-fill-2);
10113
+ stroke-width: 3;
10114
+ }
10115
+ .stat-ring-fill {
10116
+ fill: none;
10117
+ stroke: var(--token-accent);
10118
+ stroke-width: 3;
10119
+ stroke-linecap: round;
10120
+ stroke-dasharray: 0, 100;
10121
+ transition: stroke-dasharray var(--icen-tx-slow) var(--ease-out);
10122
+ }
10123
+ .stat-card .stat-ring-fill { stroke: var(--token-accent); }
10124
+
10125
+ /* ── 数字带前缀/后缀 ── */
10126
+ .stat-num-prefix,
10127
+ .stat-num-suffix {
10128
+ font-size: 0.6em;
10129
+ font-weight: 500;
10130
+ color: var(--token-text-faint);
10131
+ margin: 0 1px;
10132
+ }
10133
+
10134
+ /* ── 加载态骨架 ── */
10135
+ .stat-card.is-loading .stat-num,
10136
+ .stat-card.is-loading .stat-delta {
10137
+ color: transparent;
10138
+ background: var(--token-fill-2);
10139
+ border-radius: var(--radius-xs);
10140
+ animation: stat-skeleton 1.5s ease-in-out infinite;
10141
+ }
10142
+ .stat-card.is-loading .stat-num { height: 28px; }
10143
+ .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
+ @media (prefers-reduced-motion: reduce) {
10149
+ .stat-card.is-loading .stat-num,
10150
+ .stat-card.is-loading .stat-delta { animation: none; }
10151
+ .stat-ring-fill { transition: none; }
10152
+ }
10153
+
6997
10154
 
6998
10155
  /* ── components/steps.css ── */
6999
10156
  /*
@@ -7132,6 +10289,103 @@ html {
7132
10289
  .step-dot, .step-line { transition: none; }
7133
10290
  }
7134
10291
 
10292
+ /* ── 纯点变体(无数字/无标签,紧凑进度指示) ── */
10293
+ .steps--dots .step-dot {
10294
+ width: 10px;
10295
+ height: 10px;
10296
+ padding: 0;
10297
+ font-size: 0;
10298
+ }
10299
+ .steps--dots .step-line { top: 20px; }
10300
+ .steps--dots .step-title,
10301
+ .steps--dots .step-desc { display: none; }
10302
+ .steps--dots .step { flex: none; }
10303
+ .steps--dots { gap: var(--density-space-xs); }
10304
+
10305
+ /* ── 自定义图标槽(step-dot 内放 svg) ── */
10306
+ .step-dot-icon {
10307
+ width: 16px;
10308
+ height: 16px;
10309
+ }
10310
+ .step--finish .step-dot-icon { color: var(--token-on-accent, #fff); }
10311
+ .step--process .step-dot-icon { color: var(--token-accent); }
10312
+ .step--wait .step-dot-icon { color: var(--token-text-faint); }
10313
+ .step--error .step-dot-icon { color: var(--token-error); }
10314
+
10315
+ /* ── 可点击步骤 ── */
10316
+ .step--clickable {
10317
+ cursor: pointer;
10318
+ transition: opacity var(--icen-tx-fast);
10319
+ }
10320
+ .step--clickable:hover .step-dot {
10321
+ transform: scale(1.08);
10322
+ box-shadow: var(--icen-ring-soft);
10323
+ }
10324
+ .step--clickable:hover .step-title {
10325
+ color: var(--token-accent);
10326
+ }
10327
+
10328
+ /* ── 进度条变体(步骤间的填充进度,非离散连线) ── */
10329
+ .steps--progress .step-line {
10330
+ height: 3px;
10331
+ border-radius: var(--radius-full);
10332
+ background: var(--token-fill-2);
10333
+ overflow: hidden;
10334
+ }
10335
+ .steps--progress .step-line::after {
10336
+ content: '';
10337
+ position: absolute;
10338
+ inset: 0;
10339
+ background: var(--token-accent);
10340
+ transform-origin: left;
10341
+ transform: scaleX(0);
10342
+ transition: transform var(--icen-tx-slow) var(--ease-out);
10343
+ }
10344
+ .steps--progress .step--finish .step-line::after { transform: scaleX(1); }
10345
+
10346
+ /* ── 垂直模式的进度条变体 ── */
10347
+ .steps--vertical.steps--progress .step-line::after {
10348
+ transform-origin: top;
10349
+ transform: scaleY(0);
10350
+ }
10351
+ .steps--vertical.steps--progress .step--finish .step-line::after {
10352
+ transform: scaleY(1);
10353
+ }
10354
+
10355
+ /* ── 步骤描述富化(可选 icon + text) ── */
10356
+ .step-extra {
10357
+ display: flex;
10358
+ align-items: center;
10359
+ gap: 4px;
10360
+ margin-top: 4px;
10361
+ font-size: var(--density-font-size-xs);
10362
+ color: var(--token-text-faint);
10363
+ justify-content: center;
10364
+ }
10365
+ .step--error .step-extra { color: var(--token-error); }
10366
+
10367
+ /* ── 数字步骤的连接箭头(可选替代纯线条) ── */
10368
+ .steps--connected .step-line {
10369
+ height: 0;
10370
+ background: transparent;
10371
+ }
10372
+ .steps--connected .step-line::before {
10373
+ content: '';
10374
+ position: absolute;
10375
+ inset: 0;
10376
+ border-top: 1px dashed var(--token-outline-soft);
10377
+ }
10378
+ .steps--connected .step--finish .step-line::before {
10379
+ border-top-style: solid;
10380
+ border-top-color: var(--token-accent);
10381
+ }
10382
+
10383
+ @media (max-width: 640px) {
10384
+ /* 小屏:横向 steps 变为可横向滚动 */
10385
+ .steps { overflow-x: auto; flex-wrap: nowrap; }
10386
+ .step { min-width: 80px; }
10387
+ }
10388
+
7135
10389
 
7136
10390
  /* ── components/switch.css ── */
7137
10391
  /*
@@ -7329,22 +10583,164 @@ html {
7329
10583
  background: var(--token-control);
7330
10584
  transition: border-color var(--icen-tx-fast);
7331
10585
  }
7332
- .radio-item.is-selected .radio-dot,
7333
- .radio-item[aria-checked='true'] .radio-dot { border-color: var(--token-accent); }
7334
- .radio-dot::after {
7335
- content: '';
7336
- position: absolute;
7337
- top: 50%; left: 50%;
7338
- width: 8px;
7339
- height: 8px;
7340
- border-radius: var(--radius-full);
7341
- background: var(--token-accent);
7342
- transform: translate(-50%, -50%) scale(0);
7343
- transition: transform var(--icen-tx-bounce);
10586
+ .radio-item.is-selected .radio-dot,
10587
+ .radio-item[aria-checked='true'] .radio-dot { border-color: var(--token-accent); }
10588
+ .radio-dot::after {
10589
+ content: '';
10590
+ position: absolute;
10591
+ top: 50%; left: 50%;
10592
+ width: 8px;
10593
+ height: 8px;
10594
+ border-radius: var(--radius-full);
10595
+ background: var(--token-accent);
10596
+ transform: translate(-50%, -50%) scale(0);
10597
+ transition: transform var(--icen-tx-bounce);
10598
+ }
10599
+ .radio-item.is-selected .radio-dot::after,
10600
+ .radio-item[aria-checked='true'] .radio-dot::after {
10601
+ transform: translate(-50%, -50%) scale(1);
10602
+ }
10603
+
10604
+ /* ── 开关色彩变体 ── */
10605
+ .switch--success[aria-checked='true'] { background: var(--token-success); }
10606
+ .switch--warning[aria-checked='true'] { background: var(--token-warning); }
10607
+ .switch--danger[aria-checked='true'] { background: var(--token-error); }
10608
+
10609
+ /* ── 开关带文字(on/off 文字嵌入) ── */
10610
+ .switch--text {
10611
+ --switch-w: 52px;
10612
+ }
10613
+ .switch--text .switch-thumb {
10614
+ display: flex;
10615
+ align-items: center;
10616
+ justify-content: center;
10617
+ font-size: 9px;
10618
+ font-weight: 700;
10619
+ color: transparent;
10620
+ }
10621
+ .switch--text[aria-checked='true']::before {
10622
+ content: 'ON';
10623
+ position: absolute;
10624
+ left: 6px;
10625
+ top: 50%;
10626
+ transform: translateY(-50%);
10627
+ font-size: 9px;
10628
+ font-weight: 700;
10629
+ color: var(--token-on-accent, #fff);
10630
+ }
10631
+ .switch--text[aria-checked='false']::before {
10632
+ content: 'OFF';
10633
+ position: absolute;
10634
+ right: 6px;
10635
+ top: 50%;
10636
+ transform: translateY(-50%);
10637
+ font-size: 9px;
10638
+ font-weight: 700;
10639
+ color: var(--token-text-faint);
10640
+ }
10641
+
10642
+ /* ── 带标签的开关行 ── */
10643
+ .switch-row {
10644
+ display: flex;
10645
+ align-items: center;
10646
+ justify-content: space-between;
10647
+ gap: var(--density-space-sm);
10648
+ padding: var(--density-space-sm) 0;
10649
+ }
10650
+ .switch-row-label {
10651
+ display: flex;
10652
+ flex-direction: column;
10653
+ gap: 1px;
10654
+ flex: 1;
10655
+ min-width: 0;
10656
+ }
10657
+ .switch-row-title {
10658
+ font-size: var(--density-font-size-md);
10659
+ color: var(--token-text);
10660
+ }
10661
+ .switch-row-desc {
10662
+ font-size: var(--density-font-size-xs);
10663
+ color: var(--token-text-faint);
10664
+ }
10665
+
10666
+ /* ── 勾选行(checkbox + label 组合) ── */
10667
+ .checkbox-row {
10668
+ display: flex;
10669
+ align-items: flex-start;
10670
+ gap: var(--density-space-sm);
10671
+ cursor: pointer;
10672
+ }
10673
+ .checkbox-row-label {
10674
+ display: flex;
10675
+ flex-direction: column;
10676
+ gap: 1px;
10677
+ flex: 1;
10678
+ min-width: 0;
10679
+ }
10680
+
10681
+ /* ── 单选卡片(富化版 radio-item,带描述与图标) ── */
10682
+ .radio-card {
10683
+ display: flex;
10684
+ flex-direction: column;
10685
+ gap: var(--density-space-xs);
10686
+ padding: var(--density-space-md);
10687
+ border: 1px solid var(--token-outline-soft);
10688
+ border-radius: var(--radius-md);
10689
+ background: var(--token-surface);
10690
+ cursor: pointer;
10691
+ transition:
10692
+ border-color var(--icen-tx-fast),
10693
+ box-shadow var(--icen-tx-fast),
10694
+ background-color var(--icen-tx-fast);
10695
+ }
10696
+ .radio-card:hover {
10697
+ border-color: var(--token-hover-border, color-mix(in srgb, var(--token-accent) 30%, transparent));
10698
+ }
10699
+ .radio-card-head {
10700
+ display: flex;
10701
+ align-items: center;
10702
+ justify-content: space-between;
10703
+ gap: var(--density-space-sm);
10704
+ }
10705
+ .radio-card.is-selected,
10706
+ .radio-card[aria-checked='true'] {
10707
+ border-color: var(--token-accent);
10708
+ background: var(--token-accent-subtle);
10709
+ box-shadow: var(--icen-ring-soft);
10710
+ }
10711
+ .radio-card:focus-visible {
10712
+ outline: none;
10713
+ box-shadow: var(--icen-ring);
10714
+ }
10715
+ .radio-card-title {
10716
+ font-size: var(--density-font-size-md);
10717
+ font-weight: 500;
10718
+ color: var(--token-text);
10719
+ }
10720
+ .radio-card-desc {
10721
+ font-size: var(--density-font-size-sm);
10722
+ color: var(--token-text-muted);
10723
+ line-height: 1.5;
10724
+ }
10725
+
10726
+ /* 单选卡片网格 */
10727
+ .radio-card-grid {
10728
+ display: grid;
10729
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
10730
+ gap: var(--density-space-sm);
7344
10731
  }
7345
- .radio-item.is-selected .radio-dot::after,
7346
- .radio-item[aria-checked='true'] .radio-dot::after {
7347
- transform: translate(-50%, -50%) scale(1);
10732
+
10733
+ /* ── 复选框组(多列网格) ── */
10734
+ .checkbox-grid {
10735
+ display: grid;
10736
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
10737
+ gap: var(--density-space-sm);
10738
+ }
10739
+
10740
+ @media (prefers-reduced-motion: reduce) {
10741
+ .switch-thumb { transition: none; }
10742
+ .radio-dot::after { transition: none; }
10743
+ .radio-card { transition: none; }
7348
10744
  }
7349
10745
 
7350
10746
 
@@ -7354,22 +10750,30 @@ html {
7354
10750
  *
7355
10751
  * 完整契约:
7356
10752
  * <div class="table-wrap [table-wrap--bordered]">
7357
- * <table class="admin-table [admin-table--compact|admin-table--dense]">
10753
+ * <table class="admin-table [--zebra|--compact|--dense] [--sticky-col]">
10754
+ * <caption class="table-caption">标题说明</caption> ← 可选
7358
10755
  * <thead>
7359
- * <tr><th [colspan=rowspan]>列名</th>…</tr>
10756
+ * <tr><th [colspan=rowspan] [data-sort]>列名</th>…</tr>
7360
10757
  * </thead>
7361
10758
  * <tbody>
7362
- * <tr class="row-link [.is-selected]"> ← 整行可点
10759
+ * <tr class="row-link [.is-selected] [.is-expanded]> ← 整行可点
10760
+ * <td class="col-fixed">…</td> ← 固定列(sticky-col 模式)
7363
10761
  * <td>…</td>
7364
10762
  * <td class="ops"><button class="btn btn-sm">…</button></td>
7365
10763
  * </tr>
10764
+ * <tr class="row-expand"> ← 展开行(配合 .is-expanded)
10765
+ * <td colspan="99">…展开详情…</td>
10766
+ * </tr>
7366
10767
  * </tbody>
7367
10768
  * <tfoot><tr><td>合计</td>…</tr></tfoot>
7368
10769
  * </table>
7369
10770
  * </div>
7370
10771
  *
7371
10772
  * .table-wrap:窄屏横向滚动(滚动条样式见 base.css);--bordered 加外边框。
10773
+ * .admin-table--zebra:斑马纹(奇数行底色)。
7372
10774
  * .admin-table--compact:紧凑(cell padding 缩小);--dense:极紧凑(行高更紧)。
10775
+ * .admin-table--sticky-col:首列水平粘滞(配合 td/th .col-fixed,长表横向滚动时保持可见)。
10776
+ * .row-expand[hidden]:配合 .row-link.is-expanded 控制展开行显隐。
7373
10777
  */
7374
10778
 
7375
10779
  .table-wrap {
@@ -7471,6 +10875,81 @@ html {
7471
10875
  .admin-table th[data-sort='asc']::after { content: '↑'; opacity: 1; color: var(--token-accent); }
7472
10876
  .admin-table th[data-sort='desc']::after { content: '↓'; opacity: 1; color: var(--token-accent); }
7473
10877
 
10878
+ /* ── 斑马纹 ── */
10879
+ .admin-table--zebra tbody tr:nth-child(odd) {
10880
+ background: color-mix(in srgb, var(--token-bg-soft) 50%, transparent);
10881
+ }
10882
+ .admin-table--zebra tbody tr:hover {
10883
+ background: var(--token-surface-strong);
10884
+ }
10885
+
10886
+ /* ── 表格标题(caption) ── */
10887
+ .table-caption {
10888
+ text-align: left;
10889
+ padding: var(--density-space-sm) calc(var(--density-space-md) + 2px);
10890
+ font-size: var(--density-font-size-md);
10891
+ font-weight: 600;
10892
+ color: var(--token-text);
10893
+ caption-side: top;
10894
+ }
10895
+
10896
+ /* ── 首列粘滞(长表横向滚动时保持可见) ── */
10897
+ .admin-table--sticky-col th:first-child,
10898
+ .admin-table--sticky-col .col-fixed {
10899
+ position: sticky;
10900
+ left: 0;
10901
+ z-index: 2;
10902
+ 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);
10904
+ }
10905
+ .admin-table--sticky-col tbody td.col-fixed {
10906
+ background: var(--token-surface);
10907
+ }
10908
+ .admin-table--sticky-col tbody tr:hover td.col-fixed {
10909
+ background: var(--token-surface-strong);
10910
+ }
10911
+
10912
+ /* ── 展开行 ── */
10913
+ .admin-table .row-expand > td {
10914
+ padding: 0 var(--density-space-md);
10915
+ background: var(--token-bg-soft);
10916
+ border-bottom: 1px solid var(--token-line-soft);
10917
+ font-size: var(--density-font-size-sm);
10918
+ color: var(--token-text-muted);
10919
+ }
10920
+ .admin-table .row-expand[hidden] { display: none; }
10921
+ /* 展开行的内容容器(使用方可加 padding/动画) */
10922
+ .row-expand-content {
10923
+ padding: var(--density-space-sm) 0;
10924
+ animation: row-expand-in var(--icen-tx-fast) var(--ease-out);
10925
+ }
10926
+ @keyframes row-expand-in {
10927
+ from { opacity: 0; transform: translateY(-4px); }
10928
+ to { opacity: 1; transform: translateY(0); }
10929
+ }
10930
+
10931
+ /* ── 列宽工具类 ── */
10932
+ .admin-table .col-narrow { width: 80px; }
10933
+ .admin-table .col-wide { width: 240px; }
10934
+ .admin-table .col-action { width: 1%; white-space: nowrap; }
10935
+
10936
+ /* ── 行状态色 ── */
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); }
10940
+
10941
+ /* ── 粘滞表头阴影提示 ── */
10942
+ .admin-table thead th {
10943
+ box-shadow: 0 1px 0 var(--token-line);
10944
+ }
10945
+
10946
+ /* ── 表头分组(rowspan/colspan 样式) ── */
10947
+ .admin-table th[rowspan] { vertical-align: bottom; }
10948
+
10949
+ @media (prefers-reduced-motion: reduce) {
10950
+ .row-expand-content { animation: none; }
10951
+ }
10952
+
7474
10953
 
7475
10954
  /* ── components/tabs.css ── */
7476
10955
  /*
@@ -7583,8 +11062,129 @@ html {
7583
11062
  text-align: center;
7584
11063
  }
7585
11064
 
11065
+ /* ── 增强 ── */
11066
+
11067
+ /* 尺寸变体 */
11068
+ .page-tabs--sm .page-tab {
11069
+ padding: var(--density-space-xs) var(--density-space-sm);
11070
+ font-size: var(--density-font-size-sm);
11071
+ }
11072
+ .page-tabs--lg .page-tab {
11073
+ padding: var(--density-space-md) calc(var(--density-space-md) + 4px);
11074
+ font-size: var(--density-font-size-lg);
11075
+ }
11076
+
11077
+ /* Tab 带图标 */
11078
+ .page-tab-icon {
11079
+ width: 14px;
11080
+ height: 14px;
11081
+ flex-shrink: 0;
11082
+ margin-right: 2px;
11083
+ }
11084
+
11085
+ /* Tab 带徽章/计数 */
11086
+ .page-tab-badge {
11087
+ display: inline-flex;
11088
+ align-items: center;
11089
+ justify-content: center;
11090
+ min-width: 18px;
11091
+ height: 18px;
11092
+ padding: 0 5px;
11093
+ margin-left: var(--density-space-xs);
11094
+ font-size: 10px;
11095
+ font-weight: 600;
11096
+ color: var(--token-text-faint);
11097
+ background: var(--token-fill-2);
11098
+ border-radius: var(--radius-full);
11099
+ font-variant-numeric: tabular-nums;
11100
+ line-height: 1;
11101
+ }
11102
+ .page-tab.active .page-tab-badge,
11103
+ .page-tab[aria-selected='true'] .page-tab-badge {
11104
+ background: var(--token-accent);
11105
+ color: var(--token-on-accent, #fff);
11106
+ }
11107
+
11108
+ /* 可关闭 tab */
11109
+ .page-tab-close {
11110
+ display: inline-flex;
11111
+ align-items: center;
11112
+ justify-content: center;
11113
+ width: 16px;
11114
+ height: 16px;
11115
+ margin-left: var(--density-space-xs);
11116
+ margin-right: -4px;
11117
+ padding: 0;
11118
+ border: 0;
11119
+ border-radius: var(--radius-full);
11120
+ background: transparent;
11121
+ color: var(--token-text-faint);
11122
+ cursor: pointer;
11123
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
11124
+ }
11125
+ .page-tab-close:hover {
11126
+ color: var(--token-error);
11127
+ background: var(--token-error-muted);
11128
+ }
11129
+ .page-tab-close svg { width: 10px; height: 10px; }
11130
+
11131
+ /* 分段式 tab(替代底边线,用 segmented 视觉) */
11132
+ .page-tabs--segment {
11133
+ display: inline-flex;
11134
+ border-bottom: none;
11135
+ gap: 2px;
11136
+ padding: 2px;
11137
+ background: var(--token-panel);
11138
+ border: 1px solid var(--token-outline-soft);
11139
+ border-radius: var(--radius-md);
11140
+ box-shadow: var(--token-shadow-inset);
11141
+ }
11142
+ .page-tabs--segment .page-tab {
11143
+ border-bottom: none;
11144
+ margin-bottom: 0;
11145
+ padding: var(--density-space-xs) var(--density-space-md);
11146
+ border-radius: var(--radius-sm);
11147
+ }
11148
+ .page-tabs--segment .page-tab.active,
11149
+ .page-tabs--segment .page-tab[aria-selected='true'] {
11150
+ background: var(--token-accent);
11151
+ color: var(--token-on-accent, #fff);
11152
+ border-bottom-color: transparent;
11153
+ font-weight: 500;
11154
+ }
11155
+
11156
+ /* 垂直 tab(侧栏式) */
11157
+ .page-tabs--vertical {
11158
+ flex-direction: column;
11159
+ border-bottom: none;
11160
+ border-right: 1px solid var(--token-line-soft);
11161
+ margin-bottom: 0;
11162
+ min-width: 160px;
11163
+ }
11164
+ .page-tabs--vertical .page-tab {
11165
+ border-bottom: none;
11166
+ border-right: 2px solid transparent;
11167
+ margin-bottom: 0;
11168
+ margin-right: -1px;
11169
+ }
11170
+ .page-tabs--vertical .page-tab.active,
11171
+ .page-tabs--vertical .page-tab[aria-selected='true'] {
11172
+ border-right-color: var(--token-accent);
11173
+ border-bottom-color: transparent;
11174
+ }
11175
+
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
+
7586
11186
  @media (prefers-reduced-motion: reduce) {
7587
- .page-tab, .admin-tabs a { transition: none; }
11187
+ .page-tab, .admin-tabs a, .page-tab-close { transition: none; }
7588
11188
  }
7589
11189
 
7590
11190
 
@@ -7677,61 +11277,228 @@ html {
7677
11277
  .tag-chip { animation: none; }
7678
11278
  }
7679
11279
 
7680
- .tag-chip-x {
11280
+ .tag-chip-x {
11281
+ display: inline-flex;
11282
+ align-items: center;
11283
+ justify-content: center;
11284
+ width: 16px;
11285
+ height: 16px;
11286
+ padding: 0;
11287
+ border: none;
11288
+ background: transparent;
11289
+ cursor: pointer;
11290
+ border-radius: var(--radius-full);
11291
+ color: inherit;
11292
+ font-size: 11px;
11293
+ line-height: 1;
11294
+ opacity: 0.7;
11295
+ transition:
11296
+ background-color var(--icen-tx-fast),
11297
+ opacity var(--icen-tx-fast),
11298
+ color var(--icen-tx-fast);
11299
+ }
11300
+ .tag-chip-x:hover {
11301
+ opacity: 1;
11302
+ background: color-mix(in srgb, currentColor 18%, transparent);
11303
+ color: var(--token-on-accent, #fff);
11304
+ }
11305
+ .tag-chip-x:focus-visible {
11306
+ outline: none;
11307
+ opacity: 1;
11308
+ box-shadow: var(--icen-ring);
11309
+ }
11310
+ .tag-chip-x svg { width: 10px; height: 10px; }
11311
+
11312
+ .tag-input-field {
11313
+ flex: 1;
11314
+ min-width: 80px;
11315
+ padding: 2px;
11316
+ background: transparent;
11317
+ border: none;
11318
+ outline: none;
11319
+ color: var(--token-text);
11320
+ font: inherit;
11321
+ font-size: var(--density-font-size-sm);
11322
+ }
11323
+ .tag-input-field::placeholder {
11324
+ color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
11325
+ }
11326
+
11327
+ /* 重复值抖动反馈(behavior 短暂挂 is-shake) */
11328
+ .tag-input.is-shake {
11329
+ animation: tag-shake 0.32s var(--ease-out);
11330
+ }
11331
+ @keyframes tag-shake {
11332
+ 0%, 100% { transform: translateX(0); }
11333
+ 20%, 60% { transform: translateX(-3px); }
11334
+ 40%, 80% { transform: translateX(3px); }
11335
+ }
11336
+
11337
+
11338
+ /* ── components/tag.css ── */
11339
+ /*
11340
+ * @icen.ai/ui — Component: tag(展示标签 / Chip)
11341
+ *
11342
+ * 与 pill 的差异(各司其职,不互相替代):
11343
+ * - pill = 状态语义徽章(绿/黄/红/信息/accent),小尺寸,行内只读
11344
+ * - tag = 中性展示标签,可选关闭按钮、可选中态、可带图标
11345
+ * 常用于:分类、筛选条件、属性标记、邮件标签、文件 tag
11346
+ *
11347
+ * 完整契约:
11348
+ * <span class="tag [.tag--sm|.tag--lg] [.is-selected] [.is-disabled]
11349
+ * [.tag--accent|.tag--success|.tag--warning|.tag--error|.tag--info]">
11350
+ * <span class="tag-icon">…svg…</span> <!-- 可选左图标 -->
11351
+ * <span class="tag-label">标签文本</span>
11352
+ * <button class="tag-close" type="button" aria-label="移除">×</button> <!-- 可选 -->
11353
+ * </span>
11354
+ *
11355
+ * 选中态 .is-selected 由消费方驱动(用于筛选条件等);
11356
+ * .tag-close 点击后消费方决定是否从 DOM 移除本节点(本组件不自动删除)。
11357
+ */
11358
+
11359
+ .tag {
11360
+ display: inline-flex;
11361
+ align-items: center;
11362
+ gap: 4px;
11363
+ padding: 0 var(--density-space-sm);
11364
+ height: 24px;
11365
+ background: var(--token-fill-1);
11366
+ color: var(--token-text-muted);
11367
+ border: 1px solid var(--token-line-soft);
11368
+ border-radius: var(--radius-sm);
11369
+ font: inherit;
11370
+ font-size: var(--density-font-size-sm);
11371
+ letter-spacing: 0.01em;
11372
+ white-space: nowrap;
11373
+ vertical-align: middle;
11374
+ transition:
11375
+ background-color var(--icen-tx-fast),
11376
+ border-color var(--icen-tx-fast),
11377
+ color var(--icen-tx-fast);
11378
+ }
11379
+ .tag:hover {
11380
+ background: var(--token-fill-2);
11381
+ color: var(--token-text);
11382
+ }
11383
+
11384
+ /* 尺寸变体 */
11385
+ .tag--sm {
11386
+ height: 20px;
11387
+ padding: 0 6px;
11388
+ font-size: var(--density-font-size-xs);
11389
+ gap: 3px;
11390
+ }
11391
+ .tag--lg {
11392
+ height: 28px;
11393
+ padding: 0 10px;
11394
+ font-size: var(--density-font-size-md);
11395
+ gap: 6px;
11396
+ }
11397
+
11398
+ /* ── 图标 ── */
11399
+ .tag-icon {
11400
+ display: inline-flex;
11401
+ align-items: center;
11402
+ justify-content: center;
11403
+ flex-shrink: 0;
11404
+ color: var(--token-text-faint);
11405
+ }
11406
+ .tag-icon svg { width: 12px; height: 12px; }
11407
+ .tag--sm .tag-icon svg { width: 10px; height: 10px; }
11408
+ .tag--lg .tag-icon svg { width: 14px; height: 14px; }
11409
+ .tag-label {
11410
+ overflow: hidden;
11411
+ text-overflow: ellipsis;
11412
+ max-width: 240px;
11413
+ }
11414
+
11415
+ /* ── 关闭按钮(消费方点击后自行决定是否从 DOM 移除) ── */
11416
+ .tag-close {
7681
11417
  display: inline-flex;
7682
11418
  align-items: center;
7683
11419
  justify-content: center;
7684
11420
  width: 16px;
7685
11421
  height: 16px;
7686
11422
  padding: 0;
7687
- border: none;
11423
+ margin-right: -4px;
11424
+ margin-left: 2px;
11425
+ border: 0;
11426
+ border-radius: var(--radius-xs);
7688
11427
  background: transparent;
7689
- cursor: pointer;
7690
- border-radius: var(--radius-full);
7691
11428
  color: inherit;
7692
- font-size: 11px;
7693
- line-height: 1;
7694
- opacity: 0.7;
7695
- transition:
7696
- background-color var(--icen-tx-fast),
7697
- opacity var(--icen-tx-fast),
7698
- color var(--icen-tx-fast);
11429
+ opacity: 0.6;
11430
+ cursor: pointer;
11431
+ -webkit-tap-highlight-color: transparent;
11432
+ transition: opacity var(--icen-tx-fast), background-color var(--icen-tx-fast);
7699
11433
  }
7700
- .tag-chip-x:hover {
11434
+ .tag-close:hover {
7701
11435
  opacity: 1;
7702
- background: color-mix(in srgb, currentColor 18%, transparent);
7703
- color: var(--token-on-accent, #fff);
11436
+ background: color-mix(in srgb, var(--token-text-faint) 22%, transparent);
7704
11437
  }
7705
- .tag-chip-x:focus-visible {
11438
+ .tag-close:focus-visible {
7706
11439
  outline: none;
7707
- opacity: 1;
7708
11440
  box-shadow: var(--icen-ring);
7709
11441
  }
7710
- .tag-chip-x svg { width: 10px; height: 10px; }
11442
+ .tag-close svg { width: 10px; height: 10px; }
11443
+ .tag--sm .tag-close { width: 14px; height: 14px; margin-right: -2px; }
11444
+ .tag--lg .tag-close { width: 18px; height: 18px; margin-right: -6px; }
7711
11445
 
7712
- .tag-input-field {
7713
- flex: 1;
7714
- min-width: 80px;
7715
- padding: 2px;
7716
- background: transparent;
7717
- border: none;
7718
- outline: none;
7719
- color: var(--token-text);
7720
- font: inherit;
7721
- font-size: var(--density-font-size-sm);
11446
+ /* ── 选中态(用于筛选条件等)── */
11447
+ .tag.is-selected {
11448
+ background: var(--token-accent-subtle);
11449
+ color: var(--token-accent-deep, var(--token-accent));
11450
+ border-color: color-mix(in srgb, var(--token-accent) 40%, transparent);
7722
11451
  }
7723
- .tag-input-field::placeholder {
7724
- color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
11452
+ .tag.is-selected .tag-icon { color: var(--token-accent); }
11453
+
11454
+ /* ── 语义色变体(与 pill 对齐,但底色更淡,作分类着色用)── */
11455
+ .tag--accent {
11456
+ background: color-mix(in srgb, var(--token-accent) 10%, transparent);
11457
+ color: var(--token-accent-deep, var(--token-accent));
11458
+ border-color: color-mix(in srgb, var(--token-accent) 30%, transparent);
11459
+ }
11460
+ .tag--success {
11461
+ background: color-mix(in srgb, var(--token-success) 10%, transparent);
11462
+ color: var(--token-success);
11463
+ border-color: color-mix(in srgb, var(--token-success) 30%, transparent);
11464
+ }
11465
+ .tag--warning {
11466
+ background: color-mix(in srgb, var(--token-warning) 10%, transparent);
11467
+ color: var(--token-warning);
11468
+ border-color: color-mix(in srgb, var(--token-warning) 30%, transparent);
11469
+ }
11470
+ .tag--error {
11471
+ background: color-mix(in srgb, var(--token-error) 10%, transparent);
11472
+ color: var(--token-error);
11473
+ border-color: color-mix(in srgb, var(--token-error) 30%, transparent);
11474
+ }
11475
+ .tag--info {
11476
+ background: color-mix(in srgb, var(--token-info) 10%, transparent);
11477
+ color: var(--token-info);
11478
+ border-color: color-mix(in srgb, var(--token-info) 30%, transparent);
7725
11479
  }
7726
11480
 
7727
- /* 重复值抖动反馈(behavior 短暂挂 is-shake) */
7728
- .tag-input.is-shake {
7729
- animation: tag-shake 0.32s var(--ease-out);
11481
+ /* ── 禁用态 ── */
11482
+ .tag.is-disabled,
11483
+ .tag[aria-disabled='true'] {
11484
+ opacity: var(--icen-disabled-opacity);
11485
+ cursor: not-allowed;
11486
+ pointer-events: none;
7730
11487
  }
7731
- @keyframes tag-shake {
7732
- 0%, 100% { transform: translateX(0); }
7733
- 20%, 60% { transform: translateX(-3px); }
7734
- 40%, 80% { transform: translateX(3px); }
11488
+
11489
+ /* ── 可点击(用于可筛选 tag) ── */
11490
+ button.tag,
11491
+ .tag[role='button'] {
11492
+ cursor: pointer;
11493
+ -webkit-tap-highlight-color: transparent;
11494
+ }
11495
+ .tag:where(button, [role='button']):focus-visible {
11496
+ outline: none;
11497
+ box-shadow: var(--icen-ring);
11498
+ }
11499
+
11500
+ @media (prefers-reduced-motion: reduce) {
11501
+ .tag, .tag-close { transition: none; }
7735
11502
  }
7736
11503
 
7737
11504
 
@@ -7815,6 +11582,120 @@ html {
7815
11582
  box-shadow: var(--icen-ring);
7816
11583
  }
7817
11584
 
11585
+ /* ── 增强 ── */
11586
+
11587
+ /* 语义图标 */
11588
+ .toast-icon {
11589
+ flex-shrink: 0;
11590
+ display: inline-flex;
11591
+ align-items: center;
11592
+ justify-content: center;
11593
+ color: var(--token-text-faint);
11594
+ }
11595
+ .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); }
11599
+
11600
+ /* 关闭按钮 */
11601
+ .toast-close {
11602
+ flex-shrink: 0;
11603
+ display: inline-flex;
11604
+ align-items: center;
11605
+ justify-content: center;
11606
+ width: 20px;
11607
+ height: 20px;
11608
+ padding: 0;
11609
+ border: 0;
11610
+ border-radius: var(--radius-xs);
11611
+ background: transparent;
11612
+ color: var(--token-text-faint);
11613
+ cursor: pointer;
11614
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
11615
+ }
11616
+ .toast-close:hover {
11617
+ color: var(--token-text);
11618
+ background: var(--token-fill-1);
11619
+ }
11620
+ .toast-close svg { width: 12px; height: 12px; }
11621
+
11622
+ /* 富内容(标题 + 正文) */
11623
+ .toast-content {
11624
+ flex: 1;
11625
+ min-width: 0;
11626
+ }
11627
+ .toast-title {
11628
+ font-size: var(--density-font-size-md);
11629
+ font-weight: 500;
11630
+ color: var(--token-text);
11631
+ line-height: 1.4;
11632
+ }
11633
+ .toast-body {
11634
+ font-size: var(--density-font-size-sm);
11635
+ color: var(--token-text-muted);
11636
+ margin-top: 2px;
11637
+ line-height: 1.5;
11638
+ }
11639
+
11640
+ /* 超时进度条 */
11641
+ .toast-progress {
11642
+ position: absolute;
11643
+ bottom: 0;
11644
+ left: 0;
11645
+ right: 0;
11646
+ height: 2px;
11647
+ background: transparent;
11648
+ overflow: hidden;
11649
+ border-radius: 0 0 var(--radius-sm) var(--radius-sm);
11650
+ }
11651
+ .toast-progress-fill {
11652
+ height: 100%;
11653
+ background: var(--token-accent);
11654
+ transform-origin: left;
11655
+ animation: toast-countdown linear forwards;
11656
+ }
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); }
11660
+ @keyframes toast-countdown {
11661
+ from { transform: scaleX(1); }
11662
+ to { transform: scaleX(0); }
11663
+ }
11664
+
11665
+ .toast { position: relative; overflow: hidden; }
11666
+
11667
+ /* 容器定位变体 */
11668
+ .toast-container[data-position='top-left'] {
11669
+ right: auto;
11670
+ bottom: auto;
11671
+ top: var(--density-space-md);
11672
+ left: var(--density-space-md);
11673
+ align-items: flex-start;
11674
+ }
11675
+ .toast-container[data-position='top-right'] {
11676
+ bottom: auto;
11677
+ top: var(--density-space-md);
11678
+ }
11679
+ .toast-container[data-position='top-center'] {
11680
+ right: auto;
11681
+ bottom: auto;
11682
+ top: var(--density-space-md);
11683
+ left: 50%;
11684
+ transform: translateX(-50%);
11685
+ align-items: center;
11686
+ }
11687
+ .toast-container[data-position='bottom-left'] {
11688
+ right: auto;
11689
+ left: var(--density-space-md);
11690
+ align-items: flex-start;
11691
+ }
11692
+ .toast-container[data-position='bottom-center'] {
11693
+ right: auto;
11694
+ left: 50%;
11695
+ transform: translateX(-50%);
11696
+ align-items: center;
11697
+ }
11698
+
7818
11699
  .toast.leaving {
7819
11700
  opacity: 0;
7820
11701
  transform: translateY(8px);
@@ -7838,6 +11719,110 @@ html {
7838
11719
  }
7839
11720
 
7840
11721
 
11722
+ /* ── components/toolbar.css ── */
11723
+ /*
11724
+ * @icen.ai/ui — Component: toolbar(工具条)
11725
+ *
11726
+ * 横向承载按钮 / 控件的容器,常用于编辑器顶栏、列表上方操作行、表格批量操作条。
11727
+ * 与 segmented 的区别:
11728
+ * - segmented = 紧贴的互斥分段选择
11729
+ * - toolbar = 宽松的按钮/控件容器,可分组、可分隔、可换行
11730
+ *
11731
+ * 完整契约:
11732
+ * <div class="toolbar [--sm|--lg] [--wrap] [--borderless]">
11733
+ * <button class="btn btn-sm">…</button>
11734
+ * <div class="toolbar-group">
11735
+ * <button class="btn btn-sm">…</button>
11736
+ * <button class="btn btn-sm">…</button>
11737
+ * </div>
11738
+ * <span class="toolbar-separator"></span>
11739
+ * <div class="toolbar-spacer"></div> ← 弹性占位,把右侧元素推到末尾
11740
+ * <span class="toolbar-label">3 项</span>
11741
+ * </div>
11742
+ *
11743
+ * 编辑器顶栏(与 fixed 子项配套):
11744
+ * <div class="toolbar toolbar--elevated">
11745
+ * …
11746
+ * </div>
11747
+ */
11748
+
11749
+ .toolbar {
11750
+ display: flex;
11751
+ align-items: center;
11752
+ gap: var(--density-space-sm);
11753
+ flex-wrap: nowrap;
11754
+ padding: var(--density-space-sm);
11755
+ background: var(--token-bg-soft);
11756
+ border: 1px solid var(--token-line-soft);
11757
+ border-radius: var(--radius-md);
11758
+ }
11759
+ .toolbar--wrap { flex-wrap: wrap; }
11760
+ .toolbar--borderless {
11761
+ background: transparent;
11762
+ border-color: transparent;
11763
+ padding: 0;
11764
+ }
11765
+ .toolbar--elevated {
11766
+ background: var(--token-card-strong);
11767
+ border-color: var(--token-outline-soft);
11768
+ box-shadow: var(--token-shadow-elevated);
11769
+ }
11770
+
11771
+ /* ── 尺寸 ── */
11772
+ .toolbar--sm { padding: 4px var(--density-space-xs); gap: var(--density-space-xs); }
11773
+ .toolbar--lg { padding: var(--density-space-md); gap: var(--density-space-md); }
11774
+
11775
+ /* ── 分组(紧贴一组按钮) ── */
11776
+ .toolbar-group {
11777
+ display: inline-flex;
11778
+ align-items: center;
11779
+ gap: 2px;
11780
+ padding: 2px;
11781
+ background: var(--token-fill-1);
11782
+ border-radius: var(--radius-sm);
11783
+ }
11784
+ .toolbar-group > .btn { border-color: transparent; background: transparent; }
11785
+ .toolbar-group > .btn:hover { background: var(--token-fill-2); }
11786
+
11787
+ /* ── 分隔符 ── */
11788
+ .toolbar-separator {
11789
+ width: 1px;
11790
+ height: 20px;
11791
+ background: var(--token-line-soft);
11792
+ flex-shrink: 0;
11793
+ }
11794
+ .toolbar--sm .toolbar-separator { height: 16px; }
11795
+ .toolbar--lg .toolbar-separator { height: 24px; }
11796
+
11797
+ /* 水平工具条里的垂直分隔(用于纵向 toolbar) */
11798
+ .toolbar--vertical { flex-direction: column; align-items: stretch; }
11799
+ .toolbar--vertical .toolbar-separator {
11800
+ width: auto;
11801
+ height: 1px;
11802
+ margin: var(--density-space-xs) 0;
11803
+ }
11804
+
11805
+ /* ── 弹性占位 ── */
11806
+ .toolbar-spacer { flex: 1 1 auto; min-width: var(--density-space-xs); }
11807
+
11808
+ /* ── 内嵌文字(计数 / 状态) ── */
11809
+ .toolbar-label {
11810
+ font-size: var(--density-font-size-sm);
11811
+ color: var(--token-text-muted);
11812
+ font-variant-numeric: tabular-nums;
11813
+ white-space: nowrap;
11814
+ }
11815
+ .toolbar-label.accent { color: var(--token-accent); }
11816
+
11817
+ @media (max-width: 480px) {
11818
+ .toolbar { flex-wrap: wrap; }
11819
+ }
11820
+
11821
+ @media (prefers-reduced-motion: reduce) {
11822
+ .toolbar * { transition: none !important; }
11823
+ }
11824
+
11825
+
7841
11826
  /* ── components/tooltip.css ── */
7842
11827
  /*
7843
11828
  * @icen.ai/ui — Component: tooltip(纯 CSS 气泡提示,无 JS)
@@ -8090,12 +12075,43 @@ html {
8090
12075
  border-radius: var(--radius-sm);
8091
12076
  font-size: var(--density-font-size-sm);
8092
12077
  animation: upload-item-in var(--icen-tx-fast);
12078
+ transition: border-color var(--icen-tx-fast);
8093
12079
  }
8094
- .upload-list-item-name {
12080
+ .upload-list-item:hover {
12081
+ border-color: var(--token-outline);
12082
+ }
12083
+ .upload-list-item-thumb {
12084
+ width: 32px;
12085
+ height: 32px;
12086
+ object-fit: cover;
12087
+ border-radius: var(--radius-xs);
12088
+ flex-shrink: 0;
12089
+ background: var(--token-bg-soft);
12090
+ }
12091
+ .upload-list-item-icon {
12092
+ display: inline-flex;
12093
+ align-items: center;
12094
+ justify-content: center;
12095
+ width: 32px;
12096
+ height: 32px;
12097
+ flex-shrink: 0;
12098
+ border-radius: var(--radius-xs);
12099
+ background: var(--token-fill-1);
12100
+ color: var(--token-text-faint);
12101
+ }
12102
+ .upload-list-item-icon svg { width: 16px; height: 16px; }
12103
+ .upload-list-item-info {
8095
12104
  flex: 1;
12105
+ min-width: 0;
12106
+ display: flex;
12107
+ flex-direction: column;
12108
+ gap: 1px;
12109
+ }
12110
+ .upload-list-item-name {
8096
12111
  overflow: hidden;
8097
12112
  text-overflow: ellipsis;
8098
12113
  white-space: nowrap;
12114
+ color: var(--token-text);
8099
12115
  }
8100
12116
  .upload-list-item-size {
8101
12117
  color: var(--token-text-faint);
@@ -8107,20 +12123,46 @@ html {
8107
12123
  background: transparent;
8108
12124
  cursor: pointer;
8109
12125
  color: var(--token-text-faint);
8110
- padding: 2px;
12126
+ padding: 4px;
8111
12127
  border-radius: var(--radius-full);
8112
12128
  display: inline-flex;
12129
+ align-items: center;
12130
+ justify-content: center;
12131
+ flex-shrink: 0;
8113
12132
  transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
8114
12133
  }
8115
12134
  .upload-list-item-x:hover {
8116
- color: var(--token-text);
8117
- background: var(--token-fill-2);
12135
+ color: var(--token-error);
12136
+ background: var(--token-error-muted);
8118
12137
  }
8119
12138
  .upload-list-item-x:focus-visible {
8120
12139
  outline: none;
8121
12140
  box-shadow: var(--icen-ring);
8122
12141
  }
8123
12142
 
12143
+ /* 进度条(behavior 可选注入 .upload-list-item-progress) */
12144
+ .upload-list-item-progress {
12145
+ height: 2px;
12146
+ width: 100%;
12147
+ background: var(--token-fill-2);
12148
+ border-radius: var(--radius-full);
12149
+ overflow: hidden;
12150
+ margin-top: 2px;
12151
+ }
12152
+ .upload-list-item-progress-bar {
12153
+ height: 100%;
12154
+ background: var(--token-accent);
12155
+ border-radius: var(--radius-full);
12156
+ transition: width var(--icen-tx-normal) var(--ease-out);
12157
+ }
12158
+ .upload-list-item.is-error {
12159
+ border-color: var(--token-error);
12160
+ }
12161
+ .upload-list-item.is-error .upload-list-item-icon {
12162
+ background: var(--token-error-muted);
12163
+ color: var(--token-error);
12164
+ }
12165
+
8124
12166
  @keyframes upload-item-in {
8125
12167
  from { opacity: 0; transform: translateY(-4px); }
8126
12168
  to { opacity: 1; transform: translateY(0); }