@virtengine/openfleet 0.25.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 (120) hide show
  1. package/.env.example +914 -0
  2. package/LICENSE +190 -0
  3. package/README.md +500 -0
  4. package/agent-endpoint.mjs +918 -0
  5. package/agent-hook-bridge.mjs +230 -0
  6. package/agent-hooks.mjs +1188 -0
  7. package/agent-pool.mjs +2403 -0
  8. package/agent-prompts.mjs +689 -0
  9. package/agent-sdk.mjs +141 -0
  10. package/anomaly-detector.mjs +1195 -0
  11. package/autofix.mjs +1294 -0
  12. package/claude-shell.mjs +708 -0
  13. package/cli.mjs +906 -0
  14. package/codex-config.mjs +1274 -0
  15. package/codex-model-profiles.mjs +135 -0
  16. package/codex-shell.mjs +762 -0
  17. package/config-doctor.mjs +613 -0
  18. package/config.mjs +1720 -0
  19. package/conflict-resolver.mjs +248 -0
  20. package/container-runner.mjs +450 -0
  21. package/copilot-shell.mjs +827 -0
  22. package/daemon-restart-policy.mjs +56 -0
  23. package/diff-stats.mjs +282 -0
  24. package/error-detector.mjs +829 -0
  25. package/fetch-runtime.mjs +34 -0
  26. package/fleet-coordinator.mjs +838 -0
  27. package/get-telegram-chat-id.mjs +71 -0
  28. package/git-safety.mjs +170 -0
  29. package/github-reconciler.mjs +403 -0
  30. package/hook-profiles.mjs +651 -0
  31. package/kanban-adapter.mjs +4491 -0
  32. package/lib/logger.mjs +645 -0
  33. package/maintenance.mjs +828 -0
  34. package/merge-strategy.mjs +1171 -0
  35. package/monitor.mjs +12207 -0
  36. package/openfleet.config.example.json +115 -0
  37. package/openfleet.schema.json +465 -0
  38. package/package.json +203 -0
  39. package/postinstall.mjs +187 -0
  40. package/pr-cleanup-daemon.mjs +978 -0
  41. package/preflight.mjs +408 -0
  42. package/prepublish-check.mjs +90 -0
  43. package/presence.mjs +328 -0
  44. package/primary-agent.mjs +282 -0
  45. package/publish.mjs +151 -0
  46. package/repo-root.mjs +29 -0
  47. package/restart-controller.mjs +100 -0
  48. package/review-agent.mjs +557 -0
  49. package/rotate-agent-logs.sh +133 -0
  50. package/sdk-conflict-resolver.mjs +973 -0
  51. package/session-tracker.mjs +880 -0
  52. package/setup.mjs +3937 -0
  53. package/shared-knowledge.mjs +410 -0
  54. package/shared-state-manager.mjs +841 -0
  55. package/shared-workspace-cli.mjs +199 -0
  56. package/shared-workspace-registry.mjs +537 -0
  57. package/shared-workspaces.json +18 -0
  58. package/startup-service.mjs +1070 -0
  59. package/sync-engine.mjs +1063 -0
  60. package/task-archiver.mjs +801 -0
  61. package/task-assessment.mjs +550 -0
  62. package/task-claims.mjs +924 -0
  63. package/task-complexity.mjs +581 -0
  64. package/task-executor.mjs +5111 -0
  65. package/task-store.mjs +753 -0
  66. package/telegram-bot.mjs +9281 -0
  67. package/telegram-sentinel.mjs +2010 -0
  68. package/ui/app.js +867 -0
  69. package/ui/app.legacy.js +1464 -0
  70. package/ui/app.monolith.js +2488 -0
  71. package/ui/components/charts.js +226 -0
  72. package/ui/components/chat-view.js +567 -0
  73. package/ui/components/command-palette.js +587 -0
  74. package/ui/components/diff-viewer.js +190 -0
  75. package/ui/components/forms.js +327 -0
  76. package/ui/components/kanban-board.js +451 -0
  77. package/ui/components/session-list.js +305 -0
  78. package/ui/components/shared.js +473 -0
  79. package/ui/index.html +70 -0
  80. package/ui/modules/api.js +297 -0
  81. package/ui/modules/icons.js +461 -0
  82. package/ui/modules/router.js +81 -0
  83. package/ui/modules/settings-schema.js +261 -0
  84. package/ui/modules/state.js +679 -0
  85. package/ui/modules/telegram.js +331 -0
  86. package/ui/modules/utils.js +270 -0
  87. package/ui/styles/animations.css +140 -0
  88. package/ui/styles/base.css +98 -0
  89. package/ui/styles/components.css +1915 -0
  90. package/ui/styles/kanban.css +286 -0
  91. package/ui/styles/layout.css +809 -0
  92. package/ui/styles/sessions.css +827 -0
  93. package/ui/styles/variables.css +188 -0
  94. package/ui/styles.css +141 -0
  95. package/ui/styles.monolith.css +1046 -0
  96. package/ui/tabs/agents.js +1417 -0
  97. package/ui/tabs/chat.js +74 -0
  98. package/ui/tabs/control.js +887 -0
  99. package/ui/tabs/dashboard.js +515 -0
  100. package/ui/tabs/infra.js +537 -0
  101. package/ui/tabs/logs.js +783 -0
  102. package/ui/tabs/settings.js +1487 -0
  103. package/ui/tabs/tasks.js +1385 -0
  104. package/ui-server.mjs +4073 -0
  105. package/update-check.mjs +465 -0
  106. package/utils.mjs +172 -0
  107. package/ve-kanban.mjs +654 -0
  108. package/ve-kanban.ps1 +1365 -0
  109. package/ve-kanban.sh +18 -0
  110. package/ve-orchestrator.mjs +340 -0
  111. package/ve-orchestrator.ps1 +6546 -0
  112. package/ve-orchestrator.sh +18 -0
  113. package/vibe-kanban-wrapper.mjs +41 -0
  114. package/vk-error-resolver.mjs +470 -0
  115. package/vk-log-stream.mjs +914 -0
  116. package/whatsapp-channel.mjs +520 -0
  117. package/workspace-monitor.mjs +581 -0
  118. package/workspace-reaper.mjs +405 -0
  119. package/workspace-registry.mjs +238 -0
  120. package/worktree-manager.mjs +1266 -0
@@ -0,0 +1,1046 @@
1
+ /* ─── VirtEngine Control Center ─── */
2
+ /* Dark-first Telegram Mini App theme */
3
+
4
+ :root {
5
+ /* Base palette (dark mode) */
6
+ --bg-primary: #1a1a2e;
7
+ --bg-secondary: #16213e;
8
+ --bg-card: #1e2746;
9
+ --bg-card-hover: #253156;
10
+ --bg-surface: #0f0f23;
11
+ --bg-input: #1b2240;
12
+ --text-primary: #e8eaf0;
13
+ --text-secondary: #9ca3b8;
14
+ --text-hint: #6b7394;
15
+ --text-inverse: #1a1a2e;
16
+ --accent: #6c5ce7;
17
+ --accent-hover: #7d6ff0;
18
+ --accent-text: #ffffff;
19
+ --border: rgba(255, 255, 255, 0.08);
20
+ --border-strong: rgba(255, 255, 255, 0.15);
21
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.25);
22
+ --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.35);
23
+ --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.45);
24
+ --radius-sm: 8px;
25
+ --radius-md: 12px;
26
+ --radius-lg: 16px;
27
+ --radius-xl: 20px;
28
+ --radius-full: 999px;
29
+ --nav-height: 56px;
30
+ --header-height: 52px;
31
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
32
+
33
+ /* Status colors */
34
+ --color-todo: #8b95a2;
35
+ --color-inprogress: #3b82f6;
36
+ --color-inreview: #f59e0b;
37
+ --color-done: #22c55e;
38
+ --color-error: #ef4444;
39
+ --color-cancelled: #6b7280;
40
+
41
+ /* Priority colors */
42
+ --color-critical: #ef4444;
43
+ --color-high: #f59e0b;
44
+ --color-medium: #3b82f6;
45
+ --color-low: #8b95a2;
46
+
47
+ /* Gradient accents */
48
+ --gradient-accent: linear-gradient(135deg, #6c5ce7, #a855f7);
49
+ --gradient-success: linear-gradient(135deg, #22c55e, #16a34a);
50
+ --gradient-progress: linear-gradient(90deg, #6c5ce7, #a855f7, #ec4899);
51
+
52
+ /* Backdrop */
53
+ --backdrop: rgba(0, 0, 0, 0.6);
54
+ --backdrop-blur: blur(12px);
55
+ }
56
+
57
+ /* Light mode overrides */
58
+ @media (prefers-color-scheme: light) {
59
+ :root:not([data-tg-theme]) {
60
+ --bg-primary: #f0f2f5;
61
+ --bg-secondary: #ffffff;
62
+ --bg-card: #ffffff;
63
+ --bg-card-hover: #f5f7fa;
64
+ --bg-surface: #e8eaf0;
65
+ --bg-input: #f0f2f5;
66
+ --text-primary: #1a1a2e;
67
+ --text-secondary: #5f6b7a;
68
+ --text-hint: #9ca3b8;
69
+ --border: rgba(0, 0, 0, 0.08);
70
+ --border-strong: rgba(0, 0, 0, 0.15);
71
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
72
+ --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.1);
73
+ --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.12);
74
+ --backdrop: rgba(0, 0, 0, 0.4);
75
+ }
76
+ }
77
+
78
+ /* ─── Reset / Base ─── */
79
+ *,
80
+ *::before,
81
+ *::after {
82
+ box-sizing: border-box;
83
+ margin: 0;
84
+ padding: 0;
85
+ }
86
+
87
+ html,
88
+ body {
89
+ height: 100%;
90
+ overflow: hidden;
91
+ }
92
+
93
+ body {
94
+ font-family:
95
+ "Inter",
96
+ -apple-system,
97
+ BlinkMacSystemFont,
98
+ "Segoe UI",
99
+ sans-serif;
100
+ font-size: 15px;
101
+ color: var(--text-primary);
102
+ background: var(--bg-primary);
103
+ -webkit-font-smoothing: antialiased;
104
+ -moz-osx-font-smoothing: grayscale;
105
+ -webkit-tap-highlight-color: transparent;
106
+ overscroll-behavior: none;
107
+ }
108
+
109
+ #app {
110
+ height: 100%;
111
+ display: flex;
112
+ flex-direction: column;
113
+ overflow: hidden;
114
+ }
115
+
116
+ /* ─── Header ─── */
117
+ .app-header {
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: space-between;
121
+ height: var(--header-height);
122
+ padding: 0 16px;
123
+ background: var(--bg-secondary);
124
+ border-bottom: 1px solid var(--border);
125
+ flex-shrink: 0;
126
+ z-index: 10;
127
+ }
128
+
129
+ .app-header-title {
130
+ font-size: 18px;
131
+ font-weight: 700;
132
+ letter-spacing: -0.02em;
133
+ }
134
+
135
+ .connection-pill {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ gap: 6px;
139
+ padding: 4px 10px;
140
+ border-radius: var(--radius-full);
141
+ font-size: 11px;
142
+ font-weight: 600;
143
+ background: var(--bg-card);
144
+ border: 1px solid var(--border);
145
+ transition: all 0.3s ease;
146
+ }
147
+
148
+ .connection-pill.connected {
149
+ border-color: var(--color-done);
150
+ color: var(--color-done);
151
+ }
152
+
153
+ .connection-pill.disconnected {
154
+ border-color: var(--color-error);
155
+ color: var(--color-error);
156
+ }
157
+
158
+ .connection-dot {
159
+ width: 6px;
160
+ height: 6px;
161
+ border-radius: 50%;
162
+ background: currentColor;
163
+ animation: pulse-dot 2s infinite;
164
+ }
165
+
166
+ /* ─── Main Content ─── */
167
+ .main-content {
168
+ flex: 1;
169
+ overflow-y: auto;
170
+ overflow-x: hidden;
171
+ padding: 12px 16px;
172
+ padding-bottom: calc(var(--nav-height) + var(--safe-bottom) + 16px);
173
+ -webkit-overflow-scrolling: touch;
174
+ scroll-behavior: smooth;
175
+ }
176
+
177
+ /* ─── Tab Panel ─── */
178
+ .tab-panel {
179
+ display: none;
180
+ animation: fadeIn 0.25s ease;
181
+ }
182
+
183
+ .tab-panel.active {
184
+ display: block;
185
+ }
186
+
187
+ /* ─── Bottom Navigation ─── */
188
+ .bottom-nav {
189
+ position: fixed;
190
+ bottom: 0;
191
+ left: 0;
192
+ right: 0;
193
+ height: calc(var(--nav-height) + var(--safe-bottom));
194
+ padding-bottom: var(--safe-bottom);
195
+ background: var(--bg-secondary);
196
+ border-top: 1px solid var(--border);
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-around;
200
+ z-index: 100;
201
+ backdrop-filter: blur(20px);
202
+ -webkit-backdrop-filter: blur(20px);
203
+ }
204
+
205
+ .nav-item {
206
+ display: flex;
207
+ flex-direction: column;
208
+ align-items: center;
209
+ justify-content: center;
210
+ gap: 2px;
211
+ padding: 6px 0;
212
+ min-width: 56px;
213
+ min-height: 44px;
214
+ border: none;
215
+ background: none;
216
+ color: var(--text-hint);
217
+ font-size: 10px;
218
+ font-weight: 500;
219
+ cursor: pointer;
220
+ transition:
221
+ color 0.2s ease,
222
+ transform 0.15s ease;
223
+ -webkit-tap-highlight-color: transparent;
224
+ position: relative;
225
+ }
226
+
227
+ .nav-item:active {
228
+ transform: scale(0.9);
229
+ }
230
+
231
+ .nav-item.active {
232
+ color: var(--accent);
233
+ }
234
+
235
+ .nav-item.active::before {
236
+ content: "";
237
+ position: absolute;
238
+ top: 0;
239
+ left: 50%;
240
+ transform: translateX(-50%);
241
+ width: 24px;
242
+ height: 2px;
243
+ border-radius: 0 0 2px 2px;
244
+ background: var(--accent);
245
+ }
246
+
247
+ .nav-item svg {
248
+ width: 22px;
249
+ height: 22px;
250
+ }
251
+
252
+ .nav-label {
253
+ line-height: 1;
254
+ }
255
+
256
+ /* ─── Cards ─── */
257
+ .card {
258
+ background: var(--bg-card);
259
+ border-radius: var(--radius-lg);
260
+ padding: 16px;
261
+ border: 1px solid var(--border);
262
+ box-shadow: var(--shadow-sm);
263
+ margin-bottom: 12px;
264
+ }
265
+
266
+ .card-title {
267
+ font-size: 15px;
268
+ font-weight: 600;
269
+ margin-bottom: 12px;
270
+ color: var(--text-primary);
271
+ }
272
+
273
+ .card-subtitle {
274
+ font-size: 13px;
275
+ font-weight: 600;
276
+ margin-bottom: 8px;
277
+ color: var(--text-secondary);
278
+ }
279
+
280
+ /* ─── Stat Cards ─── */
281
+ .stats-grid {
282
+ display: grid;
283
+ grid-template-columns: repeat(2, 1fr);
284
+ gap: 10px;
285
+ margin-bottom: 12px;
286
+ }
287
+
288
+ .stat-card {
289
+ background: var(--bg-surface);
290
+ border-radius: var(--radius-md);
291
+ padding: 14px 12px;
292
+ text-align: center;
293
+ border: 1px solid var(--border);
294
+ }
295
+
296
+ .stat-value {
297
+ font-size: 24px;
298
+ font-weight: 700;
299
+ line-height: 1.2;
300
+ letter-spacing: -0.02em;
301
+ }
302
+
303
+ .stat-label {
304
+ font-size: 11px;
305
+ font-weight: 500;
306
+ color: var(--text-secondary);
307
+ margin-top: 2px;
308
+ text-transform: uppercase;
309
+ letter-spacing: 0.05em;
310
+ }
311
+
312
+ /* ─── Badges ─── */
313
+ .badge {
314
+ display: inline-flex;
315
+ align-items: center;
316
+ padding: 3px 8px;
317
+ border-radius: var(--radius-full);
318
+ font-size: 11px;
319
+ font-weight: 600;
320
+ line-height: 1.2;
321
+ white-space: nowrap;
322
+ }
323
+
324
+ .badge-todo {
325
+ background: rgba(139, 149, 162, 0.15);
326
+ color: var(--color-todo);
327
+ }
328
+ .badge-inprogress {
329
+ background: rgba(59, 130, 246, 0.15);
330
+ color: var(--color-inprogress);
331
+ }
332
+ .badge-inreview {
333
+ background: rgba(245, 158, 11, 0.15);
334
+ color: var(--color-inreview);
335
+ }
336
+ .badge-done {
337
+ background: rgba(34, 197, 94, 0.15);
338
+ color: var(--color-done);
339
+ }
340
+ .badge-error {
341
+ background: rgba(239, 68, 68, 0.15);
342
+ color: var(--color-error);
343
+ }
344
+ .badge-cancelled {
345
+ background: rgba(107, 114, 128, 0.15);
346
+ color: var(--color-cancelled);
347
+ }
348
+
349
+ .badge-critical {
350
+ background: rgba(239, 68, 68, 0.15);
351
+ color: var(--color-critical);
352
+ }
353
+ .badge-high {
354
+ background: rgba(245, 158, 11, 0.15);
355
+ color: var(--color-high);
356
+ }
357
+ .badge-medium {
358
+ background: rgba(59, 130, 246, 0.15);
359
+ color: var(--color-medium);
360
+ }
361
+ .badge-low {
362
+ background: rgba(139, 149, 162, 0.15);
363
+ color: var(--color-low);
364
+ }
365
+
366
+ /* ─── Chips / Filters ─── */
367
+ .chip-group {
368
+ display: flex;
369
+ gap: 6px;
370
+ flex-wrap: wrap;
371
+ margin-bottom: 12px;
372
+ }
373
+
374
+ .chip {
375
+ padding: 6px 14px;
376
+ border-radius: var(--radius-full);
377
+ font-size: 12px;
378
+ font-weight: 600;
379
+ border: 1px solid var(--border);
380
+ background: var(--bg-surface);
381
+ color: var(--text-secondary);
382
+ cursor: pointer;
383
+ transition: all 0.2s ease;
384
+ min-height: 32px;
385
+ display: inline-flex;
386
+ align-items: center;
387
+ }
388
+
389
+ .chip:active {
390
+ transform: scale(0.95);
391
+ }
392
+
393
+ .chip.active {
394
+ background: var(--accent);
395
+ color: var(--accent-text);
396
+ border-color: var(--accent);
397
+ }
398
+
399
+ /* ─── Buttons ─── */
400
+ .btn {
401
+ display: inline-flex;
402
+ align-items: center;
403
+ justify-content: center;
404
+ gap: 6px;
405
+ padding: 10px 16px;
406
+ border-radius: var(--radius-md);
407
+ font-size: 13px;
408
+ font-weight: 600;
409
+ border: none;
410
+ cursor: pointer;
411
+ transition: all 0.2s ease;
412
+ min-height: 44px;
413
+ -webkit-tap-highlight-color: transparent;
414
+ }
415
+
416
+ .btn:active {
417
+ transform: scale(0.96);
418
+ }
419
+
420
+ .btn-primary {
421
+ background: var(--gradient-accent);
422
+ color: var(--accent-text);
423
+ }
424
+
425
+ .btn-secondary {
426
+ background: var(--bg-surface);
427
+ color: var(--text-primary);
428
+ border: 1px solid var(--border);
429
+ }
430
+
431
+ .btn-ghost {
432
+ background: transparent;
433
+ color: var(--text-secondary);
434
+ padding: 8px 12px;
435
+ }
436
+
437
+ .btn-danger {
438
+ background: rgba(239, 68, 68, 0.15);
439
+ color: var(--color-error);
440
+ }
441
+
442
+ .btn-sm {
443
+ padding: 6px 12px;
444
+ font-size: 12px;
445
+ min-height: 32px;
446
+ }
447
+
448
+ .btn-row {
449
+ display: flex;
450
+ flex-wrap: wrap;
451
+ gap: 8px;
452
+ }
453
+
454
+ /* ─── Inputs ─── */
455
+ .input {
456
+ width: 100%;
457
+ padding: 10px 14px;
458
+ border-radius: var(--radius-md);
459
+ border: 1px solid var(--border);
460
+ background: var(--bg-input);
461
+ color: var(--text-primary);
462
+ font-size: 14px;
463
+ font-family: inherit;
464
+ min-height: 44px;
465
+ transition: border-color 0.2s ease;
466
+ outline: none;
467
+ }
468
+
469
+ .input:focus {
470
+ border-color: var(--accent);
471
+ }
472
+
473
+ .input::placeholder {
474
+ color: var(--text-hint);
475
+ }
476
+
477
+ textarea.input {
478
+ min-height: 80px;
479
+ resize: vertical;
480
+ }
481
+
482
+ select.input {
483
+ appearance: none;
484
+ cursor: pointer;
485
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%239ca3b8' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
486
+ background-repeat: no-repeat;
487
+ background-position: right 12px center;
488
+ padding-right: 32px;
489
+ }
490
+
491
+ .input-row {
492
+ display: flex;
493
+ gap: 8px;
494
+ margin-bottom: 10px;
495
+ }
496
+
497
+ .input-row .input {
498
+ flex: 1;
499
+ }
500
+
501
+ .range-row {
502
+ display: flex;
503
+ align-items: center;
504
+ gap: 10px;
505
+ margin-bottom: 10px;
506
+ }
507
+
508
+ .range-row input[type="range"] {
509
+ flex: 1;
510
+ accent-color: var(--accent);
511
+ height: 4px;
512
+ }
513
+
514
+ /* ─── Task Cards ─── */
515
+ .task-card {
516
+ background: var(--bg-surface);
517
+ border-radius: var(--radius-md);
518
+ padding: 14px;
519
+ border: 1px solid var(--border);
520
+ margin-bottom: 8px;
521
+ transition: background 0.2s ease;
522
+ }
523
+
524
+ .task-card:active {
525
+ background: var(--bg-card-hover);
526
+ }
527
+
528
+ .task-card-header {
529
+ display: flex;
530
+ justify-content: space-between;
531
+ align-items: flex-start;
532
+ gap: 8px;
533
+ margin-bottom: 6px;
534
+ }
535
+
536
+ .task-card-title {
537
+ font-size: 14px;
538
+ font-weight: 600;
539
+ line-height: 1.3;
540
+ }
541
+
542
+ .task-card-meta {
543
+ font-size: 12px;
544
+ color: var(--text-secondary);
545
+ margin-bottom: 8px;
546
+ }
547
+
548
+ /* ─── Progress Bar ─── */
549
+ .progress-bar {
550
+ height: 8px;
551
+ background: var(--bg-surface);
552
+ border-radius: var(--radius-full);
553
+ overflow: hidden;
554
+ margin-bottom: 8px;
555
+ }
556
+
557
+ .progress-bar-fill {
558
+ height: 100%;
559
+ background: var(--gradient-progress);
560
+ border-radius: var(--radius-full);
561
+ transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
562
+ }
563
+
564
+ /* ─── Donut Chart ─── */
565
+ .donut-wrap {
566
+ display: flex;
567
+ justify-content: center;
568
+ align-items: center;
569
+ padding: 8px 0;
570
+ }
571
+
572
+ .donut-legend {
573
+ display: flex;
574
+ flex-wrap: wrap;
575
+ gap: 12px;
576
+ justify-content: center;
577
+ margin-top: 8px;
578
+ }
579
+
580
+ .donut-legend-item {
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 4px;
584
+ font-size: 11px;
585
+ color: var(--text-secondary);
586
+ }
587
+
588
+ .donut-legend-swatch {
589
+ width: 8px;
590
+ height: 8px;
591
+ border-radius: 50%;
592
+ }
593
+
594
+ /* ─── Segmented Control ─── */
595
+ .segmented-control {
596
+ display: inline-flex;
597
+ background: var(--bg-surface);
598
+ border-radius: var(--radius-full);
599
+ padding: 3px;
600
+ gap: 2px;
601
+ margin-bottom: 10px;
602
+ }
603
+
604
+ .segmented-btn {
605
+ padding: 6px 14px;
606
+ border-radius: var(--radius-full);
607
+ border: none;
608
+ background: transparent;
609
+ color: var(--text-secondary);
610
+ font-size: 12px;
611
+ font-weight: 600;
612
+ cursor: pointer;
613
+ transition: all 0.2s ease;
614
+ min-height: 32px;
615
+ }
616
+
617
+ .segmented-btn.active {
618
+ background: var(--accent);
619
+ color: var(--accent-text);
620
+ box-shadow: var(--shadow-sm);
621
+ }
622
+
623
+ /* ─── Log Box (Terminal) ─── */
624
+ .log-box {
625
+ background: #0d1117;
626
+ color: #c9d1d9;
627
+ padding: 12px;
628
+ border-radius: var(--radius-md);
629
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
630
+ font-size: 11px;
631
+ line-height: 1.5;
632
+ max-height: 300px;
633
+ overflow: auto;
634
+ white-space: pre-wrap;
635
+ word-break: break-all;
636
+ border: 1px solid rgba(255, 255, 255, 0.05);
637
+ }
638
+
639
+ /* ─── Skeleton Loader ─── */
640
+ .skeleton {
641
+ background: var(--bg-surface);
642
+ border-radius: var(--radius-md);
643
+ animation: skeleton-pulse 1.5s ease-in-out infinite;
644
+ min-height: 20px;
645
+ }
646
+
647
+ .skeleton-card {
648
+ height: 80px;
649
+ margin-bottom: 8px;
650
+ }
651
+
652
+ .skeleton-stat {
653
+ height: 70px;
654
+ }
655
+
656
+ .skeleton-text {
657
+ height: 14px;
658
+ margin-bottom: 8px;
659
+ border-radius: var(--radius-sm);
660
+ }
661
+
662
+ .skeleton-text.w-60 {
663
+ width: 60%;
664
+ }
665
+ .skeleton-text.w-40 {
666
+ width: 40%;
667
+ }
668
+ .skeleton-text.w-80 {
669
+ width: 80%;
670
+ }
671
+
672
+ /* ─── Modal ─── */
673
+ .modal-overlay {
674
+ position: fixed;
675
+ inset: 0;
676
+ background: var(--backdrop);
677
+ backdrop-filter: var(--backdrop-blur);
678
+ -webkit-backdrop-filter: var(--backdrop-blur);
679
+ z-index: 200;
680
+ display: flex;
681
+ align-items: flex-end;
682
+ justify-content: center;
683
+ animation: fadeIn 0.2s ease;
684
+ }
685
+
686
+ .modal-content {
687
+ background: var(--bg-secondary);
688
+ border-radius: var(--radius-xl) var(--radius-xl) 0 0;
689
+ width: 100%;
690
+ max-width: 500px;
691
+ max-height: 92vh;
692
+ overflow-y: auto;
693
+ padding: 20px 16px;
694
+ padding-bottom: calc(20px + var(--safe-bottom));
695
+ animation: slideUp 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
696
+ }
697
+
698
+ .modal-handle {
699
+ width: 36px;
700
+ height: 4px;
701
+ background: var(--border-strong);
702
+ border-radius: var(--radius-full);
703
+ margin: 0 auto 16px;
704
+ }
705
+
706
+ .modal-title {
707
+ font-size: 20px;
708
+ font-weight: 700;
709
+ margin-bottom: 16px;
710
+ }
711
+
712
+ /* ─── Toast ─── */
713
+ .toast-container {
714
+ position: fixed;
715
+ top: 12px;
716
+ left: 16px;
717
+ right: 16px;
718
+ z-index: 300;
719
+ display: flex;
720
+ flex-direction: column;
721
+ gap: 8px;
722
+ pointer-events: none;
723
+ }
724
+
725
+ .toast {
726
+ background: var(--bg-card);
727
+ color: var(--text-primary);
728
+ border-radius: var(--radius-md);
729
+ padding: 12px 16px;
730
+ font-size: 13px;
731
+ font-weight: 500;
732
+ box-shadow: var(--shadow-lg);
733
+ border: 1px solid var(--border);
734
+ animation: slideDown 0.3s ease;
735
+ pointer-events: auto;
736
+ }
737
+
738
+ .toast-success {
739
+ border-left: 3px solid var(--color-done);
740
+ }
741
+ .toast-error {
742
+ border-left: 3px solid var(--color-error);
743
+ }
744
+ .toast-info {
745
+ border-left: 3px solid var(--color-inprogress);
746
+ }
747
+
748
+ /* ─── FAB ─── */
749
+ .fab {
750
+ position: fixed;
751
+ bottom: calc(var(--nav-height) + var(--safe-bottom) + 16px);
752
+ right: 16px;
753
+ width: 56px;
754
+ height: 56px;
755
+ border-radius: 50%;
756
+ background: var(--gradient-accent);
757
+ color: white;
758
+ border: none;
759
+ display: flex;
760
+ align-items: center;
761
+ justify-content: center;
762
+ box-shadow: var(--shadow-lg);
763
+ cursor: pointer;
764
+ z-index: 50;
765
+ transition:
766
+ transform 0.2s ease,
767
+ box-shadow 0.2s ease;
768
+ }
769
+
770
+ .fab:active {
771
+ transform: scale(0.9);
772
+ }
773
+
774
+ .fab svg {
775
+ width: 24px;
776
+ height: 24px;
777
+ }
778
+
779
+ /* ─── Collapsible Section ─── */
780
+ .collapsible-header {
781
+ display: flex;
782
+ align-items: center;
783
+ justify-content: space-between;
784
+ padding: 12px 0;
785
+ cursor: pointer;
786
+ border: none;
787
+ background: none;
788
+ width: 100%;
789
+ color: var(--text-primary);
790
+ font-size: 15px;
791
+ font-weight: 600;
792
+ -webkit-tap-highlight-color: transparent;
793
+ }
794
+
795
+ .collapsible-header svg {
796
+ transition: transform 0.2s ease;
797
+ }
798
+
799
+ .collapsible-header.open svg {
800
+ transform: rotate(180deg);
801
+ }
802
+
803
+ .collapsible-body {
804
+ overflow: hidden;
805
+ max-height: 0;
806
+ transition: max-height 0.3s ease;
807
+ }
808
+
809
+ .collapsible-body.open {
810
+ max-height: 2000px;
811
+ }
812
+
813
+ /* ─── Pill ─── */
814
+ .pill {
815
+ display: inline-flex;
816
+ align-items: center;
817
+ padding: 4px 10px;
818
+ border-radius: var(--radius-full);
819
+ font-size: 11px;
820
+ font-weight: 600;
821
+ background: var(--bg-surface);
822
+ color: var(--text-secondary);
823
+ }
824
+
825
+ /* ─── Pull to Refresh ─── */
826
+ .ptr-spinner {
827
+ display: flex;
828
+ justify-content: center;
829
+ padding: 12px 0;
830
+ }
831
+
832
+ .ptr-spinner-icon {
833
+ width: 24px;
834
+ height: 24px;
835
+ border: 2px solid var(--border);
836
+ border-top-color: var(--accent);
837
+ border-radius: 50%;
838
+ animation: spin 0.8s linear infinite;
839
+ }
840
+
841
+ /* ─── Misc ─── */
842
+ .meta-text {
843
+ font-size: 12px;
844
+ color: var(--text-hint);
845
+ }
846
+
847
+ .gap-sm {
848
+ gap: 6px;
849
+ }
850
+ .gap-md {
851
+ gap: 10px;
852
+ }
853
+ .mb-sm {
854
+ margin-bottom: 6px;
855
+ }
856
+ .mb-md {
857
+ margin-bottom: 12px;
858
+ }
859
+ .mb-lg {
860
+ margin-bottom: 16px;
861
+ }
862
+ .mt-sm {
863
+ margin-top: 6px;
864
+ }
865
+ .mt-md {
866
+ margin-top: 12px;
867
+ }
868
+
869
+ .flex-between {
870
+ display: flex;
871
+ justify-content: space-between;
872
+ align-items: center;
873
+ }
874
+
875
+ .text-center {
876
+ text-align: center;
877
+ }
878
+ .text-right {
879
+ text-align: right;
880
+ }
881
+
882
+ .hidden {
883
+ display: none !important;
884
+ }
885
+
886
+ /* ─── Pager ─── */
887
+ .pager {
888
+ display: flex;
889
+ justify-content: space-between;
890
+ align-items: center;
891
+ gap: 8px;
892
+ margin-top: 12px;
893
+ }
894
+
895
+ .pager-info {
896
+ font-size: 12px;
897
+ color: var(--text-secondary);
898
+ font-weight: 600;
899
+ }
900
+
901
+ /* ─── Flex Utilities ─── */
902
+ .flex-col {
903
+ display: flex;
904
+ flex-direction: column;
905
+ }
906
+
907
+ .flex-row {
908
+ display: flex;
909
+ flex-direction: row;
910
+ align-items: center;
911
+ }
912
+
913
+ .flex-wrap {
914
+ flex-wrap: wrap;
915
+ }
916
+ .flex-1 {
917
+ flex: 1;
918
+ }
919
+
920
+ /* ─── Animations ─── */
921
+ @keyframes fadeIn {
922
+ from {
923
+ opacity: 0;
924
+ }
925
+ to {
926
+ opacity: 1;
927
+ }
928
+ }
929
+
930
+ @keyframes slideUp {
931
+ from {
932
+ transform: translateY(100%);
933
+ }
934
+ to {
935
+ transform: translateY(0);
936
+ }
937
+ }
938
+
939
+ @keyframes slideDown {
940
+ from {
941
+ opacity: 0;
942
+ transform: translateY(-20px);
943
+ }
944
+ to {
945
+ opacity: 1;
946
+ transform: translateY(0);
947
+ }
948
+ }
949
+
950
+ @keyframes slideLeft {
951
+ from {
952
+ opacity: 0;
953
+ transform: translateX(20px);
954
+ }
955
+ to {
956
+ opacity: 1;
957
+ transform: translateX(0);
958
+ }
959
+ }
960
+
961
+ @keyframes skeleton-pulse {
962
+ 0%,
963
+ 100% {
964
+ opacity: 0.4;
965
+ }
966
+ 50% {
967
+ opacity: 0.8;
968
+ }
969
+ }
970
+
971
+ @keyframes spin {
972
+ to {
973
+ transform: rotate(360deg);
974
+ }
975
+ }
976
+
977
+ @keyframes pulse-dot {
978
+ 0%,
979
+ 100% {
980
+ opacity: 1;
981
+ }
982
+ 50% {
983
+ opacity: 0.4;
984
+ }
985
+ }
986
+
987
+ @keyframes counter-up {
988
+ from {
989
+ opacity: 0;
990
+ transform: translateY(8px);
991
+ }
992
+ to {
993
+ opacity: 1;
994
+ transform: translateY(0);
995
+ }
996
+ }
997
+
998
+ /* ─── Scrollbar ─── */
999
+ .main-content::-webkit-scrollbar {
1000
+ width: 4px;
1001
+ }
1002
+
1003
+ .main-content::-webkit-scrollbar-thumb {
1004
+ background: var(--border-strong);
1005
+ border-radius: var(--radius-full);
1006
+ }
1007
+
1008
+ .log-box::-webkit-scrollbar {
1009
+ width: 4px;
1010
+ }
1011
+
1012
+ .log-box::-webkit-scrollbar-thumb {
1013
+ background: rgba(255, 255, 255, 0.1);
1014
+ border-radius: var(--radius-full);
1015
+ }
1016
+
1017
+ /* ─── Responsive ─── */
1018
+ @media (max-width: 380px) {
1019
+ .stats-grid {
1020
+ grid-template-columns: repeat(2, 1fr);
1021
+ gap: 6px;
1022
+ }
1023
+
1024
+ .stat-value {
1025
+ font-size: 20px;
1026
+ }
1027
+
1028
+ .nav-label {
1029
+ font-size: 9px;
1030
+ }
1031
+ }
1032
+
1033
+ @media (min-width: 600px) {
1034
+ .stats-grid {
1035
+ grid-template-columns: repeat(4, 1fr);
1036
+ }
1037
+
1038
+ .modal-content {
1039
+ border-radius: var(--radius-xl);
1040
+ margin-bottom: 24px;
1041
+ }
1042
+
1043
+ .modal-overlay {
1044
+ align-items: center;
1045
+ }
1046
+ }