4bnode 4.0.0 → 4.0.2

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 (37) hide show
  1. package/README.md +10 -0
  2. package/add-login.js +2 -2
  3. package/index.js +57 -5
  4. package/lib/indexFile.js +60 -13
  5. package/package.json +1 -1
  6. package/skeleton/.4bnode/assets/js/App.js +115 -0
  7. package/skeleton/.4bnode/assets/js/Root.js +44 -0
  8. package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
  9. package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
  10. package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
  11. package/skeleton/.4bnode/assets/js/context.js +7 -0
  12. package/skeleton/.4bnode/assets/js/lib.js +83 -0
  13. package/skeleton/.4bnode/assets/js/main.js +5 -0
  14. package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
  15. package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
  16. package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
  17. package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
  18. package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
  19. package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
  20. package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
  21. package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
  22. package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
  23. package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
  24. package/skeleton/{dev-api.js → .4bnode/dev-api.js} +259 -114
  25. package/skeleton/.4bnode/ui.html +1556 -0
  26. package/skeleton/index.js +62 -16
  27. package/skeleton/package.json +1 -1
  28. package/skeleton/public/index.html +290 -7586
  29. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Bold.woff2 +0 -0
  30. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Light.woff2 +0 -0
  31. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Medium.woff2 +0 -0
  32. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Regular.woff2 +0 -0
  33. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-SemiBold.woff2 +0 -0
  34. /package/skeleton/{public → .4bnode/assets}/images/mongo.png +0 -0
  35. /package/skeleton/{public → .4bnode/assets}/images/mysql.png +0 -0
  36. /package/skeleton/{public → .4bnode/assets}/images/postgresql.png +0 -0
  37. /package/skeleton/{public → .4bnode/assets}/images/sqlite.png +0 -0
@@ -0,0 +1,1556 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>4bnode Dashboard</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <style>
14
+ @font-face {
15
+ font-family: "Noto Sans";
16
+ src: url("/_dev/assets/fonts/NotoSans-Light.woff2") format("woff2");
17
+ font-weight: 300;
18
+ font-style: normal;
19
+ font-display: swap;
20
+ }
21
+ @font-face {
22
+ font-family: "Noto Sans";
23
+ src: url("/_dev/assets/fonts/NotoSans-Regular.woff2") format("woff2");
24
+ font-weight: 400;
25
+ font-style: normal;
26
+ font-display: swap;
27
+ }
28
+ @font-face {
29
+ font-family: "Noto Sans";
30
+ src: url("/_dev/assets/fonts/NotoSans-Medium.woff2") format("woff2");
31
+ font-weight: 500;
32
+ font-style: normal;
33
+ font-display: swap;
34
+ }
35
+ @font-face {
36
+ font-family: "Noto Sans";
37
+ src: url("/_dev/assets/fonts/NotoSans-SemiBold.woff2") format("woff2");
38
+ font-weight: 600;
39
+ font-style: normal;
40
+ font-display: swap;
41
+ }
42
+ @font-face {
43
+ font-family: "Noto Sans";
44
+ src: url("/_dev/assets/fonts/NotoSans-Bold.woff2") format("woff2");
45
+ font-weight: 700;
46
+ font-style: normal;
47
+ font-display: swap;
48
+ }
49
+
50
+ *,
51
+ *::before,
52
+ *::after {
53
+ margin: 0;
54
+ padding: 0;
55
+ box-sizing: border-box;
56
+ }
57
+
58
+ :root {
59
+ --bg: #09090b;
60
+ --bg-surface: #0f0f13;
61
+ --bg-card: #16161d;
62
+ --bg-card-hover: #1c1c26;
63
+ --bg-input: #12121a;
64
+ --border: rgba(255, 255, 255, 0.07);
65
+ --border-focus: rgba(99, 102, 241, 0.5);
66
+ --text: #f0f0f5;
67
+ --text-secondary: #a1a1aa;
68
+ --text-muted: #63637a;
69
+ --accent: #6366f1;
70
+ --accent-light: #818cf8;
71
+ --accent-glow: rgba(99, 102, 241, 0.15);
72
+ --success: #22c55e;
73
+ --success-glow: rgba(34, 197, 94, 0.12);
74
+ --warning: #f59e0b;
75
+ --warning-glow: rgba(245, 158, 11, 0.12);
76
+ --error: #ef4444;
77
+ --error-glow: rgba(239, 68, 68, 0.12);
78
+ --info: #3b82f6;
79
+ --info-glow: rgba(59, 130, 246, 0.12);
80
+ --radius: 8px;
81
+ --radius-lg: 12px;
82
+ --radius-xl: 16px;
83
+ --sidebar-w: 220px;
84
+ --transition: 150ms ease;
85
+ }
86
+
87
+ html,
88
+ body {
89
+ height: 100%;
90
+ }
91
+ body {
92
+ font-family:
93
+ "Noto Sans",
94
+ -apple-system,
95
+ BlinkMacSystemFont,
96
+ sans-serif;
97
+ background: var(--bg);
98
+ color: var(--text);
99
+ overflow: hidden;
100
+ -webkit-font-smoothing: antialiased;
101
+ }
102
+
103
+ #root {
104
+ display: flex;
105
+ height: 100vh;
106
+ }
107
+
108
+ /* ── Scrollbar ── */
109
+ ::-webkit-scrollbar {
110
+ width: 6px;
111
+ }
112
+ ::-webkit-scrollbar-track {
113
+ background: transparent;
114
+ }
115
+ ::-webkit-scrollbar-thumb {
116
+ background: rgba(255, 255, 255, 0.08);
117
+ border-radius: 3px;
118
+ }
119
+ ::-webkit-scrollbar-thumb:hover {
120
+ background: rgba(255, 255, 255, 0.14);
121
+ }
122
+
123
+ /* ── Sidebar ── */
124
+ .sidebar {
125
+ width: var(--sidebar-w);
126
+ background: var(--bg-surface);
127
+ border-right: 1px solid var(--border);
128
+ display: flex;
129
+ flex-direction: column;
130
+ flex-shrink: 0;
131
+ padding: 20px 0;
132
+ overflow-y: auto;
133
+ }
134
+ .sidebar-brand {
135
+ padding: 0 20px 20px;
136
+ border-bottom: 1px solid var(--border);
137
+ margin-bottom: 8px;
138
+ }
139
+ .sidebar-brand pre {
140
+ font-family: monospace;
141
+ font-size: 5.5px;
142
+ line-height: 1.1;
143
+ letter-spacing: 0.5px;
144
+ background: linear-gradient(135deg, #00c6ff, #0072ff, #7b2ff7);
145
+ -webkit-background-clip: text;
146
+ -webkit-text-fill-color: transparent;
147
+ margin: 0 0 6px -2px;
148
+ white-space: pre;
149
+ user-select: none;
150
+ }
151
+ .sidebar-brand span {
152
+ font-size: 11px;
153
+ color: var(--text-muted);
154
+ font-weight: 400;
155
+ }
156
+ .sidebar-section {
157
+ padding: 12px 12px 4px;
158
+ font-size: 10px;
159
+ font-weight: 600;
160
+ color: var(--text-muted);
161
+ text-transform: uppercase;
162
+ letter-spacing: 1px;
163
+ }
164
+ .sidebar-item {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 10px;
168
+ padding: 9px 16px;
169
+ margin: 1px 8px;
170
+ border-radius: var(--radius);
171
+ font-size: 13px;
172
+ font-weight: 500;
173
+ color: var(--text-secondary);
174
+ cursor: pointer;
175
+ transition: all var(--transition);
176
+ position: relative;
177
+ }
178
+ .sidebar-item:hover {
179
+ background: rgba(255, 255, 255, 0.04);
180
+ color: var(--text);
181
+ }
182
+ .sidebar-item.active {
183
+ background: var(--accent-glow);
184
+ color: var(--accent-light);
185
+ }
186
+ .sidebar-item.locked {
187
+ opacity: 0.4;
188
+ cursor: not-allowed;
189
+ }
190
+ .sidebar-item .lock-icon {
191
+ position: absolute;
192
+ right: 12px;
193
+ font-size: 10px;
194
+ opacity: 0.6;
195
+ }
196
+ .sidebar-icon {
197
+ width: 18px;
198
+ height: 18px;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ flex-shrink: 0;
203
+ }
204
+ .sidebar-icon svg {
205
+ width: 16px;
206
+ height: 16px;
207
+ stroke: currentColor;
208
+ fill: none;
209
+ }
210
+
211
+ /* ── Main Area ── */
212
+ .main {
213
+ flex: 1;
214
+ display: flex;
215
+ flex-direction: column;
216
+ overflow: hidden;
217
+ }
218
+ .topbar {
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: space-between;
222
+ padding: 16px 28px;
223
+ border-bottom: 1px solid var(--border);
224
+ flex-shrink: 0;
225
+ }
226
+ .topbar h2 {
227
+ font-size: 16px;
228
+ font-weight: 600;
229
+ }
230
+ .topbar-badge {
231
+ font-size: 11px;
232
+ padding: 3px 10px;
233
+ border-radius: 20px;
234
+ background: var(--accent-glow);
235
+ color: var(--accent-light);
236
+ font-weight: 500;
237
+ }
238
+ .content {
239
+ flex: 1;
240
+ overflow-y: auto;
241
+ padding: 28px;
242
+ }
243
+
244
+ /* ── Cards ── */
245
+ .card {
246
+ background: var(--bg-card);
247
+ border: 1px solid var(--border);
248
+ border-radius: var(--radius-lg);
249
+ padding: 20px;
250
+ transition: all var(--transition);
251
+ }
252
+ .card:hover {
253
+ border-color: rgba(255, 255, 255, 0.1);
254
+ }
255
+ .card-header {
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: space-between;
259
+ margin-bottom: 16px;
260
+ }
261
+ .card-title {
262
+ font-size: 14px;
263
+ font-weight: 600;
264
+ }
265
+ .card-subtitle {
266
+ font-size: 12px;
267
+ color: var(--text-muted);
268
+ margin-top: 2px;
269
+ }
270
+
271
+ /* ── Stat Cards ── */
272
+ .stats-grid {
273
+ display: grid;
274
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
275
+ gap: 16px;
276
+ margin-bottom: 24px;
277
+ }
278
+ .stat-card {
279
+ background: var(--bg-card);
280
+ border: 1px solid var(--border);
281
+ border-radius: var(--radius-lg);
282
+ padding: 18px 20px;
283
+ display: flex;
284
+ flex-direction: column;
285
+ gap: 8px;
286
+ }
287
+ .stat-card .stat-label {
288
+ font-size: 12px;
289
+ color: var(--text-muted);
290
+ font-weight: 500;
291
+ }
292
+ .stat-card .stat-value {
293
+ font-size: 28px;
294
+ font-weight: 700;
295
+ letter-spacing: -1px;
296
+ }
297
+ .stat-card .stat-badge {
298
+ display: inline-flex;
299
+ align-items: center;
300
+ gap: 4px;
301
+ font-size: 11px;
302
+ font-weight: 500;
303
+ padding: 2px 8px;
304
+ border-radius: 20px;
305
+ width: fit-content;
306
+ }
307
+ .stat-badge.success {
308
+ background: var(--success-glow);
309
+ color: var(--success);
310
+ }
311
+ .stat-badge.warning {
312
+ background: var(--warning-glow);
313
+ color: var(--warning);
314
+ }
315
+ .stat-badge.error {
316
+ background: var(--error-glow);
317
+ color: var(--error);
318
+ }
319
+ .stat-badge.info {
320
+ background: var(--info-glow);
321
+ color: var(--info);
322
+ }
323
+
324
+ /* ── Buttons ── */
325
+ .btn {
326
+ display: inline-flex;
327
+ align-items: center;
328
+ gap: 6px;
329
+ padding: 8px 16px;
330
+ border-radius: var(--radius);
331
+ font-size: 13px;
332
+ font-weight: 500;
333
+ font-family: inherit;
334
+ border: none;
335
+ cursor: pointer;
336
+ transition: all var(--transition);
337
+ white-space: nowrap;
338
+ }
339
+ .btn:disabled {
340
+ opacity: 0.5;
341
+ cursor: not-allowed;
342
+ }
343
+ .btn-primary {
344
+ background: var(--accent);
345
+ color: white;
346
+ box-shadow: 0 0 20px var(--accent-glow);
347
+ }
348
+ .btn-primary:hover:not(:disabled) {
349
+ background: var(--accent-light);
350
+ }
351
+ .btn-secondary {
352
+ background: rgba(255, 255, 255, 0.06);
353
+ color: var(--text);
354
+ border: 1px solid var(--border);
355
+ }
356
+ .btn-secondary:hover:not(:disabled) {
357
+ background: rgba(255, 255, 255, 0.1);
358
+ border-color: rgba(255, 255, 255, 0.12);
359
+ }
360
+ .btn-ghost {
361
+ background: transparent;
362
+ color: var(--text-secondary);
363
+ }
364
+ .btn-ghost:hover:not(:disabled) {
365
+ background: rgba(255, 255, 255, 0.04);
366
+ color: var(--text);
367
+ }
368
+ .btn-danger {
369
+ background: var(--error-glow);
370
+ color: var(--error);
371
+ border: 1px solid rgba(239, 68, 68, 0.2);
372
+ }
373
+ .btn-danger:hover:not(:disabled) {
374
+ background: rgba(239, 68, 68, 0.2);
375
+ }
376
+ .btn-sm {
377
+ padding: 5px 10px;
378
+ font-size: 12px;
379
+ }
380
+ .btn-icon {
381
+ padding: 6px;
382
+ }
383
+
384
+ /* ── Form elements ── */
385
+ .form-group {
386
+ margin-bottom: 16px;
387
+ }
388
+ .form-label {
389
+ display: block;
390
+ font-size: 12px;
391
+ font-weight: 500;
392
+ color: var(--text-secondary);
393
+ margin-bottom: 6px;
394
+ }
395
+ .form-input,
396
+ .form-select {
397
+ width: 100%;
398
+ padding: 9px 12px;
399
+ background: var(--bg-input);
400
+ border: 1px solid var(--border);
401
+ border-radius: var(--radius);
402
+ color: var(--text);
403
+ font-size: 13px;
404
+ font-family: inherit;
405
+ outline: none;
406
+ transition: border-color var(--transition);
407
+ }
408
+ .form-input:focus,
409
+ .form-select:focus {
410
+ border-color: var(--border-focus);
411
+ }
412
+ .form-input::placeholder {
413
+ color: var(--text-muted);
414
+ }
415
+ .form-select option {
416
+ background: var(--bg-card);
417
+ }
418
+
419
+ .form-toggle {
420
+ position: relative;
421
+ width: 36px;
422
+ height: 20px;
423
+ display: inline-block;
424
+ }
425
+ .form-toggle input {
426
+ opacity: 0;
427
+ width: 0;
428
+ height: 0;
429
+ }
430
+ .form-toggle .slider {
431
+ position: absolute;
432
+ cursor: pointer;
433
+ inset: 0;
434
+ background: rgba(255, 255, 255, 0.1);
435
+ border-radius: 20px;
436
+ transition: var(--transition);
437
+ }
438
+ .form-toggle .slider::before {
439
+ content: "";
440
+ position: absolute;
441
+ width: 14px;
442
+ height: 14px;
443
+ left: 3px;
444
+ bottom: 3px;
445
+ background: white;
446
+ border-radius: 50%;
447
+ transition: var(--transition);
448
+ }
449
+ .form-toggle input:checked + .slider {
450
+ background: var(--accent);
451
+ }
452
+ .form-toggle input:checked + .slider::before {
453
+ transform: translateX(16px);
454
+ }
455
+
456
+ /* ── Grid layouts ── */
457
+ .grid-2 {
458
+ display: grid;
459
+ grid-template-columns: 1fr 1fr;
460
+ gap: 16px;
461
+ }
462
+ .grid-3 {
463
+ display: grid;
464
+ grid-template-columns: repeat(3, 1fr);
465
+ gap: 16px;
466
+ }
467
+ .grid-4 {
468
+ display: grid;
469
+ grid-template-columns: repeat(4, 1fr);
470
+ gap: 12px;
471
+ }
472
+
473
+ /* ── Badge ── */
474
+ .badge {
475
+ display: inline-flex;
476
+ align-items: center;
477
+ gap: 4px;
478
+ font-size: 11px;
479
+ font-weight: 500;
480
+ padding: 3px 8px;
481
+ border-radius: 20px;
482
+ }
483
+ .badge-success {
484
+ background: var(--success-glow);
485
+ color: var(--success);
486
+ }
487
+ .badge-warning {
488
+ background: var(--warning-glow);
489
+ color: var(--warning);
490
+ }
491
+ .badge-error {
492
+ background: var(--error-glow);
493
+ color: var(--error);
494
+ }
495
+ .badge-info {
496
+ background: var(--info-glow);
497
+ color: var(--info);
498
+ }
499
+ .badge-muted {
500
+ background: rgba(255, 255, 255, 0.05);
501
+ color: var(--text-muted);
502
+ }
503
+
504
+ /* ── DB Cards ── */
505
+ .db-card {
506
+ background: var(--bg-card);
507
+ border: 1px solid var(--border);
508
+ border-radius: var(--radius-lg);
509
+ padding: 24px;
510
+ text-align: center;
511
+ cursor: pointer;
512
+ transition: all var(--transition);
513
+ position: relative;
514
+ overflow: hidden;
515
+ }
516
+ .db-card:hover:not(.disabled) {
517
+ border-color: var(--accent);
518
+ box-shadow: 0 0 30px var(--accent-glow);
519
+ transform: translateY(-2px);
520
+ }
521
+ .db-card.active {
522
+ border-color: var(--accent);
523
+ box-shadow: 0 0 30px var(--accent-glow);
524
+ }
525
+ .db-card.disabled {
526
+ opacity: 0.5;
527
+ cursor: not-allowed;
528
+ }
529
+ .db-card .db-icon {
530
+ width: 36px;
531
+ height: 36px;
532
+ margin: 0 auto 10px;
533
+ display: flex;
534
+ align-items: center;
535
+ justify-content: center;
536
+ }
537
+ .db-card .db-name {
538
+ font-size: 14px;
539
+ font-weight: 600;
540
+ margin-bottom: 4px;
541
+ }
542
+ .db-card .db-desc {
543
+ font-size: 11px;
544
+ color: var(--text-muted);
545
+ }
546
+ .db-card .coming-soon {
547
+ position: absolute;
548
+ top: 8px;
549
+ right: 8px;
550
+ font-size: 9px;
551
+ font-weight: 600;
552
+ text-transform: uppercase;
553
+ letter-spacing: 0.5px;
554
+ padding: 2px 8px;
555
+ border-radius: 20px;
556
+ background: var(--warning-glow);
557
+ color: var(--warning);
558
+ }
559
+
560
+ /* ── Empty State ── */
561
+ .empty-state {
562
+ text-align: center;
563
+ padding: 48px 24px;
564
+ color: var(--text-muted);
565
+ }
566
+ .empty-state .empty-icon {
567
+ font-size: 36px;
568
+ margin-bottom: 12px;
569
+ opacity: 0.5;
570
+ display: flex;
571
+ align-items: center;
572
+ justify-content: center;
573
+ color: var(--text-muted);
574
+ }
575
+ .empty-state h3 {
576
+ font-size: 15px;
577
+ font-weight: 600;
578
+ color: var(--text-secondary);
579
+ margin-bottom: 6px;
580
+ }
581
+ .empty-state p {
582
+ font-size: 13px;
583
+ margin-bottom: 16px;
584
+ }
585
+
586
+ /* ── Locked State ── */
587
+ .locked-state {
588
+ text-align: center;
589
+ padding: 60px 24px;
590
+ }
591
+ .locked-state .locked-icon {
592
+ width: 56px;
593
+ height: 56px;
594
+ border-radius: 50%;
595
+ background: rgba(255, 255, 255, 0.04);
596
+ display: inline-flex;
597
+ align-items: center;
598
+ justify-content: center;
599
+ font-size: 24px;
600
+ margin-bottom: 16px;
601
+ }
602
+ .locked-state h3 {
603
+ font-size: 16px;
604
+ font-weight: 600;
605
+ margin-bottom: 6px;
606
+ }
607
+ .locked-state p {
608
+ font-size: 13px;
609
+ color: var(--text-muted);
610
+ margin-bottom: 20px;
611
+ max-width: 360px;
612
+ margin-left: auto;
613
+ margin-right: auto;
614
+ }
615
+
616
+ /* ── Table ── */
617
+ .table-wrap {
618
+ overflow-x: auto;
619
+ }
620
+ table {
621
+ width: 100%;
622
+ border-collapse: collapse;
623
+ }
624
+ th {
625
+ text-align: left;
626
+ font-size: 11px;
627
+ font-weight: 600;
628
+ color: var(--text-muted);
629
+ text-transform: uppercase;
630
+ letter-spacing: 0.5px;
631
+ padding: 10px 14px;
632
+ border-bottom: 1px solid var(--border);
633
+ }
634
+ td {
635
+ padding: 12px 14px;
636
+ font-size: 13px;
637
+ border-bottom: 1px solid var(--border);
638
+ }
639
+ tr:last-child td {
640
+ border-bottom: none;
641
+ }
642
+ tr:hover td {
643
+ background: rgba(255, 255, 255, 0.02);
644
+ }
645
+
646
+ /* ── Schema Builder (full page) ── */
647
+ .schema-builder {
648
+ display: grid;
649
+ grid-template-columns: 1fr 340px;
650
+ gap: 20px;
651
+ min-height: calc(100vh - 160px);
652
+ }
653
+ .schema-builder-fields {
654
+ display: flex;
655
+ flex-direction: column;
656
+ }
657
+ .schema-builder-sidebar {
658
+ position: sticky;
659
+ top: 0;
660
+ align-self: start;
661
+ }
662
+ .field-table {
663
+ width: 100%;
664
+ border-collapse: collapse;
665
+ }
666
+ .field-table th {
667
+ text-align: left;
668
+ font-size: 10px;
669
+ font-weight: 600;
670
+ color: var(--text-muted);
671
+ text-transform: uppercase;
672
+ letter-spacing: 0.8px;
673
+ padding: 8px 12px;
674
+ border-bottom: 1px solid var(--border);
675
+ position: sticky;
676
+ top: 0;
677
+ background: var(--bg-card);
678
+ z-index: 1;
679
+ }
680
+ .field-table td {
681
+ padding: 6px 12px;
682
+ font-size: 13px;
683
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
684
+ vertical-align: middle;
685
+ }
686
+ .field-table tr:hover td {
687
+ background: rgba(255, 255, 255, 0.02);
688
+ }
689
+ .field-table .ft-num {
690
+ color: var(--text-muted);
691
+ font-size: 11px;
692
+ width: 32px;
693
+ font-variant-numeric: tabular-nums;
694
+ }
695
+ .field-table .ft-name {
696
+ font-weight: 500;
697
+ }
698
+ .field-table .ft-type {
699
+ color: var(--accent-light);
700
+ font-size: 12px;
701
+ }
702
+ .field-table .ft-toggle {
703
+ width: 28px;
704
+ height: 16px;
705
+ border-radius: 8px;
706
+ border: none;
707
+ cursor: pointer;
708
+ transition: all var(--transition);
709
+ }
710
+ .ft-toggle.on {
711
+ background: var(--success);
712
+ }
713
+ .ft-toggle.off {
714
+ background: rgba(255, 255, 255, 0.08);
715
+ }
716
+ .field-count {
717
+ font-size: 11px;
718
+ color: var(--text-muted);
719
+ font-variant-numeric: tabular-nums;
720
+ padding: 2px 8px;
721
+ background: rgba(255, 255, 255, 0.04);
722
+ border-radius: 20px;
723
+ }
724
+
725
+ /* Old field-row kept for schema cards in list view */
726
+ .field-row {
727
+ display: flex;
728
+ align-items: center;
729
+ gap: 10px;
730
+ padding: 8px 12px;
731
+ background: var(--bg-surface);
732
+ border: 1px solid var(--border);
733
+ border-radius: var(--radius);
734
+ margin-bottom: 4px;
735
+ font-size: 13px;
736
+ }
737
+ .field-row .field-name {
738
+ font-weight: 500;
739
+ min-width: 80px;
740
+ }
741
+ .field-row .field-type {
742
+ font-size: 12px;
743
+ color: var(--accent-light);
744
+ min-width: 60px;
745
+ }
746
+ .field-row .field-badges {
747
+ display: flex;
748
+ gap: 4px;
749
+ flex: 1;
750
+ }
751
+
752
+ /* ── Route Page Layout ── */
753
+ .routes-layout {
754
+ display: grid;
755
+ grid-template-columns: 340px 1fr;
756
+ gap: 20px;
757
+ min-height: calc(100vh - 160px);
758
+ }
759
+ .routes-list {
760
+ display: flex;
761
+ flex-direction: column;
762
+ gap: 0;
763
+ }
764
+ .routes-detail {
765
+ position: sticky;
766
+ top: 0;
767
+ align-self: start;
768
+ }
769
+ .route-item {
770
+ display: flex;
771
+ align-items: center;
772
+ gap: 10px;
773
+ padding: 12px 16px;
774
+ cursor: pointer;
775
+ border-left: 2px solid transparent;
776
+ transition: all var(--transition);
777
+ }
778
+ .route-item:hover {
779
+ background: rgba(255, 255, 255, 0.02);
780
+ }
781
+ .route-item.active {
782
+ background: var(--accent-glow);
783
+ border-left-color: var(--accent);
784
+ }
785
+ .route-item .ri-name {
786
+ font-weight: 500;
787
+ font-size: 13px;
788
+ flex: 1;
789
+ }
790
+ .route-item .ri-file {
791
+ font-size: 10px;
792
+ color: var(--text-muted);
793
+ }
794
+ .route-method {
795
+ font-size: 9px;
796
+ font-weight: 700;
797
+ letter-spacing: 0.5px;
798
+ padding: 2px 7px;
799
+ border-radius: 3px;
800
+ font-family: "SF Mono", "Fira Code", monospace;
801
+ text-align: center;
802
+ display: inline-block;
803
+ }
804
+ .route-method.get {
805
+ background: rgba(34, 197, 94, 0.15);
806
+ color: #22c55e;
807
+ }
808
+ .route-method.post {
809
+ background: rgba(59, 130, 246, 0.15);
810
+ color: #3b82f6;
811
+ }
812
+ .route-method.put {
813
+ background: rgba(245, 158, 11, 0.15);
814
+ color: #f59e0b;
815
+ }
816
+ .route-method.patch {
817
+ background: rgba(168, 85, 247, 0.15);
818
+ color: #a855f7;
819
+ }
820
+ .route-method.delete {
821
+ background: rgba(239, 68, 68, 0.15);
822
+ color: #ef4444;
823
+ }
824
+ .route-method.multi {
825
+ background: rgba(99, 102, 241, 0.15);
826
+ color: #818cf8;
827
+ }
828
+ .route-endpoint-row {
829
+ display: flex;
830
+ align-items: center;
831
+ gap: 10px;
832
+ padding: 10px 14px;
833
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
834
+ font-size: 13px;
835
+ }
836
+ .route-endpoint-row:last-child {
837
+ border-bottom: none;
838
+ }
839
+ .route-endpoint-path {
840
+ font-family: "SF Mono", "Fira Code", monospace;
841
+ font-size: 12px;
842
+ color: var(--text-secondary);
843
+ }
844
+ .route-code-wrap {
845
+ max-height: 400px;
846
+ overflow-y: auto;
847
+ border-radius: var(--radius);
848
+ border: 1px solid var(--border);
849
+ }
850
+ .route-code-wrap .code-block {
851
+ border: none;
852
+ border-radius: 0;
853
+ margin: 0;
854
+ font-size: 11px;
855
+ line-height: 1.6;
856
+ }
857
+
858
+ /* ── API Tester ── */
859
+ .api-tester-bar {
860
+ display: flex;
861
+ gap: 8px;
862
+ align-items: stretch;
863
+ margin-bottom: 16px;
864
+ }
865
+ .api-tester-method {
866
+ padding: 8px 12px;
867
+ border: 1px solid var(--border);
868
+ border-radius: var(--radius);
869
+ background: var(--bg-input);
870
+ color: var(--text-primary);
871
+ font-weight: 600;
872
+ font-size: 13px;
873
+ font-family: inherit;
874
+ cursor: pointer;
875
+ min-width: 100px;
876
+ }
877
+ .api-tester-url {
878
+ flex: 1;
879
+ padding: 8px 14px;
880
+ border: 1px solid var(--border);
881
+ border-radius: var(--radius);
882
+ background: var(--bg-input);
883
+ color: var(--text-primary);
884
+ font-family: 'SF Mono', 'Fira Code', monospace;
885
+ font-size: 13px;
886
+ outline: none;
887
+ transition: border-color 0.15s;
888
+ }
889
+ .api-tester-url:focus {
890
+ border-color: var(--accent);
891
+ }
892
+ .api-tester-url::placeholder {
893
+ color: var(--text-muted);
894
+ }
895
+ .api-tester-send {
896
+ padding: 8px 20px;
897
+ border: none;
898
+ border-radius: var(--radius);
899
+ background: var(--accent);
900
+ color: white;
901
+ font-weight: 600;
902
+ font-size: 13px;
903
+ font-family: inherit;
904
+ cursor: pointer;
905
+ display: flex;
906
+ align-items: center;
907
+ gap: 6px;
908
+ transition: opacity 0.15s;
909
+ }
910
+ .api-tester-send:hover {
911
+ opacity: 0.9;
912
+ }
913
+ .api-tester-send:disabled {
914
+ opacity: 0.5;
915
+ cursor: not-allowed;
916
+ }
917
+ .api-tester-tabs {
918
+ display: flex;
919
+ gap: 0;
920
+ border-bottom: 1px solid var(--border);
921
+ margin-bottom: 0;
922
+ }
923
+ .api-tester-tab {
924
+ padding: 8px 16px;
925
+ font-size: 12px;
926
+ font-weight: 500;
927
+ color: var(--text-muted);
928
+ border: none;
929
+ background: none;
930
+ cursor: pointer;
931
+ border-bottom: 2px solid transparent;
932
+ font-family: inherit;
933
+ transition: all 0.15s;
934
+ }
935
+ .api-tester-tab:hover {
936
+ color: var(--text-primary);
937
+ }
938
+ .api-tester-tab.active {
939
+ color: var(--accent-light);
940
+ border-bottom-color: var(--accent);
941
+ }
942
+ .api-tester-body-editor {
943
+ width: 100%;
944
+ min-height: 180px;
945
+ padding: 14px;
946
+ border: none;
947
+ outline: none;
948
+ font-family: 'SF Mono', 'Fira Code', monospace;
949
+ font-size: 12px;
950
+ line-height: 1.6;
951
+ background: var(--bg-card);
952
+ color: var(--text-primary);
953
+ resize: vertical;
954
+ tab-size: 2;
955
+ }
956
+ .api-tester-kv-row {
957
+ display: flex;
958
+ gap: 8px;
959
+ align-items: center;
960
+ padding: 6px 14px;
961
+ border-bottom: 1px solid var(--border);
962
+ }
963
+ .api-tester-kv-row input {
964
+ flex: 1;
965
+ padding: 6px 10px;
966
+ border: 1px solid var(--border);
967
+ border-radius: 4px;
968
+ background: var(--bg-input);
969
+ color: var(--text-primary);
970
+ font-size: 12px;
971
+ font-family: 'SF Mono', monospace;
972
+ outline: none;
973
+ }
974
+ .api-tester-kv-row input:focus {
975
+ border-color: var(--accent);
976
+ }
977
+ .api-tester-status {
978
+ display: inline-flex;
979
+ align-items: center;
980
+ padding: 3px 10px;
981
+ border-radius: 4px;
982
+ font-size: 12px;
983
+ font-weight: 600;
984
+ font-family: 'SF Mono', monospace;
985
+ }
986
+ .api-tester-status.s2xx { background: rgba(52,211,153,0.12); color: var(--success); }
987
+ .api-tester-status.s3xx { background: rgba(96,165,250,0.12); color: #60a5fa; }
988
+ .api-tester-status.s4xx { background: rgba(251,191,36,0.12); color: var(--warning); }
989
+ .api-tester-status.s5xx { background: rgba(248,113,113,0.12); color: var(--danger); }
990
+ .api-tester-status.s0xx { background: rgba(248,113,113,0.12); color: var(--danger); }
991
+ .api-tester-response-body {
992
+ padding: 14px;
993
+ font-family: 'SF Mono', 'Fira Code', monospace;
994
+ font-size: 12px;
995
+ line-height: 1.6;
996
+ white-space: pre-wrap;
997
+ word-break: break-word;
998
+ color: var(--text-secondary);
999
+ max-height: 400px;
1000
+ overflow-y: auto;
1001
+ }
1002
+ .api-tester-saved-item {
1003
+ padding: 10px 14px;
1004
+ cursor: pointer;
1005
+ border-bottom: 1px solid var(--border);
1006
+ transition: background 0.1s;
1007
+ }
1008
+ .api-tester-saved-item:hover {
1009
+ background: var(--bg-input);
1010
+ }
1011
+ .api-tester-saved-item.active {
1012
+ background: var(--accent-glow);
1013
+ border-left: 2px solid var(--accent);
1014
+ }
1015
+
1016
+ .api-tester-mode-tabs {
1017
+ display: inline-flex;
1018
+ gap: 2px;
1019
+ padding: 3px;
1020
+ background: var(--bg-input);
1021
+ border-radius: 8px;
1022
+ margin-bottom: 16px;
1023
+ }
1024
+ .api-tester-mode-tab {
1025
+ padding: 6px 16px;
1026
+ border-radius: 6px;
1027
+ border: none;
1028
+ cursor: pointer;
1029
+ display: flex;
1030
+ align-items: center;
1031
+ gap: 6px;
1032
+ font-size: 13px;
1033
+ font-weight: 400;
1034
+ font-family: inherit;
1035
+ background: transparent;
1036
+ color: var(--text-muted);
1037
+ transition: all 0.15s;
1038
+ }
1039
+ .api-tester-mode-tab.active {
1040
+ background: var(--bg-surface);
1041
+ color: var(--text-primary);
1042
+ font-weight: 600;
1043
+ box-shadow: 0 1px 3px rgba(0,0,0,0.2);
1044
+ }
1045
+ .ws-log {
1046
+ max-height: 350px;
1047
+ overflow-y: auto;
1048
+ font-family: 'SF Mono', 'Fira Code', monospace;
1049
+ font-size: 12px;
1050
+ line-height: 1.6;
1051
+ }
1052
+ .ws-log-entry {
1053
+ padding: 4px 14px;
1054
+ border-bottom: 1px solid var(--border);
1055
+ display: flex;
1056
+ gap: 8px;
1057
+ align-items: flex-start;
1058
+ }
1059
+ .ws-log-entry .dir {
1060
+ font-size: 10px;
1061
+ font-weight: 600;
1062
+ padding: 1px 5px;
1063
+ border-radius: 3px;
1064
+ flex-shrink: 0;
1065
+ margin-top: 2px;
1066
+ }
1067
+ .ws-log-entry .dir.sent {
1068
+ background: rgba(59,130,246,0.12);
1069
+ color: #60a5fa;
1070
+ }
1071
+ .ws-log-entry .dir.received {
1072
+ background: rgba(52,211,153,0.12);
1073
+ color: var(--success);
1074
+ }
1075
+ .ws-log-entry .dir.system {
1076
+ background: rgba(251,191,36,0.12);
1077
+ color: var(--warning);
1078
+ }
1079
+ .ws-log-entry .msg {
1080
+ white-space: pre-wrap;
1081
+ word-break: break-word;
1082
+ color: var(--text-secondary);
1083
+ }
1084
+ .ws-log-entry .ts {
1085
+ font-size: 10px;
1086
+ color: var(--text-muted);
1087
+ margin-left: auto;
1088
+ flex-shrink: 0;
1089
+ }
1090
+ .ws-connected {
1091
+ display: inline-flex;
1092
+ align-items: center;
1093
+ gap: 5px;
1094
+ font-size: 12px;
1095
+ font-weight: 500;
1096
+ }
1097
+ .ws-connected .dot {
1098
+ width: 7px;
1099
+ height: 7px;
1100
+ border-radius: 50%;
1101
+ }
1102
+ .ws-connected .dot.on { background: var(--success); }
1103
+ .ws-connected .dot.off { background: var(--text-muted); }
1104
+
1105
+ /* ── Pipeline/Flow ── */
1106
+ .pipeline {
1107
+ display: flex;
1108
+ align-items: center;
1109
+ gap: 0;
1110
+ margin-bottom: 32px;
1111
+ overflow-x: auto;
1112
+ padding: 8px 0;
1113
+ }
1114
+ .pipeline-step {
1115
+ display: flex;
1116
+ flex-direction: column;
1117
+ align-items: center;
1118
+ gap: 8px;
1119
+ min-width: 100px;
1120
+ padding: 16px;
1121
+ }
1122
+ .pipeline-dot {
1123
+ width: 40px;
1124
+ height: 40px;
1125
+ border-radius: 50%;
1126
+ display: flex;
1127
+ align-items: center;
1128
+ justify-content: center;
1129
+ font-size: 16px;
1130
+ border: 2px solid var(--border);
1131
+ background: var(--bg-card);
1132
+ transition: all 0.3s;
1133
+ }
1134
+ .pipeline-dot.done {
1135
+ border-color: var(--success);
1136
+ background: var(--success-glow);
1137
+ color: var(--success);
1138
+ }
1139
+ .pipeline-dot.current {
1140
+ border-color: var(--accent);
1141
+ background: var(--accent-glow);
1142
+ color: var(--accent-light);
1143
+ box-shadow: 0 0 20px var(--accent-glow);
1144
+ }
1145
+ .pipeline-label {
1146
+ font-size: 11px;
1147
+ font-weight: 500;
1148
+ color: var(--text-muted);
1149
+ text-align: center;
1150
+ }
1151
+ .pipeline-label.done {
1152
+ color: var(--success);
1153
+ }
1154
+ .pipeline-label.current {
1155
+ color: var(--accent-light);
1156
+ }
1157
+ .pipeline-connector {
1158
+ width: 40px;
1159
+ height: 2px;
1160
+ background: var(--border);
1161
+ flex-shrink: 0;
1162
+ margin-top: -24px;
1163
+ }
1164
+ .pipeline-connector.done {
1165
+ background: var(--success);
1166
+ }
1167
+
1168
+ /* ── Toast ── */
1169
+ .toast-container {
1170
+ position: fixed;
1171
+ top: 16px;
1172
+ right: 16px;
1173
+ z-index: 1000;
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ gap: 8px;
1177
+ }
1178
+ .toast {
1179
+ padding: 12px 16px;
1180
+ border-radius: var(--radius);
1181
+ font-size: 13px;
1182
+ font-weight: 500;
1183
+ background: var(--bg-card);
1184
+ border: 1px solid var(--border);
1185
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
1186
+ animation: slideIn 0.2s ease;
1187
+ max-width: 400px;
1188
+ }
1189
+ .toast.success {
1190
+ border-left: 3px solid var(--success);
1191
+ }
1192
+ .toast.error {
1193
+ border-left: 3px solid var(--error);
1194
+ }
1195
+ .toast.info {
1196
+ border-left: 3px solid var(--info);
1197
+ }
1198
+
1199
+ @keyframes slideIn {
1200
+ from {
1201
+ transform: translateX(100%);
1202
+ opacity: 0;
1203
+ }
1204
+ to {
1205
+ transform: translateX(0);
1206
+ opacity: 1;
1207
+ }
1208
+ }
1209
+
1210
+ /* ── Modal ── */
1211
+ .modal-overlay {
1212
+ position: fixed;
1213
+ inset: 0;
1214
+ background: rgba(0, 0, 0, 0.6);
1215
+ backdrop-filter: blur(4px);
1216
+ display: flex;
1217
+ align-items: center;
1218
+ justify-content: center;
1219
+ z-index: 900;
1220
+ animation: fadeIn 0.15s ease;
1221
+ }
1222
+ .modal {
1223
+ background: var(--bg-card);
1224
+ border: 1px solid var(--border);
1225
+ border-radius: var(--radius-xl);
1226
+ padding: 24px;
1227
+ min-width: 420px;
1228
+ max-width: 560px;
1229
+ max-height: 80vh;
1230
+ overflow-y: auto;
1231
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
1232
+ animation: scaleIn 0.15s ease;
1233
+ }
1234
+ .modal-title {
1235
+ font-size: 16px;
1236
+ font-weight: 600;
1237
+ margin-bottom: 16px;
1238
+ }
1239
+ .modal-actions {
1240
+ display: flex;
1241
+ gap: 8px;
1242
+ justify-content: flex-end;
1243
+ margin-top: 20px;
1244
+ }
1245
+
1246
+ @keyframes fadeIn {
1247
+ from {
1248
+ opacity: 0;
1249
+ }
1250
+ to {
1251
+ opacity: 1;
1252
+ }
1253
+ }
1254
+ @keyframes scaleIn {
1255
+ from {
1256
+ transform: scale(0.96);
1257
+ opacity: 0;
1258
+ }
1259
+ to {
1260
+ transform: scale(1);
1261
+ opacity: 1;
1262
+ }
1263
+ }
1264
+
1265
+ /* ── Code block ── */
1266
+ .code-block {
1267
+ background: #0d0d12;
1268
+ border: 1px solid var(--border);
1269
+ border-radius: var(--radius);
1270
+ padding: 16px;
1271
+ font-family: "SF Mono", "Fira Code", monospace;
1272
+ font-size: 12px;
1273
+ line-height: 1.6;
1274
+ overflow-x: auto;
1275
+ white-space: pre;
1276
+ color: var(--text-secondary);
1277
+ }
1278
+
1279
+ /* ── Checkbox ── */
1280
+ .checkbox-group {
1281
+ display: flex;
1282
+ flex-wrap: wrap;
1283
+ gap: 8px;
1284
+ }
1285
+ .checkbox-item {
1286
+ display: flex;
1287
+ align-items: center;
1288
+ gap: 8px;
1289
+ padding: 8px 14px;
1290
+ background: var(--bg-surface);
1291
+ border: 1px solid var(--border);
1292
+ border-radius: var(--radius);
1293
+ cursor: pointer;
1294
+ font-size: 13px;
1295
+ transition: all var(--transition);
1296
+ }
1297
+ .checkbox-item:hover {
1298
+ border-color: rgba(255, 255, 255, 0.12);
1299
+ }
1300
+ .checkbox-item.checked {
1301
+ border-color: var(--accent);
1302
+ background: var(--accent-glow);
1303
+ color: var(--accent-light);
1304
+ }
1305
+ .checkbox-item input {
1306
+ display: none;
1307
+ }
1308
+
1309
+ /* ── Utilities ── */
1310
+ .flex {
1311
+ display: flex;
1312
+ }
1313
+ .flex-col {
1314
+ flex-direction: column;
1315
+ }
1316
+ .items-center {
1317
+ align-items: center;
1318
+ }
1319
+ .justify-between {
1320
+ justify-content: space-between;
1321
+ }
1322
+ .gap-2 {
1323
+ gap: 8px;
1324
+ }
1325
+ .gap-3 {
1326
+ gap: 12px;
1327
+ }
1328
+ .gap-4 {
1329
+ gap: 16px;
1330
+ }
1331
+ .mb-2 {
1332
+ margin-bottom: 8px;
1333
+ }
1334
+ .mb-3 {
1335
+ margin-bottom: 12px;
1336
+ }
1337
+ .mb-4 {
1338
+ margin-bottom: 16px;
1339
+ }
1340
+ .mb-6 {
1341
+ margin-bottom: 24px;
1342
+ }
1343
+ .mt-4 {
1344
+ margin-top: 16px;
1345
+ }
1346
+ .text-sm {
1347
+ font-size: 12px;
1348
+ }
1349
+ .text-muted {
1350
+ color: var(--text-muted);
1351
+ }
1352
+ .text-secondary {
1353
+ color: var(--text-secondary);
1354
+ }
1355
+ .w-full {
1356
+ width: 100%;
1357
+ }
1358
+
1359
+ /* ── Spinner ── */
1360
+ .spinner {
1361
+ width: 16px;
1362
+ height: 16px;
1363
+ border: 2px solid rgba(255, 255, 255, 0.15);
1364
+ border-top-color: var(--accent-light);
1365
+ border-radius: 50%;
1366
+ animation: spin 0.6s linear infinite;
1367
+ }
1368
+ @keyframes spin {
1369
+ to {
1370
+ transform: rotate(360deg);
1371
+ }
1372
+ }
1373
+ @keyframes pulse {
1374
+ 0%, 100% { opacity: 1; }
1375
+ 50% { opacity: 0.7; }
1376
+ }
1377
+ .spin {
1378
+ animation: spin 0.6s linear infinite;
1379
+ }
1380
+
1381
+ /* ── Env editor ── */
1382
+ .env-editor {
1383
+ width: 100%;
1384
+ min-height: 280px;
1385
+ background: var(--bg-input);
1386
+ border: 1px solid var(--border);
1387
+ border-radius: var(--radius);
1388
+ color: var(--text-primary);
1389
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
1390
+ font-size: 13px;
1391
+ line-height: 1.6;
1392
+ padding: 14px 16px;
1393
+ resize: vertical;
1394
+ outline: none;
1395
+ transition: border-color 0.15s;
1396
+ }
1397
+ .env-editor:focus {
1398
+ border-color: var(--accent);
1399
+ }
1400
+
1401
+ /* ── Splash Screen ── */
1402
+ .splash {
1403
+ position: fixed;
1404
+ inset: 0;
1405
+ z-index: 9999;
1406
+ display: flex;
1407
+ flex-direction: column;
1408
+ align-items: center;
1409
+ justify-content: center;
1410
+ background: var(--bg);
1411
+ }
1412
+ .splash-logo {
1413
+ font-family: monospace;
1414
+ font-size: 10px;
1415
+ line-height: 1.15;
1416
+ letter-spacing: 1px;
1417
+ white-space: pre;
1418
+ background: linear-gradient(135deg, #00c6ff, #0072ff, #7b2ff7);
1419
+ -webkit-background-clip: text;
1420
+ -webkit-text-fill-color: transparent;
1421
+ user-select: none;
1422
+ text-align: center;
1423
+ }
1424
+ .splash-tagline {
1425
+ font-family: "Noto Sans", sans-serif;
1426
+ font-size: 16px;
1427
+ font-weight: 400;
1428
+ color: var(--text-secondary);
1429
+ letter-spacing: 0.5px;
1430
+ margin-top: 20px;
1431
+ }
1432
+ .splash-company {
1433
+ position: absolute;
1434
+ bottom: 36px;
1435
+ font-family: "Noto Sans", sans-serif;
1436
+ font-size: 12px;
1437
+ color: var(--text-muted);
1438
+ letter-spacing: 0.3px;
1439
+ }
1440
+ .splash-company span {
1441
+ font-weight: 600;
1442
+ background: linear-gradient(135deg, #00c6ff, #7b2ff7);
1443
+ -webkit-background-clip: text;
1444
+ -webkit-text-fill-color: transparent;
1445
+ }
1446
+ .splash-glow {
1447
+ position: absolute;
1448
+ width: 300px;
1449
+ height: 300px;
1450
+ border-radius: 50%;
1451
+ background: radial-gradient(circle, rgba(99,102,241,0.12) 0%, transparent 70%);
1452
+ pointer-events: none;
1453
+ }
1454
+
1455
+ /* ── Passkey Login Screen ── */
1456
+ .passkey-screen {
1457
+ position: fixed;
1458
+ inset: 0;
1459
+ z-index: 9999;
1460
+ display: flex;
1461
+ flex-direction: column;
1462
+ align-items: center;
1463
+ justify-content: center;
1464
+ background: var(--bg);
1465
+ }
1466
+ .passkey-card {
1467
+ display: flex;
1468
+ flex-direction: column;
1469
+ align-items: center;
1470
+ gap: 24px;
1471
+ padding: 48px 40px;
1472
+ background: var(--card);
1473
+ border: 1px solid var(--border);
1474
+ border-radius: 16px;
1475
+ min-width: 340px;
1476
+ }
1477
+ .passkey-card h2 {
1478
+ font-family: "Noto Sans", sans-serif;
1479
+ font-size: 20px;
1480
+ font-weight: 600;
1481
+ color: var(--text);
1482
+ margin: 0;
1483
+ }
1484
+ .passkey-card p {
1485
+ font-family: "Noto Sans", sans-serif;
1486
+ font-size: 13px;
1487
+ color: var(--text-secondary);
1488
+ margin: 0;
1489
+ }
1490
+ .passkey-inputs {
1491
+ display: flex;
1492
+ gap: 10px;
1493
+ }
1494
+ .passkey-inputs input {
1495
+ width: 44px;
1496
+ height: 52px;
1497
+ text-align: center;
1498
+ font-size: 22px;
1499
+ font-weight: 600;
1500
+ font-family: "Noto Sans", sans-serif;
1501
+ background: var(--bg);
1502
+ border: 1px solid var(--border);
1503
+ border-radius: 10px;
1504
+ color: var(--text);
1505
+ outline: none;
1506
+ transition: border-color 0.2s;
1507
+ }
1508
+ .passkey-inputs input:focus {
1509
+ border-color: #0072ff;
1510
+ box-shadow: 0 0 0 2px rgba(0, 114, 255, 0.15);
1511
+ }
1512
+ .passkey-inputs input.error {
1513
+ border-color: #ef4444;
1514
+ box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.15);
1515
+ }
1516
+ .passkey-error {
1517
+ font-family: "Noto Sans", sans-serif;
1518
+ font-size: 13px;
1519
+ color: #ef4444;
1520
+ min-height: 18px;
1521
+ }
1522
+ .passkey-btn {
1523
+ width: 100%;
1524
+ padding: 12px;
1525
+ font-family: "Noto Sans", sans-serif;
1526
+ font-size: 14px;
1527
+ font-weight: 600;
1528
+ color: #fff;
1529
+ background: linear-gradient(135deg, #0072ff, #7b2ff7);
1530
+ border: none;
1531
+ border-radius: 10px;
1532
+ cursor: pointer;
1533
+ transition: opacity 0.2s;
1534
+ }
1535
+ .passkey-btn:hover { opacity: 0.9; }
1536
+ .passkey-btn:disabled { opacity: 0.5; cursor: not-allowed; }
1537
+ </style>
1538
+ </head>
1539
+ <body>
1540
+ <div id="root"></div>
1541
+ <script type="importmap">
1542
+ {
1543
+ "imports": {
1544
+ "react": "https://esm.sh/react@18.3.1",
1545
+ "react/jsx-runtime": "https://esm.sh/react@18.3.1/jsx-runtime",
1546
+ "react-dom/client": "https://esm.sh/react-dom@18.3.1/client",
1547
+ "htm": "https://esm.sh/htm@3.1.1",
1548
+ "react-icons/lu": "https://esm.sh/react-icons@4.12.0/lu?external=react",
1549
+ "framer-motion": "https://esm.sh/framer-motion@11.0.3?external=react,react-dom",
1550
+ "socket.io-client": "https://esm.sh/socket.io-client@4.7.4"
1551
+ }
1552
+ }
1553
+ </script>
1554
+ <script type="module" src="/_dev/assets/js/main.js"></script>
1555
+ </body>
1556
+ </html>