tavern-harness 0.1.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 (56) hide show
  1. package/README.md +161 -0
  2. package/README.zh.md +123 -0
  3. package/bin/cli.js +48 -0
  4. package/index.html +35 -0
  5. package/package.json +50 -0
  6. package/public/favicon.png +0 -0
  7. package/public/tavern-harness-logo.png +0 -0
  8. package/sandbox-server.mjs +997 -0
  9. package/src/App.tsx +268 -0
  10. package/src/assets/tavern-handbook.md +39 -0
  11. package/src/components/AchievementModal.tsx +158 -0
  12. package/src/components/CharactersView.tsx +904 -0
  13. package/src/components/ChatView.tsx +1639 -0
  14. package/src/components/ConfirmationDialog.tsx +56 -0
  15. package/src/components/Dashboard.tsx +180 -0
  16. package/src/components/DeleteConfirmDialog.tsx +48 -0
  17. package/src/components/FileDisplayModal.tsx +556 -0
  18. package/src/components/GameplayDialogs.tsx +191 -0
  19. package/src/components/NewSessionMenu.tsx +362 -0
  20. package/src/components/SettingsView.tsx +517 -0
  21. package/src/components/Sidebar.tsx +392 -0
  22. package/src/components/StatsView.tsx +189 -0
  23. package/src/components/Toasts.tsx +20 -0
  24. package/src/components/WorkspaceFileManagerModal.tsx +216 -0
  25. package/src/components/shared.tsx +369 -0
  26. package/src/core/achievements.ts +85 -0
  27. package/src/core/fileDownload.ts +67 -0
  28. package/src/core/gameplay.ts +521 -0
  29. package/src/core/horizontalWheel.ts +93 -0
  30. package/src/core/i18n.ts +1403 -0
  31. package/src/core/linkedScroll.ts +236 -0
  32. package/src/core/markdown.ts +170 -0
  33. package/src/core/openai.ts +451 -0
  34. package/src/core/openingGreeting.ts +41 -0
  35. package/src/core/prompts.ts +254 -0
  36. package/src/core/proxy.ts +58 -0
  37. package/src/core/sillyTavernImporter.ts +317 -0
  38. package/src/core/stats.ts +91 -0
  39. package/src/core/strings.ts +19 -0
  40. package/src/core/toolDefinitions.ts +400 -0
  41. package/src/core/tools/builtinTools.ts +38 -0
  42. package/src/core/tools/generatedSkillExecutor.ts +706 -0
  43. package/src/core/tools/generatedWorkspace.ts +13 -0
  44. package/src/core/tools/toolExecutor.ts +718 -0
  45. package/src/core/turnLoop.ts +211 -0
  46. package/src/db/database.ts +647 -0
  47. package/src/main.tsx +23 -0
  48. package/src/store/store.ts +2419 -0
  49. package/src/theme/chat.css +1227 -0
  50. package/src/theme/global.css +1377 -0
  51. package/src/theme/theme.ts +143 -0
  52. package/src/theme/views.css +1430 -0
  53. package/src/types/models.ts +364 -0
  54. package/src/vite-env.d.ts +1 -0
  55. package/tsconfig.json +21 -0
  56. package/vite.config.ts +273 -0
@@ -0,0 +1,1377 @@
1
+ /* ============================================================
2
+ Tavern Harness — 全局样式(酒馆主题)
3
+ 设计语言:暖色木质 + 渐变色玻璃 + 微妙纹理
4
+ ============================================================ */
5
+
6
+ :root {
7
+ background: #F7F2E9;
8
+ color-scheme: light;
9
+ --radius: 14px;
10
+ --radius-lg: 18px;
11
+ --radius-sm: 9px;
12
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
13
+ 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
14
+ /* 衬线展示栈:本地系统衬线,零网络依赖;用于品牌名 / Hero / 页面标题 / 统计数字 */
15
+ --font-serif: 'Iowan Old Style', 'Palatino Linotype', 'Songti SC', 'Noto Serif SC',
16
+ 'Source Han Serif SC', 'STSong', serif;
17
+ --font-mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace;
18
+ font-synthesis: none;
19
+ text-rendering: optimizeLegibility;
20
+ }
21
+
22
+ * {
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ html,
27
+ body,
28
+ #root {
29
+ height: 100%;
30
+ margin: 0;
31
+ }
32
+
33
+ html,
34
+ body {
35
+ background: var(--bg, #F7F2E9);
36
+ }
37
+
38
+ body {
39
+ font-family: var(--font-sans);
40
+ background: var(--bg);
41
+ color: var(--text);
42
+ transition: background-color 0.25s ease, color 0.25s ease;
43
+ overflow: hidden;
44
+ font-size: 14.5px;
45
+ line-height: 1.6;
46
+ }
47
+
48
+ /* 酒馆氛围背景:低饱和度纯色 + 噪点纹理 */
49
+ .tav-bg {
50
+ position: fixed;
51
+ inset: 0;
52
+ z-index: 0;
53
+ pointer-events: none;
54
+ background: var(--bg);
55
+ }
56
+ .tav-bg::after {
57
+ content: '';
58
+ position: absolute;
59
+ inset: 0;
60
+ opacity: 0.35;
61
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.03 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
62
+ }
63
+
64
+ ::-webkit-scrollbar {
65
+ width: 9px;
66
+ height: 9px;
67
+ }
68
+ ::-webkit-scrollbar-thumb {
69
+ background: var(--border);
70
+ border-radius: 8px;
71
+ border: 2px solid transparent;
72
+ background-clip: content-box;
73
+ }
74
+ ::-webkit-scrollbar-thumb:hover {
75
+ background: var(--text-faint);
76
+ border: 2px solid transparent;
77
+ background-clip: content-box;
78
+ }
79
+
80
+ button {
81
+ font-family: inherit;
82
+ cursor: pointer;
83
+ }
84
+
85
+ input,
86
+ textarea,
87
+ select {
88
+ font-family: inherit;
89
+ }
90
+
91
+ .mono {
92
+ font-family: var(--font-mono);
93
+ }
94
+
95
+ /* ---------- 布局框架 ---------- */
96
+ .app-shell {
97
+ position: relative;
98
+ z-index: 1;
99
+ display: grid;
100
+ grid-template-columns: var(--sidebar-w, 356px) 1fr;
101
+ height: 100dvh;
102
+ }
103
+
104
+ /* ---------- 侧边栏 ---------- */
105
+ .sidebar {
106
+ position: relative;
107
+ display: flex;
108
+ flex-direction: column;
109
+ min-width: 0;
110
+ /* grid 子项默认 min-height:auto:内容(会话列表)再多也不会收缩,
111
+ 会把整个侧边栏撑高、把底部搜索框挤出屏幕;必须显式允许收缩 */
112
+ min-height: 0;
113
+ overflow: hidden;
114
+ border-right: 1px solid var(--border);
115
+ background: color-mix(in srgb, var(--surface) 55%, transparent);
116
+ backdrop-filter: blur(18px);
117
+ }
118
+ /* 拖拽手柄 */
119
+ .sidebar-resizer {
120
+ position: absolute;
121
+ top: 0;
122
+ right: -4px;
123
+ width: 8px;
124
+ height: 100%;
125
+ cursor: col-resize;
126
+ z-index: 30;
127
+ transition: background 0.15s;
128
+ }
129
+ .sidebar-resizer:hover {
130
+ background: color-mix(in srgb, var(--primary) 30%, transparent);
131
+ }
132
+ body.resizing {
133
+ cursor: col-resize;
134
+ user-select: none;
135
+ }
136
+ body.resizing .sidebar-resizer {
137
+ background: color-mix(in srgb, var(--primary) 45%, transparent);
138
+ }
139
+ .sidebar-head {
140
+ padding: 14px 14px 10px;
141
+ }
142
+ .brand {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 10px;
146
+ padding: 6px 8px 12px;
147
+ cursor: pointer;
148
+ border-radius: 10px;
149
+ user-select: none;
150
+ transition: background 0.15s;
151
+ }
152
+ .brand:hover {
153
+ background: color-mix(in srgb, var(--text) 5%, transparent);
154
+ }
155
+ .brand:active {
156
+ background: color-mix(in srgb, var(--text) 9%, transparent);
157
+ }
158
+ .brand-logo {
159
+ width: 38px;
160
+ height: 38px;
161
+ border-radius: 50%;
162
+ background: radial-gradient(circle at 35% 30%, var(--grad-from), var(--grad-to) 88%);
163
+ display: grid;
164
+ place-items: center;
165
+ color: #fff;
166
+ font-weight: 800;
167
+ font-size: 19px;
168
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 45%, transparent), 0 0 22px var(--glow);
169
+ flex-shrink: 0;
170
+ transition: box-shadow 0.2s ease;
171
+ }
172
+ .brand:hover .brand-logo {
173
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 60%, transparent), 0 0 30px var(--glow);
174
+ }
175
+ .brand-name {
176
+ font-family: var(--font-serif);
177
+ font-weight: 700;
178
+ font-size: 18px;
179
+ letter-spacing: 0.3px;
180
+ }
181
+ .brand-sub {
182
+ font-size: 11px;
183
+ color: var(--text-faint);
184
+ }
185
+
186
+ .side-nav {
187
+ position: relative;
188
+ display: flex;
189
+ gap: 8px;
190
+ padding: 14px 14px 14px;
191
+ overflow-x: auto;
192
+ /* 不要滚动条:完全隐藏,滚轮由 horizontalWheel.ts 转换为横向滚动 */
193
+ scrollbar-width: none;
194
+ scrollbar-color: var(--border) transparent;
195
+ }
196
+ .side-nav::-webkit-scrollbar {
197
+ display: none;
198
+ height: 0;
199
+ background: transparent;
200
+ }
201
+ /* 有内容被遮挡时:被遮挡侧边缘轻微淡出,提示可横向滚动(不占空间) */
202
+ .side-nav.has-overflow.hidden-left {
203
+ -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px);
204
+ mask-image: linear-gradient(to right, transparent 0, #000 18px);
205
+ }
206
+ .side-nav.has-overflow.hidden-right {
207
+ -webkit-mask-image: linear-gradient(to left, transparent 0, #000 18px);
208
+ mask-image: linear-gradient(to left, transparent 0, #000 18px);
209
+ }
210
+ .side-nav.has-overflow.hidden-left.hidden-right {
211
+ -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
212
+ mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
213
+ }
214
+ .nav-chip {
215
+ flex-shrink: 0;
216
+ display: inline-flex;
217
+ align-items: center;
218
+ gap: 6px;
219
+ padding: 6px 12px;
220
+ border-radius: 999px;
221
+ border: 1px solid var(--border);
222
+ background: transparent;
223
+ color: var(--text-dim);
224
+ font-size: 13px;
225
+ font-weight: 600;
226
+ transition: all 0.32s cubic-bezier(0.16, 1, 0.3, 1);
227
+ white-space: nowrap;
228
+ }
229
+ .nav-chip:hover {
230
+ background: color-mix(in srgb, var(--primary) 8%, transparent);
231
+ color: var(--text);
232
+ border-color: color-mix(in srgb, var(--primary) 25%, transparent);
233
+ }
234
+ .nav-chip.active {
235
+ position: relative;
236
+ z-index: 1;
237
+ background: color-mix(in srgb, var(--primary) 15%, var(--surface));
238
+ border-color: color-mix(in srgb, var(--primary) 58%, transparent);
239
+ color: var(--primary);
240
+ /* 柔和感以 inset 内发光 + 1px 高光环为主;外发光半径收在容器 padding 内避免 overflow 断层 */
241
+ box-shadow:
242
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 14%, transparent),
243
+ inset 0 0 10px color-mix(in srgb, var(--glow) 30%, transparent),
244
+ 0 0 0 1px color-mix(in srgb, var(--primary) 26%, transparent),
245
+ 0 0 8px color-mix(in srgb, var(--glow) 42%, transparent),
246
+ 0 0 14px color-mix(in srgb, var(--glow) 20%, transparent);
247
+ }
248
+ .nav-chip .dot {
249
+ width: 7px;
250
+ height: 7px;
251
+ border-radius: 50%;
252
+ background: currentColor;
253
+ opacity: 0.75;
254
+ }
255
+
256
+ .session-list {
257
+ flex: 1;
258
+ /* flex 子项默认 min-height:auto,内容再多也不会收缩,
259
+ 导致列表整体撑高、把搜索框挤出屏幕而自身不出现滚动条 */
260
+ min-height: 0;
261
+ overflow-y: auto;
262
+ padding: 6px 14px 16px;
263
+ display: flex;
264
+ flex-direction: column;
265
+ gap: 4px;
266
+ }
267
+ .session-item {
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 10px;
271
+ padding: 9px 10px;
272
+ border-radius: var(--radius-sm);
273
+ border: 1px solid transparent;
274
+ background: transparent;
275
+ color: var(--text);
276
+ text-align: left;
277
+ transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.32s cubic-bezier(0.16, 1, 0.3, 1);
278
+ width: 100%;
279
+ }
280
+ .session-item:hover {
281
+ background: color-mix(in srgb, var(--primary) 6%, transparent);
282
+ }
283
+ .session-item.active {
284
+ position: relative;
285
+ z-index: 1;
286
+ background: color-mix(in srgb, var(--primary) 12%, var(--surface));
287
+ border-color: color-mix(in srgb, var(--primary) 36%, transparent);
288
+ box-shadow:
289
+ inset 3px 0 0 var(--primary),
290
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 10%, transparent),
291
+ inset 0 0 10px color-mix(in srgb, var(--glow) 22%, transparent),
292
+ 0 0 0 1px color-mix(in srgb, var(--primary) 14%, transparent),
293
+ 0 0 8px color-mix(in srgb, var(--glow) 36%, transparent),
294
+ 0 0 14px color-mix(in srgb, var(--glow) 18%, transparent);
295
+ }
296
+ .session-item .svc {
297
+ width: 34px;
298
+ height: 34px;
299
+ border-radius: 10px;
300
+ display: grid;
301
+ place-items: center;
302
+ font-size: 15px;
303
+ flex-shrink: 0;
304
+ background: var(--surface-3);
305
+ border: 1px solid var(--border-soft);
306
+ }
307
+ .session-item.active .svc {
308
+ background: var(--grad-from);
309
+ border-color: transparent;
310
+ box-shadow:
311
+ 0 0 0 1px color-mix(in srgb, var(--primary) 35%, transparent),
312
+ 0 0 8px color-mix(in srgb, var(--glow) 55%, transparent),
313
+ 0 0 14px color-mix(in srgb, var(--glow) 24%, transparent);
314
+ }
315
+ /* 群聊外框保持中性底色,选中(激活)时不变色 */
316
+ .session-item.active .svc.svc-group {
317
+ background: var(--surface-3);
318
+ border-color: var(--border-soft);
319
+ }
320
+ /* 单人头像:外框与群聊拼贴外框尺寸一致,选中时同样保持中性底色 */
321
+ .session-item .svc.svc-single {
322
+ position: relative;
323
+ overflow: hidden;
324
+ padding: 2px;
325
+ background: var(--surface-3);
326
+ border-color: var(--border-soft);
327
+ }
328
+ .session-item.active .svc.svc-single {
329
+ background: var(--surface-3);
330
+ border-color: var(--border-soft);
331
+ }
332
+ .session-item .svc.svc-single .avatar {
333
+ width: 100%;
334
+ height: 100%;
335
+ border-radius: 7px;
336
+ box-shadow: none;
337
+ }
338
+ /* 会话头部单人头像(与群聊头部外框 40px 一致) */
339
+ .svc-group-lg.svc-single {
340
+ width: 40px;
341
+ height: 40px;
342
+ padding: 3px;
343
+ border-radius: 12px;
344
+ }
345
+ .svc-group-lg.svc-single .avatar {
346
+ width: 100%;
347
+ height: 100%;
348
+ border-radius: 8px;
349
+ box-shadow: none;
350
+ }
351
+
352
+ /* ---------- 群聊头像拼贴(方框内嵌套至多 4 个头像) ---------- */
353
+ .gcollage {
354
+ display: grid;
355
+ width: 100%;
356
+ height: 100%;
357
+ gap: 1px;
358
+ }
359
+ .gcollage .gslot {
360
+ min-width: 0;
361
+ min-height: 0;
362
+ display: grid;
363
+ place-items: center;
364
+ overflow: hidden;
365
+ container-type: size;
366
+ padding: 1px;
367
+ }
368
+ .gcollage .gslot .avatar {
369
+ width: 100%;
370
+ height: 100%;
371
+ border-radius: 4px;
372
+ box-shadow: none;
373
+ }
374
+ /* 文字头像:字号随格子尺寸缩放(cqw/cqh),撑满格子居中,保证字形完整不被裁切 */
375
+ .gcollage .gslot .avatar:not(:has(img)) {
376
+ line-height: 1;
377
+ font-size: min(72cqw, 72cqh);
378
+ }
379
+ /* 4 位角色:2×2 */
380
+ .gcollage-m4 {
381
+ grid-template-columns: 1fr 1fr;
382
+ grid-template-rows: 1fr 1fr;
383
+ }
384
+ /* 侧边栏会话项的 .svc 是 grid + place-items:center,
385
+ 让拼贴绝对定位于方框内以稳定撑满 34px */
386
+ .session-item .svc.svc-group {
387
+ position: relative;
388
+ overflow: hidden;
389
+ }
390
+ .session-item .svc.svc-group .gcollage {
391
+ position: absolute;
392
+ inset: 2px;
393
+ width: auto;
394
+ height: auto;
395
+ padding: 0;
396
+ gap: 1px;
397
+ }
398
+ .session-item .smeta {
399
+ flex: 1;
400
+ min-width: 0;
401
+ }
402
+ .session-item .stitle {
403
+ font-family: var(--font-serif);
404
+ font-weight: 700;
405
+ font-size: 13.8px;
406
+ white-space: nowrap;
407
+ overflow: visible;
408
+ display: flex;
409
+ align-items: center;
410
+ gap: 4px;
411
+ }
412
+ .session-item .stitle-text {
413
+ overflow: hidden;
414
+ text-overflow: ellipsis;
415
+ white-space: nowrap;
416
+ min-width: 0;
417
+ }
418
+ .session-item .spinned {
419
+ display: inline-flex;
420
+ align-items: center;
421
+ justify-content: center;
422
+ color: var(--primary);
423
+ flex-shrink: 0;
424
+ }
425
+ .session-item .session-activity-dot {
426
+ width: 8px;
427
+ height: 8px;
428
+ flex: 0 0 8px;
429
+ margin-left: auto;
430
+ border-radius: 50%;
431
+ background: #38c976;
432
+ box-shadow: 0 0 0 0 color-mix(in srgb, #38c976 55%, transparent);
433
+ animation: session-activity-breathe 1.8s ease-in-out infinite;
434
+ }
435
+ @keyframes session-activity-breathe {
436
+ 0%, 100% {
437
+ opacity: 0.62;
438
+ box-shadow: 0 0 0 0 color-mix(in srgb, #38c976 50%, transparent);
439
+ }
440
+ 50% {
441
+ opacity: 1;
442
+ box-shadow: 0 0 0 5px color-mix(in srgb, #38c976 0%, transparent);
443
+ }
444
+ }
445
+ @media (prefers-reduced-motion: reduce) {
446
+ .session-item .session-activity-dot {
447
+ animation: none;
448
+ opacity: 1;
449
+ }
450
+ }
451
+ .session-item .sprev {
452
+ font-size: 11.5px;
453
+ color: var(--text-faint);
454
+ white-space: nowrap;
455
+ overflow: hidden;
456
+ text-overflow: ellipsis;
457
+ }
458
+ /* ---------- 会话区域(头部 + 滚动列表) ---------- */
459
+ .session-section {
460
+ flex: 1;
461
+ min-height: 0;
462
+ display: flex;
463
+ flex-direction: column;
464
+ }
465
+
466
+ /* ---------- 会话列表头部(标题 + 新建按钮) ---------- */
467
+ .session-list-head {
468
+ display: flex;
469
+ align-items: center;
470
+ justify-content: space-between;
471
+ gap: 8px;
472
+ /* 左边与列表项头像对齐:session-list padding-left 14 + session-item padding-left 10 */
473
+ /* 右边与列表项右边缘对齐:session-list padding-right 14 + session-item padding-right 10 */
474
+ padding: 4px 24px 8px 24px;
475
+ /* 与侧边栏同色背景,彻底挡住下方滚动的列表项 */
476
+ background: color-mix(in srgb, var(--surface) 55%, transparent);
477
+ backdrop-filter: blur(18px);
478
+ border-bottom: 1px solid var(--border-soft);
479
+ }
480
+ /* 新建按钮:粗体文字 + 加号,不高亮 */
481
+ .session-new-btn {
482
+ display: inline-flex;
483
+ align-items: center;
484
+ gap: 3px;
485
+ border: none;
486
+ background: none;
487
+ color: var(--text);
488
+ font-size: 13px;
489
+ font-weight: 800;
490
+ padding: 4px 8px;
491
+ border-radius: 8px;
492
+ cursor: pointer;
493
+ transition: background 0.15s, color 0.15s;
494
+ }
495
+ .session-new-btn:hover {
496
+ background: var(--surface-2);
497
+ color: var(--primary);
498
+ }
499
+ /* 会话列表头部的按钮组(导入 + 新建) */
500
+ .session-head-actions {
501
+ display: flex;
502
+ align-items: center;
503
+ gap: 2px;
504
+ }
505
+
506
+ /* ---------- 侧边栏底部搜索框 ---------- */
507
+ .sidebar-search {
508
+ position: relative;
509
+ padding: 10px 12px 12px;
510
+ border-top: 1px solid var(--border-soft);
511
+ }
512
+ .search-input {
513
+ width: 100%;
514
+ padding: 7px 30px 7px 12px;
515
+ border-radius: 10px;
516
+ font-size: 12.5px;
517
+ border: 1px solid var(--border);
518
+ background: var(--surface);
519
+ color: var(--text);
520
+ outline: none;
521
+ transition: border-color 0.32s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.32s cubic-bezier(0.16, 1, 0.3, 1);
522
+ }
523
+ .search-input:focus {
524
+ position: relative;
525
+ z-index: 1;
526
+ border-color: color-mix(in srgb, var(--primary) 48%, transparent);
527
+ box-shadow:
528
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 10%, transparent),
529
+ inset 0 0 8px color-mix(in srgb, var(--glow) 16%, transparent),
530
+ 0 0 0 1px color-mix(in srgb, var(--primary) 20%, transparent),
531
+ 0 0 8px color-mix(in srgb, var(--glow) 34%, transparent),
532
+ 0 0 14px color-mix(in srgb, var(--glow) 12%, transparent);
533
+ }
534
+ .search-clear {
535
+ position: absolute;
536
+ right: 18px;
537
+ top: 50%;
538
+ transform: translateY(-50%);
539
+ width: 22px;
540
+ height: 22px;
541
+ border: none;
542
+ border-radius: 50%;
543
+ background: var(--surface-2);
544
+ color: var(--text-dim);
545
+ display: grid;
546
+ place-items: center;
547
+ cursor: pointer;
548
+ }
549
+ .search-clear:hover {
550
+ color: var(--danger);
551
+ background: var(--danger-soft);
552
+ }
553
+
554
+ /* ---------- 通用按钮 ---------- */
555
+ .btn {
556
+ display: inline-flex;
557
+ align-items: center;
558
+ justify-content: center;
559
+ gap: 7px;
560
+ padding: 8px 15px;
561
+ border-radius: 10px;
562
+ border: 1px solid var(--border);
563
+ background: color-mix(in srgb, var(--surface) 80%, transparent);
564
+ color: var(--text);
565
+ font-size: 13.2px;
566
+ font-weight: 600;
567
+ text-decoration: none;
568
+ transition: all 0.32s cubic-bezier(0.16, 1, 0.3, 1);
569
+ user-select: none;
570
+ }
571
+ .btn:hover {
572
+ position: relative;
573
+ z-index: 1;
574
+ border-color: color-mix(in srgb, var(--primary) 26%, transparent);
575
+ background: color-mix(in srgb, var(--primary) 7%, transparent);
576
+ /* 悬停仅轻微高亮;辉光保留给激活/选中状态 */
577
+ }
578
+ .btn:active {
579
+ transform: translateY(0);
580
+ }
581
+ .btn:disabled {
582
+ opacity: 0.5;
583
+ cursor: not-allowed;
584
+ transform: none;
585
+ }
586
+ .btn-primary {
587
+ background: var(--grad-from);
588
+ border-color: transparent;
589
+ color: #fff;
590
+ box-shadow:
591
+ 0 0 0 1px color-mix(in srgb, var(--primary) 40%, transparent),
592
+ 0 4px 18px color-mix(in srgb, var(--glow) 60%, transparent),
593
+ 0 12px 40px color-mix(in srgb, var(--glow) 28%, transparent);
594
+ }
595
+ .btn-primary:hover {
596
+ /* 主按钮常驻辉光;悬停仅轻微提亮,不再增强辉光 */
597
+ filter: brightness(1.05);
598
+ background: var(--grad-from);
599
+ color: #fff;
600
+ }
601
+ .btn-danger {
602
+ color: var(--danger);
603
+ border-color: color-mix(in srgb, var(--danger) 30%, transparent);
604
+ background: var(--danger-soft);
605
+ }
606
+ .btn-danger:hover {
607
+ background: color-mix(in srgb, var(--danger) 16%, transparent);
608
+ }
609
+ .btn-ghost {
610
+ background: transparent;
611
+ border-color: transparent;
612
+ color: var(--text-dim);
613
+ padding: 6px 10px;
614
+ }
615
+ .btn-ghost:hover {
616
+ background: var(--surface-2);
617
+ color: var(--text);
618
+ }
619
+ .btn-sm {
620
+ padding: 4px 10px;
621
+ font-size: 12px;
622
+ border-radius: 8px;
623
+ }
624
+ .icon-btn {
625
+ display: inline-flex;
626
+ align-items: center;
627
+ justify-content: center;
628
+ width: 30px;
629
+ height: 30px;
630
+ border: none;
631
+ border-radius: 8px;
632
+ background: transparent;
633
+ color: var(--text-dim);
634
+ font-size: 14px;
635
+ transition: background 0.15s ease, color 0.15s ease;
636
+ }
637
+ .icon-btn:hover {
638
+ background: var(--surface-2);
639
+ color: var(--text);
640
+ }
641
+
642
+ /* ---------- 输入控件 ---------- */
643
+ .field {
644
+ display: flex;
645
+ flex-direction: column;
646
+ gap: 5px;
647
+ min-width: 0;
648
+ }
649
+ .field > label {
650
+ font-size: 12px;
651
+ font-weight: 700;
652
+ color: var(--text-dim);
653
+ letter-spacing: 0.3px;
654
+ text-transform: uppercase;
655
+ }
656
+ .input,
657
+ .textarea,
658
+ .select {
659
+ width: 100%;
660
+ padding: 9px 12px;
661
+ border-radius: 10px;
662
+ border: 1px solid var(--border);
663
+ background: var(--surface);
664
+ color: var(--text);
665
+ font-size: 13.5px;
666
+ transition: border-color 0.32s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.32s cubic-bezier(0.16, 1, 0.3, 1);
667
+ outline: none;
668
+ }
669
+ .input:focus,
670
+ .textarea:focus,
671
+ .select:focus {
672
+ position: relative;
673
+ z-index: 1;
674
+ border-color: color-mix(in srgb, var(--primary) 52%, transparent);
675
+ box-shadow:
676
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 10%, transparent),
677
+ inset 0 0 8px color-mix(in srgb, var(--glow) 18%, transparent),
678
+ 0 0 0 1px color-mix(in srgb, var(--primary) 22%, transparent),
679
+ 0 0 8px color-mix(in srgb, var(--glow) 36%, transparent),
680
+ 0 0 14px color-mix(in srgb, var(--glow) 14%, transparent);
681
+ }
682
+ .textarea {
683
+ resize: vertical;
684
+ min-height: 76px;
685
+ line-height: 1.55;
686
+ }
687
+ .input::placeholder,
688
+ .textarea::placeholder {
689
+ color: var(--text-faint);
690
+ }
691
+ .select {
692
+ appearance: none;
693
+ background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
694
+ linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
695
+ background-position: calc(100% - 16px) 55%, calc(100% - 11px) 55%;
696
+ background-size: 5px 5px;
697
+ background-repeat: no-repeat;
698
+ padding-right: 28px;
699
+ cursor: pointer;
700
+ }
701
+ .check {
702
+ display: flex;
703
+ align-items: center;
704
+ gap: 8px;
705
+ cursor: pointer;
706
+ font-size: 13.5px;
707
+ user-select: none;
708
+ }
709
+ .check input {
710
+ accent-color: var(--primary);
711
+ width: 16px;
712
+ height: 16px;
713
+ cursor: pointer;
714
+ }
715
+
716
+ .tag {
717
+ display: inline-flex;
718
+ align-items: center;
719
+ gap: 5px;
720
+ padding: 2px 9px;
721
+ border-radius: 999px;
722
+ background: var(--tag-bg);
723
+ color: var(--tag-text);
724
+ font-size: 11.5px;
725
+ font-weight: 700;
726
+ }
727
+
728
+ .card {
729
+ border: 1px solid var(--border);
730
+ background: var(--surface);
731
+ border-radius: var(--radius);
732
+ box-shadow: var(--shadow);
733
+ }
734
+
735
+ /* ---------- 主区域 ---------- */
736
+ .main-area {
737
+ display: flex;
738
+ flex-direction: column;
739
+ min-width: 0;
740
+ height: 100dvh;
741
+ position: relative;
742
+ }
743
+
744
+ .chat-scroll {
745
+ flex: 1;
746
+ overflow-y: auto;
747
+ padding: 20px 0 10px;
748
+ /* 与聊天头部 / 输入区同一块磨砂玻璃面:统一底色,消除可见的色块分界线 */
749
+ background: color-mix(in srgb, var(--surface) 62%, transparent);
750
+ backdrop-filter: blur(16px);
751
+ -webkit-backdrop-filter: blur(16px);
752
+ }
753
+
754
+ .chat-col {
755
+ max-width: 820px;
756
+ margin: 0 auto;
757
+ padding: 0 22px;
758
+ display: flex;
759
+ flex-direction: column;
760
+ gap: 16px;
761
+ }
762
+
763
+ /* ---------- 聊天头部 ---------- */
764
+ .chat-header {
765
+ display: flex;
766
+ align-items: center;
767
+ gap: 12px;
768
+ padding: 12px 22px;
769
+ border-bottom: 1px solid var(--border);
770
+ background: color-mix(in srgb, var(--surface) 62%, transparent);
771
+ backdrop-filter: blur(16px);
772
+ z-index: 5;
773
+ }
774
+ .avatar {
775
+ width: 40px;
776
+ height: 40px;
777
+ border-radius: 12px;
778
+ display: grid;
779
+ place-items: center;
780
+ font-weight: 700;
781
+ font-size: 17px;
782
+ font-family: var(--font-serif);
783
+ color: #fff;
784
+ flex-shrink: 0;
785
+ background: var(--grad-from);
786
+ box-shadow: 0 3px 12px var(--glow);
787
+ overflow: hidden;
788
+ }
789
+ .avatar img {
790
+ width: 100%;
791
+ height: 100%;
792
+ object-fit: cover;
793
+ }
794
+ .avatar.sm {
795
+ width: 32px;
796
+ height: 32px;
797
+ font-size: 14px;
798
+ border-radius: 10px;
799
+ }
800
+ .avatar.xs {
801
+ width: 26px;
802
+ height: 26px;
803
+ font-size: 12px;
804
+ border-radius: 8px;
805
+ box-shadow: none;
806
+ }
807
+
808
+ /* 会话头部大号群聊拼贴(外框与侧边栏统一:圆角矩形 + 中性底色 + 边框) */
809
+ .svc-group-lg {
810
+ width: 40px;
811
+ height: 40px;
812
+ border-radius: 12px;
813
+ font-size: 17px;
814
+ display: grid;
815
+ place-items: center;
816
+ flex-shrink: 0;
817
+ background: var(--surface-3);
818
+ border: 1px solid var(--border-soft);
819
+ overflow: hidden;
820
+ box-shadow: 0 3px 12px var(--glow);
821
+ }
822
+ .svc-group .gcollage {
823
+ padding: 1px;
824
+ gap: 1px;
825
+ }
826
+
827
+ /* 聊天头部:内边距加宽,让内部头像避开外框圆角(12px)的圆弧遮挡 */
828
+ .chat-header .svc-group-lg .gcollage {
829
+ padding: 3px;
830
+ gap: 2px;
831
+ }
832
+ .chat-header .tinfo {
833
+ flex: 1;
834
+ min-width: 0;
835
+ }
836
+ .chat-header .ttitle {
837
+ font-family: var(--font-serif);
838
+ font-weight: 700;
839
+ font-size: 17px;
840
+ white-space: nowrap;
841
+ overflow: hidden;
842
+ text-overflow: ellipsis;
843
+ }
844
+ .chat-header .tsub {
845
+ font-size: 12px;
846
+ color: var(--text-faint);
847
+ white-space: nowrap;
848
+ overflow: hidden;
849
+ text-overflow: ellipsis;
850
+ display: flex;
851
+ align-items: center;
852
+ gap: 8px;
853
+ }
854
+ .chat-header .tsub-members {
855
+ overflow: hidden;
856
+ text-overflow: ellipsis;
857
+ white-space: nowrap;
858
+ }
859
+ .chat-header .tsub-mode {
860
+ flex-shrink: 0;
861
+ font-weight: 700;
862
+ color: var(--primary);
863
+ }
864
+
865
+ /* ---------- 聊天头部右侧动作按钮 ---------- */
866
+ .chat-actions {
867
+ display: flex;
868
+ align-items: center;
869
+ gap: 4px;
870
+ }
871
+ .chat-actions .icon-tooltip {
872
+ display: inline-flex;
873
+ align-items: center;
874
+ justify-content: center;
875
+ width: 34px;
876
+ height: 34px;
877
+ border: none;
878
+ border-radius: 9px;
879
+ background: transparent;
880
+ color: var(--text-dim);
881
+ transition: background 0.15s ease, color 0.15s ease;
882
+ cursor: pointer;
883
+ }
884
+ .chat-actions .icon-tooltip:hover {
885
+ background: var(--surface-2);
886
+ color: var(--text);
887
+ }
888
+ .chat-actions .icon-tooltip.danger:hover {
889
+ color: var(--danger);
890
+ background: var(--danger-soft);
891
+ }
892
+
893
+ /* ---------- 卡片/仪表盘 ---------- */
894
+ .dash-scroll {
895
+ flex: 1;
896
+ overflow-y: auto;
897
+ padding: 26px;
898
+ }
899
+ .dash {
900
+ max-width: 960px;
901
+ margin: 0 auto;
902
+ display: flex;
903
+ flex-direction: column;
904
+ gap: 18px;
905
+ }
906
+ .dash-hero {
907
+ position: relative;
908
+ padding: 34px 34px 30px;
909
+ border-radius: var(--radius-lg);
910
+ overflow: hidden;
911
+ border: 1px solid color-mix(in srgb, var(--primary) 26%, var(--border));
912
+ background: color-mix(in srgb, var(--primary) 7%, var(--surface));
913
+ box-shadow: 0 22px 60px color-mix(in srgb, var(--glow) 22%, transparent);
914
+ }
915
+ .dash-hero::before {
916
+ content: '';
917
+ position: absolute;
918
+ inset: -40% -20%;
919
+ background: radial-gradient(ellipse at 50% -20%, color-mix(in srgb, var(--glow) 42%, transparent), transparent 65%);
920
+ pointer-events: none;
921
+ }
922
+ .dash-hero h1 {
923
+ position: relative;
924
+ margin: 0 0 8px;
925
+ font-family: var(--font-serif);
926
+ font-size: 32px;
927
+ font-weight: 700;
928
+ letter-spacing: 0.2px;
929
+ }
930
+ .dash-hero p {
931
+ position: relative;
932
+ margin: 0 0 20px;
933
+ color: var(--text-dim);
934
+ max-width: 720px;
935
+ white-space: pre-line;
936
+ line-height: 1.7;
937
+ }
938
+ /* 欢迎语中的强调句:衬线 + 琥珀渐变 + 微放大,克制而醒目 */
939
+ .dash-hero-accent {
940
+ display: block;
941
+ margin-top: 6px;
942
+ font-family: var(--font-serif);
943
+ font-size: 1.22em;
944
+ font-weight: 600;
945
+ letter-spacing: 0.3px;
946
+ color: var(--primary);
947
+ background: linear-gradient(
948
+ 100deg,
949
+ color-mix(in srgb, var(--primary) 88%, #fff) 0%,
950
+ var(--primary) 42%,
951
+ color-mix(in srgb, var(--primary) 70%, #ffd9a8) 100%
952
+ );
953
+ -webkit-background-clip: text;
954
+ background-clip: text;
955
+ -webkit-text-fill-color: transparent;
956
+ color: var(--primary);
957
+ filter: drop-shadow(0 1px 0 color-mix(in srgb, var(--bg) 40%, transparent));
958
+ }
959
+ .dash-hero .actions {
960
+ position: relative;
961
+ display: flex;
962
+ gap: 10px;
963
+ flex-wrap: wrap;
964
+ }
965
+ /* ---------- 与角色继续对话(最近会话列表) ---------- */
966
+ .recent-list {
967
+ display: flex;
968
+ flex-direction: column;
969
+ gap: 8px;
970
+ }
971
+ .recent-item {
972
+ display: flex;
973
+ align-items: center;
974
+ gap: 12px;
975
+ width: 100%;
976
+ text-align: left;
977
+ padding: 10px 12px;
978
+ border: 1px solid var(--border);
979
+ border-radius: var(--radius);
980
+ background: var(--surface);
981
+ color: var(--text);
982
+ cursor: pointer;
983
+ transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
984
+ }
985
+ .recent-item:hover {
986
+ border-color: color-mix(in srgb, var(--primary) 32%, transparent);
987
+ background: color-mix(in srgb, var(--primary) 4%, var(--surface));
988
+ box-shadow: 0 10px 26px color-mix(in srgb, var(--glow) 18%, transparent);
989
+ }
990
+ .recent-item .svc {
991
+ width: 40px;
992
+ height: 40px;
993
+ border-radius: 12px;
994
+ flex-shrink: 0;
995
+ }
996
+ .recent-item .svc.svc-single {
997
+ padding: 3px;
998
+ }
999
+ .recent-item .svc.svc-single .avatar {
1000
+ width: 100%;
1001
+ height: 100%;
1002
+ border-radius: 8px;
1003
+ box-shadow: none;
1004
+ }
1005
+ .recent-item .svc .gcollage {
1006
+ padding: 2px;
1007
+ }
1008
+ .recent-meta {
1009
+ flex: 1;
1010
+ min-width: 0;
1011
+ }
1012
+ .recent-title {
1013
+ display: flex;
1014
+ align-items: baseline;
1015
+ gap: 8px;
1016
+ }
1017
+ .recent-title-text {
1018
+ font-family: var(--font-serif);
1019
+ font-weight: 700;
1020
+ font-size: 14px;
1021
+ white-space: nowrap;
1022
+ overflow: hidden;
1023
+ text-overflow: ellipsis;
1024
+ min-width: 0;
1025
+ }
1026
+ .recent-time {
1027
+ flex-shrink: 0;
1028
+ font-size: 11px;
1029
+ color: var(--text-faint);
1030
+ white-space: nowrap;
1031
+ }
1032
+ .recent-preview {
1033
+ font-size: 12px;
1034
+ color: var(--text-dim);
1035
+ white-space: nowrap;
1036
+ overflow: hidden;
1037
+ text-overflow: ellipsis;
1038
+ margin-top: 2px;
1039
+ }
1040
+ .recent-go {
1041
+ display: inline-flex;
1042
+ align-items: center;
1043
+ justify-content: center;
1044
+ width: 28px;
1045
+ height: 28px;
1046
+ border-radius: 8px;
1047
+ color: var(--text-faint);
1048
+ background: var(--surface-2);
1049
+ flex-shrink: 0;
1050
+ transition: color 0.18s ease, background 0.18s ease;
1051
+ }
1052
+ .recent-item:hover .recent-go {
1053
+ color: var(--primary);
1054
+ background: var(--primary-soft);
1055
+ }
1056
+
1057
+ .stat-grid {
1058
+ display: grid;
1059
+ grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
1060
+ gap: 14px;
1061
+ }
1062
+ .stat-card {
1063
+ padding: 16px 18px;
1064
+ border-radius: var(--radius);
1065
+ border: 1px solid var(--border);
1066
+ background: var(--surface);
1067
+ }
1068
+ .stat-card .v {
1069
+ font-family: var(--font-serif);
1070
+ font-size: 27px;
1071
+ font-weight: 700;
1072
+ background: var(--grad-from);
1073
+ -webkit-background-clip: text;
1074
+ background-clip: text;
1075
+ -webkit-text-fill-color: transparent;
1076
+ line-height: 1.2;
1077
+ }
1078
+ .stat-card .k {
1079
+ font-size: 12px;
1080
+ color: var(--text-faint);
1081
+ font-weight: 600;
1082
+ letter-spacing: 0.4px;
1083
+ }
1084
+ .section-title {
1085
+ font-family: var(--font-serif);
1086
+ font-size: 15.5px;
1087
+ font-weight: 700;
1088
+ color: var(--text);
1089
+ letter-spacing: 0.4px;
1090
+ margin: 6px 0 10px;
1091
+ display: flex;
1092
+ align-items: center;
1093
+ gap: 8px;
1094
+ }
1095
+ .section-title::after {
1096
+ content: '';
1097
+ flex: 1;
1098
+ max-width: 120px;
1099
+ height: 1px;
1100
+ background: linear-gradient(to right, var(--border), transparent);
1101
+ }
1102
+
1103
+ /* ---------- 彩色首字母(低饱和度暖色调,与暖核桃木夜风格统一) ---------- */
1104
+ .avatar-hue-0 { background: #C27063; } /* 红葡萄酒 */
1105
+ .avatar-hue-1 { background: #D9A673; } /* 琥珀 */
1106
+ .avatar-hue-2 { background: #6FAE80; } /* 苔藓绿 */
1107
+ .avatar-hue-3 { background: #6F9FB8; } /* 烛光湖蓝 */
1108
+ .avatar-hue-4 { background: #A98DB5; } /* 暖紫藤 */
1109
+ .avatar-hue-5 { background: #7BA89F; } /* 雾青 */
1110
+
1111
+ /* ---------- 空状态 ---------- */
1112
+ .empty-state {
1113
+ display: flex;
1114
+ flex-direction: column;
1115
+ align-items: center;
1116
+ justify-content: center;
1117
+ gap: 8px;
1118
+ padding: 60px 20px;
1119
+ color: var(--text-faint);
1120
+ text-align: center;
1121
+ }
1122
+ .empty-state .big {
1123
+ font-size: 44px;
1124
+ filter: grayscale(0.15) brightness(1.05);
1125
+ opacity: 0.9;
1126
+ }
1127
+
1128
+ /* ---------- 成就解锁庆祝(奖杯 + 撒花) ---------- */
1129
+ .ach-overlay {
1130
+ position: fixed;
1131
+ inset: 0;
1132
+ background: rgba(8, 6, 16, 0.5);
1133
+ backdrop-filter: blur(3px);
1134
+ z-index: 1400;
1135
+ animation: fadeIn 0.2s ease;
1136
+ }
1137
+ .ach-celebration {
1138
+ position: fixed;
1139
+ inset: 0;
1140
+ z-index: 1401;
1141
+ display: grid;
1142
+ place-items: center;
1143
+ pointer-events: none;
1144
+ perspective: 800px;
1145
+ }
1146
+ .ach-celebration .ach-trophy,
1147
+ .ach-celebration .ach-caption {
1148
+ pointer-events: auto;
1149
+ }
1150
+ .ach-rain {
1151
+ position: absolute;
1152
+ inset: 0;
1153
+ overflow: hidden;
1154
+ pointer-events: none;
1155
+ }
1156
+ .ach-rain i {
1157
+ position: absolute;
1158
+ top: -24px;
1159
+ border-radius: 2px;
1160
+ opacity: 0;
1161
+ animation: confettiFall linear infinite;
1162
+ }
1163
+ .ach-rain i.round {
1164
+ border-radius: 50%;
1165
+ }
1166
+ @keyframes confettiFall {
1167
+ 0% {
1168
+ transform: translate3d(0, -4vh, 0) rotate(0deg);
1169
+ opacity: 1;
1170
+ }
1171
+ 85% {
1172
+ opacity: 1;
1173
+ }
1174
+ 100% {
1175
+ transform: translate3d(var(--drift, 0px), 108vh, 0) rotate(var(--rot, 520deg));
1176
+ opacity: 0;
1177
+ }
1178
+ }
1179
+ .ach-trophy {
1180
+ position: relative;
1181
+ width: 150px;
1182
+ height: 150px;
1183
+ display: grid;
1184
+ place-items: center;
1185
+ animation: trophyIn 0.9s cubic-bezier(0.18, 0.9, 0.28, 1.35) both;
1186
+ }
1187
+ .ach-trophy-badge {
1188
+ position: relative;
1189
+ z-index: 1;
1190
+ width: 128px;
1191
+ height: 128px;
1192
+ display: grid;
1193
+ place-items: center;
1194
+ font-size: 64px;
1195
+ border-radius: 50%;
1196
+ background: linear-gradient(160deg, #ffe9a8, #f5c24d 55%, #d99a2b);
1197
+ box-shadow:
1198
+ 0 0 0 6px rgba(255, 233, 168, 0.25),
1199
+ 0 0 0 12px rgba(255, 233, 168, 0.12),
1200
+ 0 18px 60px rgba(240, 180, 60, 0.55);
1201
+ }
1202
+ .ach-trophy-glow {
1203
+ position: absolute;
1204
+ inset: -34px;
1205
+ border-radius: 50%;
1206
+ background: radial-gradient(circle, rgba(255, 210, 110, 0.55), transparent 62%);
1207
+ animation: trophyGlow 2.2s ease-in-out infinite;
1208
+ }
1209
+ @keyframes trophyIn {
1210
+ 0% {
1211
+ opacity: 0;
1212
+ transform: translateY(46px) scale(0.3) rotate(-12deg);
1213
+ }
1214
+ 100% {
1215
+ opacity: 1;
1216
+ transform: translateY(0) scale(1) rotate(0deg);
1217
+ }
1218
+ }
1219
+ @keyframes trophyGlow {
1220
+ 0%,
1221
+ 100% {
1222
+ opacity: 0.65;
1223
+ transform: scale(1);
1224
+ }
1225
+ 50% {
1226
+ opacity: 1;
1227
+ transform: scale(1.12);
1228
+ }
1229
+ }
1230
+ .ach-caption {
1231
+ position: absolute;
1232
+ bottom: clamp(56px, 15vh, 140px);
1233
+ left: 50%;
1234
+ transform: translateX(-50%);
1235
+ display: flex;
1236
+ flex-direction: column;
1237
+ align-items: center;
1238
+ gap: 8px;
1239
+ text-align: center;
1240
+ animation: captionIn 0.7s 0.35s ease both;
1241
+ width: min(420px, calc(100vw - 48px));
1242
+ }
1243
+ @keyframes captionIn {
1244
+ from {
1245
+ opacity: 0;
1246
+ transform: translate(-50%, 22px);
1247
+ }
1248
+ }
1249
+ .ach-caption-title {
1250
+ font-size: 13px;
1251
+ font-weight: 800;
1252
+ letter-spacing: 3px;
1253
+ color: #ffd166;
1254
+ text-shadow: 0 0 18px rgba(255, 209, 102, 0.5);
1255
+ }
1256
+ .ach-caption-name {
1257
+ font-size: 30px;
1258
+ font-weight: 900;
1259
+ color: var(--text);
1260
+ text-shadow: 0 2px 22px rgba(0, 0, 0, 0.35);
1261
+ }
1262
+ .ach-caption-desc {
1263
+ font-size: 13px;
1264
+ color: var(--text-dim);
1265
+ line-height: 1.6;
1266
+ }
1267
+ .ach-caption-total {
1268
+ font-size: 11.5px;
1269
+ color: var(--text-faint);
1270
+ font-family: var(--font-mono);
1271
+ }
1272
+ .ach-celebration .ach-trophy.leave,
1273
+ .ach-celebration .ach-caption.leave {
1274
+ transition: all 0.26s ease;
1275
+ opacity: 0;
1276
+ transform: translateY(26px) scale(0.94);
1277
+ }
1278
+
1279
+ /* ---------- Toast ---------- */
1280
+ .toast-wrap {
1281
+ position: fixed;
1282
+ bottom: 26px;
1283
+ left: 50%;
1284
+ transform: translateX(-50%);
1285
+ z-index: 9999;
1286
+ display: flex;
1287
+ flex-direction: column;
1288
+ gap: 8px;
1289
+ align-items: center;
1290
+ pointer-events: none;
1291
+ }
1292
+ .toast {
1293
+ padding: 10px 18px;
1294
+ border-radius: 999px;
1295
+ background: var(--surface);
1296
+ border: 1px solid var(--border);
1297
+ box-shadow: var(--shadow-lg);
1298
+ font-size: 13px;
1299
+ font-weight: 600;
1300
+ color: var(--text);
1301
+ animation: toastIn 0.22s cubic-bezier(0.2, 0.9, 0.3, 1.2);
1302
+ max-width: 70vw;
1303
+ }
1304
+ .toast.err {
1305
+ border-color: color-mix(in srgb, var(--danger) 40%, transparent);
1306
+ color: var(--danger);
1307
+ }
1308
+ @keyframes toastIn {
1309
+ from {
1310
+ opacity: 0;
1311
+ transform: translateY(10px) scale(0.96);
1312
+ }
1313
+ }
1314
+
1315
+ /* ---------- 动画 ---------- */
1316
+ @keyframes pulse {
1317
+ 0%,
1318
+ 100% {
1319
+ opacity: 0.35;
1320
+ }
1321
+ 50% {
1322
+ opacity: 1;
1323
+ }
1324
+ }
1325
+ @keyframes spin {
1326
+ to {
1327
+ transform: rotate(360deg);
1328
+ }
1329
+ }
1330
+ @keyframes fadeUp {
1331
+ from {
1332
+ opacity: 0;
1333
+ transform: translateY(8px);
1334
+ }
1335
+ to {
1336
+ opacity: 1;
1337
+ transform: translateY(0);
1338
+ }
1339
+ }
1340
+ .fade-up {
1341
+ animation: fadeUp 0.25s ease both;
1342
+ }
1343
+
1344
+ .spinner {
1345
+ width: 16px;
1346
+ height: 16px;
1347
+ border: 2px solid var(--border);
1348
+ border-top-color: var(--primary);
1349
+ border-radius: 50%;
1350
+ animation: spin 0.7s linear infinite;
1351
+ flex-shrink: 0;
1352
+ }
1353
+
1354
+ .table {
1355
+ width: 100%;
1356
+ border-collapse: collapse;
1357
+ font-size: 13px;
1358
+ }
1359
+ .table th,
1360
+ .table td {
1361
+ padding: 7px 10px;
1362
+ border-bottom: 1px solid var(--border-soft);
1363
+ text-align: left;
1364
+ }
1365
+ .table th {
1366
+ color: var(--text-faint);
1367
+ font-weight: 700;
1368
+ font-size: 12px;
1369
+ text-transform: uppercase;
1370
+ letter-spacing: 0.4px;
1371
+ }
1372
+
1373
+ hr.sep {
1374
+ border: none;
1375
+ border-top: 1px solid var(--border);
1376
+ margin: 14px 0;
1377
+ }