flowtrace-omega 1.0.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.
@@ -0,0 +1,2045 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>FlowTrace Dashboard</title>
8
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link
12
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@500;600;700;800&display=swap"
13
+ rel="stylesheet">
14
+
15
+ <style>
16
+ :root {
17
+ --bg: #050505;
18
+ --sidebar-bg: #0a0a0b;
19
+ --surface: rgba(18, 18, 20, 0.7);
20
+ --surface-hover: rgba(26, 26, 28, 0.85);
21
+ --surface-elevated: #0f0f10;
22
+ --surface-border: rgba(255, 255, 255, 0.04);
23
+
24
+ --accent-orange: #fb8500;
25
+ --accent-orange-dim: rgba(251, 133, 0, 0.1);
26
+ --accent-amber: #ffb703;
27
+ --accent-emerald: #10b981;
28
+ --accent-rose: #ff4757;
29
+ --accent-java: #fb8500;
30
+
31
+ --border: rgba(255, 255, 255, 0.06);
32
+ --border-lit: rgba(255, 255, 255, 0.12);
33
+ --border-orange: rgba(251, 133, 0, 0.25);
34
+
35
+ --text: #f8fafc;
36
+ --text-muted: #94a3b8;
37
+ --text-dim: #64748b;
38
+
39
+ --font-ui: 'Inter', sans-serif;
40
+ --font-display: 'Outfit', sans-serif;
41
+ --font-mono: 'JetBrains Mono', monospace;
42
+
43
+ --radius-sm: 8px;
44
+ --radius-md: 12px;
45
+ --radius-lg: 20px;
46
+ --shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
47
+ }
48
+
49
+ [data-theme="dark"] {
50
+ --bg: #0b0e14;
51
+ --bg-gradient: radial-gradient(circle at top right, rgba(99, 102, 241, 0.15), transparent 40%),
52
+ radial-gradient(circle at bottom left, rgba(56, 189, 248, 0.08), transparent 40%);
53
+
54
+ --surface: rgba(22, 27, 34, 0.6);
55
+ --surface-hover: rgba(30, 41, 59, 0.8);
56
+ --surface-elevated: #161b22;
57
+ --surface-border: rgba(255, 255, 255, 0.1);
58
+
59
+ --accent-cyan: #38bdf8;
60
+ --accent-cyan-dim: rgba(56, 189, 248, 0.15);
61
+ --accent-violet: #818cf8;
62
+
63
+ --border: rgba(255, 255, 255, 0.08);
64
+ --border-lit: rgba(255, 255, 255, 0.15);
65
+ --border-cyan: rgba(56, 189, 248, 0.3);
66
+
67
+ --text: #f1f5f9;
68
+ --text-muted: #94a3b8;
69
+ --text-dim: #64748b;
70
+ --shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
71
+ }
72
+
73
+ *,
74
+ *::before,
75
+ *::after {
76
+ box-sizing: border-box;
77
+ margin: 0;
78
+ padding: 0;
79
+ }
80
+
81
+ body {
82
+ font-family: var(--font-ui);
83
+ background-color: var(--bg);
84
+ background-image:
85
+ linear-gradient(rgba(5, 5, 5, 0.11), rgba(5, 5, 5, 0.11)),
86
+ url('../../flowtrace-frontend/hero-bg.png');
87
+ background-size: cover;
88
+ background-attachment: fixed;
89
+ color: var(--text);
90
+ height: 100vh;
91
+ width: 100vw;
92
+ overflow: hidden;
93
+ display: grid;
94
+ grid-template-rows: 64px 1fr;
95
+ grid-template-columns: 340px 1fr;
96
+ grid-template-areas: "header header" "sidebar main";
97
+ }
98
+
99
+ body.tester-mode {
100
+ grid-template-columns: 1fr;
101
+ grid-template-areas: "header" "main";
102
+ }
103
+
104
+ @media (max-width: 992px) {
105
+ body {
106
+ grid-template-columns: 1fr;
107
+ grid-template-areas: "header" "main";
108
+ }
109
+
110
+ body.tester-mode {
111
+ grid-template-columns: 1fr;
112
+ grid-template-areas: "header" "main";
113
+ }
114
+
115
+ aside {
116
+ position: fixed;
117
+ top: 64px;
118
+ left: -320px;
119
+ bottom: 0;
120
+ width: 320px;
121
+ z-index: 1000;
122
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
123
+ box-shadow: 20px 0 50px rgba(0, 0, 0, 0.5);
124
+ }
125
+
126
+ aside.open {
127
+ transform: translateX(320px);
128
+ }
129
+
130
+ #sidebar-overlay {
131
+ position: fixed;
132
+ inset: 0;
133
+ background: rgba(0, 0, 0, 0.6);
134
+ backdrop-filter: blur(4px);
135
+ z-index: 999;
136
+ display: none;
137
+ }
138
+
139
+ #sidebar-overlay.open {
140
+ display: block;
141
+ }
142
+ }
143
+
144
+ header {
145
+ grid-area: header;
146
+ display: grid;
147
+ grid-template-columns: auto 1fr auto;
148
+ align-items: center;
149
+ padding: 0 24px;
150
+ border-bottom: 1px solid var(--border);
151
+ background: var(--surface);
152
+ backdrop-filter: blur(12px);
153
+ -webkit-backdrop-filter: blur(12px);
154
+ z-index: 100;
155
+ gap: 12px;
156
+ box-shadow: var(--shadow);
157
+ }
158
+
159
+ @media (max-width: 768px) {
160
+ header {
161
+ padding: 0 12px;
162
+ grid-template-columns: auto 1fr auto;
163
+ gap: 8px;
164
+ }
165
+
166
+ .hcenter {
167
+ display: none !important;
168
+ }
169
+
170
+ .hbtns {
171
+ gap: 4px;
172
+ }
173
+
174
+ .btn span {
175
+ display: none;
176
+ }
177
+
178
+ .btn {
179
+ padding: 8px 10px;
180
+ }
181
+
182
+ .logo-mark {
183
+ width: 28px;
184
+ height: 28px;
185
+ }
186
+
187
+ .logo {
188
+ font-size: 16px;
189
+ gap: 8px;
190
+ }
191
+ }
192
+
193
+ @media (max-width: 600px) {
194
+ .view-toggle {
195
+ display: none !important;
196
+ }
197
+
198
+ .sidebar-nav {
199
+ display: flex !important;
200
+ }
201
+ }
202
+
203
+ .bun-menu {
204
+ display: none;
205
+ flex-direction: column;
206
+ gap: 5px;
207
+ background: transparent;
208
+ border: none;
209
+ cursor: pointer;
210
+ padding: 10px;
211
+ margin-right: 12px;
212
+ }
213
+
214
+ .bun-bar {
215
+ width: 22px;
216
+ height: 2px;
217
+ background: var(--text);
218
+ border-radius: 2px;
219
+ transition: 0.3s;
220
+ }
221
+
222
+ @media (max-width: 992px) {
223
+ .bun-menu {
224
+ display: flex;
225
+ }
226
+ }
227
+
228
+ .logo {
229
+ display: flex;
230
+ align-items: center;
231
+ gap: 12px;
232
+ font-family: var(--font-display);
233
+ font-weight: 800;
234
+ font-size: 20px;
235
+ letter-spacing: -0.5px;
236
+ text-decoration: none;
237
+ color: var(--text);
238
+ }
239
+
240
+ .logo-mark {
241
+ width: 32px;
242
+ height: 32px;
243
+ background: linear-gradient(135deg, var(--accent-orange), var(--accent-amber));
244
+ border-radius: var(--radius-sm);
245
+ display: grid;
246
+ place-items: center;
247
+ font-size: 16px;
248
+ box-shadow: 0 4px 12px rgba(251, 133, 0, 0.3);
249
+ position: relative;
250
+ }
251
+
252
+ .logo-mark::after {
253
+ content: '';
254
+ position: absolute;
255
+ inset: -2px;
256
+ border-radius: inherit;
257
+ background: linear-gradient(135deg, var(--accent-orange), var(--accent-amber));
258
+ filter: blur(8px);
259
+ opacity: 0.5;
260
+ z-index: -1;
261
+ }
262
+
263
+ .logo em {
264
+ color: var(--text-muted);
265
+ font-style: normal;
266
+ font-weight: 500;
267
+ }
268
+
269
+ .hcenter {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 24px;
273
+ background: var(--surface);
274
+ padding: 6px 16px;
275
+ border-radius: 99px;
276
+ border: 1px solid var(--border);
277
+ }
278
+
279
+ .hstat {
280
+ font-size: 12px;
281
+ color: var(--text-muted);
282
+ font-weight: 500;
283
+ }
284
+
285
+ .hstat strong {
286
+ color: var(--text);
287
+ margin-left: 4px;
288
+ font-family: var(--font-mono);
289
+ }
290
+
291
+ .live {
292
+ display: flex;
293
+ align-items: center;
294
+ gap: 8px;
295
+ font-size: 11px;
296
+ font-weight: 600;
297
+ color: var(--accent-emerald);
298
+ letter-spacing: 0.5px;
299
+ text-transform: uppercase;
300
+ }
301
+
302
+ .dot {
303
+ width: 8px;
304
+ height: 8px;
305
+ border-radius: 50%;
306
+ background: var(--accent-emerald);
307
+ box-shadow: 0 0 8px var(--accent-emerald);
308
+ animation: blink 2s ease-in-out infinite;
309
+ }
310
+
311
+ @keyframes blink {
312
+
313
+ 0%,
314
+ 100% {
315
+ opacity: 1
316
+ }
317
+
318
+ 50% {
319
+ opacity: 0.4
320
+ }
321
+ }
322
+
323
+ .hbtns {
324
+ display: flex;
325
+ gap: 8px;
326
+ }
327
+
328
+ .btn {
329
+ padding: 8px 16px;
330
+ border-radius: var(--radius-sm);
331
+ border: 1px solid var(--border);
332
+ background: var(--surface-elevated);
333
+ color: var(--text-muted);
334
+ font-family: var(--font-ui);
335
+ font-weight: 500;
336
+ font-size: 12px;
337
+ cursor: pointer;
338
+ transition: all 0.2s;
339
+ display: flex;
340
+ align-items: center;
341
+ gap: 6px;
342
+ box-shadow: var(--shadow);
343
+ }
344
+
345
+ .btn:hover {
346
+ background: rgba(255, 255, 255, 0.06);
347
+ color: var(--text);
348
+ border-color: var(--border-lit);
349
+ }
350
+
351
+ .btn.d:hover {
352
+ border-color: var(--accent-rose);
353
+ color: var(--accent-rose);
354
+ background: rgba(244, 63, 94, 0.1);
355
+ box-shadow: 0 0 12px rgba(244, 63, 94, 0.2);
356
+ }
357
+
358
+ aside {
359
+ grid-area: sidebar;
360
+ border-right: 1px solid var(--border);
361
+ background: var(--sidebar-bg);
362
+ display: flex;
363
+ flex-direction: column;
364
+ overflow: hidden;
365
+ }
366
+
367
+ .sidebar-top {
368
+ padding: 16px;
369
+ border-bottom: 1px solid var(--border);
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 16px;
373
+ flex-shrink: 0;
374
+ }
375
+
376
+ .sidebar-nav {
377
+ display: none;
378
+ flex-direction: column;
379
+ gap: 8px;
380
+ margin-bottom: 8px;
381
+ }
382
+
383
+ .sidebar-nav .vt-btn {
384
+ width: 100%;
385
+ text-align: center;
386
+ border: 1px solid var(--border);
387
+ padding: 10px;
388
+ background: var(--surface);
389
+ }
390
+
391
+ .sidebar-nav .vt-btn.active {
392
+ border-color: var(--accent-orange);
393
+ background: var(--accent-orange-dim);
394
+ }
395
+
396
+ .filters {
397
+ display: flex;
398
+ gap: 6px;
399
+ flex-wrap: wrap;
400
+ }
401
+
402
+ .fc {
403
+ padding: 4px 10px;
404
+ border-radius: 99px;
405
+ font-size: 10px;
406
+ font-family: var(--font-mono);
407
+ font-weight: 600;
408
+ cursor: pointer;
409
+ border: 1px solid var(--border);
410
+ background: transparent;
411
+ color: var(--text-muted);
412
+ transition: all 0.2s;
413
+ }
414
+
415
+ .fc:hover {
416
+ border-color: var(--border-lit);
417
+ color: var(--text);
418
+ }
419
+
420
+ .fc.active {
421
+ background: var(--surface-hover);
422
+ border-color: var(--border-lit);
423
+ color: var(--text);
424
+ }
425
+
426
+ .fc[data-m="GET"].active {
427
+ background: rgba(16, 185, 129, 0.15);
428
+ border-color: var(--accent-emerald);
429
+ color: var(--accent-emerald);
430
+ }
431
+
432
+ .fc[data-m="POST"].active {
433
+ background: rgba(251, 133, 0, 0.15);
434
+ border-color: var(--accent-orange);
435
+ color: var(--accent-orange);
436
+ }
437
+
438
+ .fc[data-m="PUT"].active {
439
+ background: rgba(255, 183, 3, 0.15);
440
+ border-color: var(--accent-amber);
441
+ color: var(--accent-amber);
442
+ }
443
+
444
+ .fc[data-m="DELETE"].active {
445
+ background: rgba(255, 71, 87, 0.15);
446
+ border-color: var(--accent-rose);
447
+ color: var(--accent-rose);
448
+ }
449
+
450
+ .fc[data-m="JAVA"].active {
451
+ background: rgba(251, 133, 0, 0.15);
452
+ border-color: var(--accent-orange);
453
+ color: var(--accent-orange);
454
+ }
455
+
456
+ .h-right {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 16px;
460
+ justify-self: end;
461
+ }
462
+
463
+ .search {
464
+ width: 100%;
465
+ padding: 10px 14px;
466
+ background: var(--bg);
467
+ border: 1px solid var(--border);
468
+ border-radius: var(--radius-sm);
469
+ color: var(--text);
470
+ font-family: var(--font-ui);
471
+ font-size: 13px;
472
+ outline: none;
473
+ transition: all 0.2s;
474
+ }
475
+
476
+ .search::placeholder {
477
+ color: var(--text-dim);
478
+ }
479
+
480
+ .search:focus {
481
+ border-color: var(--accent-orange);
482
+ box-shadow: 0 0 0 2px var(--accent-orange-dim);
483
+ }
484
+
485
+ #trace-list {
486
+ flex: 1;
487
+ overflow-y: auto;
488
+ padding: 12px;
489
+ display: flex;
490
+ flex-direction: column;
491
+ gap: 6px;
492
+ }
493
+
494
+ .ti {
495
+ padding: 14px;
496
+ border-radius: var(--radius-md);
497
+ background: var(--surface-elevated);
498
+ border: 1px solid var(--border);
499
+ cursor: pointer;
500
+ transition: all 0.2s;
501
+ position: relative;
502
+ overflow: hidden;
503
+ animation: slideIn 0.3s ease-out backwards;
504
+ box-shadow: var(--shadow);
505
+ }
506
+
507
+ @keyframes slideIn {
508
+ from {
509
+ opacity: 0;
510
+ transform: translateY(10px);
511
+ }
512
+
513
+ to {
514
+ opacity: 1;
515
+ transform: translateY(0);
516
+ }
517
+ }
518
+
519
+ .ti:hover {
520
+ background: var(--surface-hover);
521
+ border-color: var(--border-lit);
522
+ transform: translateY(-1px);
523
+ box-shadow: var(--shadow);
524
+ }
525
+
526
+ .ti.active {
527
+ background: var(--surface-elevated);
528
+ border-color: var(--accent-orange);
529
+ box-shadow: var(--shadow);
530
+ }
531
+
532
+ .ti::before {
533
+ content: '';
534
+ position: absolute;
535
+ left: 0;
536
+ top: 0;
537
+ bottom: 0;
538
+ width: 3px;
539
+ background: transparent;
540
+ transition: background 0.2s;
541
+ }
542
+
543
+ .ti.active::before {
544
+ background: var(--accent-orange);
545
+ }
546
+
547
+ .ti.java-t::before {
548
+ background: var(--accent-orange);
549
+ }
550
+
551
+ .ti.err-t::before {
552
+ background: var(--accent-rose);
553
+ width: 4px;
554
+ }
555
+
556
+ .ti-top {
557
+ display: flex;
558
+ align-items: center;
559
+ justify-content: space-between;
560
+ margin-bottom: 8px;
561
+ }
562
+
563
+ .mb {
564
+ font-size: 10px;
565
+ font-weight: 700;
566
+ font-family: var(--font-mono);
567
+ padding: 3px 8px;
568
+ border-radius: 4px;
569
+ letter-spacing: 0.5px;
570
+ }
571
+
572
+ .mb-GET {
573
+ background: rgba(16, 185, 129, 0.15);
574
+ color: var(--accent-emerald);
575
+ }
576
+
577
+ .mb-POST {
578
+ background: rgba(251, 133, 0, 0.15);
579
+ color: var(--accent-orange);
580
+ }
581
+
582
+ .mb-PUT {
583
+ background: rgba(255, 183, 3, 0.15);
584
+ color: var(--accent-amber);
585
+ }
586
+
587
+ .mb-DELETE {
588
+ background: rgba(255, 71, 87, 0.15);
589
+ color: var(--accent-rose);
590
+ }
591
+
592
+ .mb-JAVA {
593
+ background: rgba(251, 133, 0, 0.15);
594
+ color: var(--accent-orange);
595
+ }
596
+
597
+ .ti-dur {
598
+ font-size: 12px;
599
+ font-family: var(--font-mono);
600
+ color: var(--text-muted);
601
+ font-weight: 500;
602
+ }
603
+
604
+ .ti-url {
605
+ font-size: 13px;
606
+ font-weight: 500;
607
+ color: var(--text);
608
+ margin-bottom: 6px;
609
+ white-space: nowrap;
610
+ overflow: hidden;
611
+ text-overflow: ellipsis;
612
+ line-height: 1.4;
613
+ }
614
+
615
+ .ti-meta {
616
+ display: flex;
617
+ align-items: center;
618
+ gap: 12px;
619
+ font-size: 11px;
620
+ color: var(--text-dim);
621
+ font-family: var(--font-mono);
622
+ }
623
+
624
+ .ti-meta span {
625
+ display: flex;
626
+ align-items: center;
627
+ gap: 4px;
628
+ }
629
+
630
+ .sok {
631
+ color: var(--accent-emerald);
632
+ }
633
+
634
+ .swarn {
635
+ color: var(--accent-amber);
636
+ }
637
+
638
+ .serr {
639
+ color: var(--accent-rose);
640
+ font-weight: 600;
641
+ }
642
+
643
+ .empty {
644
+ padding: 40px 20px;
645
+ text-align: center;
646
+ color: var(--text-dim);
647
+ font-size: 13px;
648
+ font-weight: 500;
649
+ line-height: 1.6;
650
+ }
651
+
652
+ main {
653
+ grid-area: main;
654
+ display: flex;
655
+ flex-direction: column;
656
+ overflow: hidden;
657
+ position: relative;
658
+ }
659
+
660
+ .toolbar {
661
+ padding: 16px 32px;
662
+ border-bottom: 1px solid var(--border);
663
+ background: rgba(18, 20, 28, 0.4);
664
+ backdrop-filter: blur(8px);
665
+ display: flex;
666
+ align-items: center;
667
+ justify-content: space-between;
668
+ flex-shrink: 0;
669
+ z-index: 10;
670
+ }
671
+
672
+ @media (max-width: 768px) {
673
+ .toolbar {
674
+ padding: 12px 16px;
675
+ flex-direction: column;
676
+ gap: 12px;
677
+ align-items: stretch;
678
+ }
679
+
680
+ .tbc {
681
+ font-size: 13px;
682
+ }
683
+
684
+ .tabs {
685
+ justify-content: space-around;
686
+ }
687
+ }
688
+
689
+ .tbc {
690
+ display: flex;
691
+ align-items: center;
692
+ gap: 12px;
693
+ font-size: 14px;
694
+ }
695
+
696
+ .tbc-sep {
697
+ color: var(--border-lit);
698
+ }
699
+
700
+ .tbc-id {
701
+ font-family: var(--font-mono);
702
+ color: var(--text-muted);
703
+ font-size: 12px;
704
+ }
705
+
706
+ .tabs {
707
+ display: flex;
708
+ gap: 4px;
709
+ background: rgba(0, 0, 0, 0.3);
710
+ border: 1px solid var(--border);
711
+ border-radius: var(--radius-sm);
712
+ padding: 4px;
713
+ }
714
+
715
+ .tab {
716
+ padding: 6px 16px;
717
+ border-radius: 4px;
718
+ font-size: 12px;
719
+ font-weight: 500;
720
+ font-family: var(--font-ui);
721
+ cursor: pointer;
722
+ color: var(--text-muted);
723
+ transition: all 0.2s;
724
+ background: transparent;
725
+ border: none;
726
+ }
727
+
728
+ .tab:hover {
729
+ color: var(--text);
730
+ }
731
+
732
+ .tab.active {
733
+ background: var(--accent-orange-dim);
734
+ color: var(--accent-orange);
735
+ border: 1px solid var(--border-orange);
736
+ }
737
+
738
+ .stats {
739
+ display: flex;
740
+ gap: 16px;
741
+ padding: 24px 32px;
742
+ flex-shrink: 0;
743
+ }
744
+
745
+ @media (max-width: 768px) {
746
+ .stats {
747
+ display: grid;
748
+ grid-template-columns: 1fr;
749
+ padding: 12px;
750
+ gap: 12px;
751
+ }
752
+
753
+ .sc {
754
+ padding: 16px;
755
+ }
756
+
757
+ .sc-val {
758
+ font-size: 24px;
759
+ }
760
+ }
761
+
762
+ .sc {
763
+ flex: 1;
764
+ padding: 20px 24px;
765
+ background: var(--surface-elevated);
766
+ border: 1px solid var(--border);
767
+ border-radius: var(--radius-md);
768
+ display: flex;
769
+ flex-direction: column;
770
+ gap: 6px;
771
+ box-shadow: var(--shadow);
772
+ transition: transform 0.3s, border-color 0.3s, background 0.3s;
773
+ }
774
+
775
+ .sc:hover {
776
+ transform: translateY(-2px);
777
+ border-color: var(--accent-orange);
778
+ background: var(--surface-hover);
779
+ }
780
+
781
+ .sc-label {
782
+ font-size: 10px;
783
+ font-weight: 700;
784
+ font-family: var(--font-ui);
785
+ letter-spacing: 1.5px;
786
+ text-transform: uppercase;
787
+ color: var(--text-dim);
788
+ }
789
+
790
+ .sc-val {
791
+ font-size: 28px;
792
+ font-weight: 700;
793
+ font-family: var(--font-display);
794
+ color: var(--text);
795
+ line-height: 1;
796
+ letter-spacing: -0.5px;
797
+ }
798
+
799
+ .sc-val.good {
800
+ color: var(--accent-emerald);
801
+ }
802
+
803
+ .sc-val.warn {
804
+ color: var(--accent-amber);
805
+ }
806
+
807
+ .sc-val.bad {
808
+ color: var(--accent-rose);
809
+ }
810
+
811
+ .sc-sub {
812
+ font-size: 12px;
813
+ font-family: var(--font-mono);
814
+ color: var(--text-muted);
815
+ overflow: hidden;
816
+ text-overflow: ellipsis;
817
+ white-space: nowrap;
818
+ }
819
+
820
+ .panel {
821
+ flex: 1;
822
+ overflow-y: auto;
823
+ padding: 0 32px 32px 32px;
824
+ }
825
+
826
+ @media (max-width: 768px) {
827
+ .panel {
828
+ padding: 0 12px 12px 12px;
829
+ }
830
+ }
831
+
832
+ #dv {
833
+ display: flex;
834
+ justify-content: center;
835
+ }
836
+
837
+ .dw {
838
+ width: 100%;
839
+ background: var(--surface-elevated);
840
+ border: 1px solid var(--border);
841
+ border-radius: var(--radius-lg);
842
+ padding: 40px;
843
+ overflow-x: auto;
844
+ box-shadow: var(--shadow);
845
+ }
846
+
847
+ @media (max-width: 768px) {
848
+ .dw {
849
+ padding: 20px;
850
+ }
851
+ }
852
+
853
+ .dw .mermaid {
854
+ display: flex;
855
+ justify-content: center;
856
+ min-width: 600px;
857
+ }
858
+
859
+ #wv,
860
+ #sv,
861
+ #jv {
862
+ display: none;
863
+ background: var(--surface-elevated);
864
+ border: 1px solid var(--border);
865
+ border-radius: var(--radius-lg);
866
+ padding: 24px;
867
+ box-shadow: var(--shadow);
868
+ }
869
+
870
+ #wv {
871
+ padding-top: 16px;
872
+ }
873
+
874
+ .wh {
875
+ display: grid;
876
+ grid-template-columns: 1fr 120px 80px 1.5fr;
877
+ gap: 16px;
878
+ padding: 12px 16px;
879
+ border-bottom: 1px solid var(--border);
880
+ font-size: 10px;
881
+ font-weight: 700;
882
+ letter-spacing: 1px;
883
+ text-transform: uppercase;
884
+ color: var(--text-dim);
885
+ }
886
+
887
+ @media (max-width: 768px) {
888
+
889
+ .wh,
890
+ .wr {
891
+ grid-template-columns: 1fr 80px;
892
+ }
893
+
894
+ .wh div:nth-child(2),
895
+ .wh div:nth-child(4),
896
+ .wr .wl.caller,
897
+ .wr .wtrack {
898
+ display: none;
899
+ }
900
+
901
+ #wv,
902
+ #sv,
903
+ #jv {
904
+ padding: 12px;
905
+ }
906
+ }
907
+
908
+ .wr:hover {
909
+ background: rgba(255, 255, 255, 0.02);
910
+ }
911
+
912
+ .wr:last-child {
913
+ border-bottom: none;
914
+ }
915
+
916
+ .wl {
917
+ color: var(--text);
918
+ overflow: hidden;
919
+ text-overflow: ellipsis;
920
+ white-space: nowrap;
921
+ }
922
+
923
+ .wl.caller {
924
+ color: var(--text-muted);
925
+ }
926
+
927
+ .wd {
928
+ color: var(--accent-amber);
929
+ font-weight: 500;
930
+ }
931
+
932
+ .wtrack {
933
+ position: relative;
934
+ height: 6px;
935
+ background: rgba(0, 0, 0, 0.3);
936
+ border-radius: 99px;
937
+ overflow: hidden;
938
+ }
939
+
940
+ .wbar {
941
+ position: absolute;
942
+ top: 0;
943
+ height: 100%;
944
+ border-radius: 99px;
945
+ }
946
+
947
+ .wbar.fast {
948
+ background: linear-gradient(90deg, var(--accent-emerald), #34d399);
949
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
950
+ }
951
+
952
+ .wbar.med {
953
+ background: linear-gradient(90deg, var(--accent-amber), #fbbf24);
954
+ box-shadow: 0 0 10px rgba(245, 158, 11, 0.5);
955
+ }
956
+
957
+ .wbar.slow {
958
+ background: linear-gradient(90deg, var(--accent-rose), #fb7185);
959
+ box-shadow: 0 0 10px rgba(244, 63, 94, 0.5);
960
+ }
961
+
962
+ .st {
963
+ width: 100%;
964
+ border-collapse: collapse;
965
+ font-size: 13px;
966
+ font-family: var(--font-mono);
967
+ table-layout: fixed;
968
+ }
969
+
970
+ .st th {
971
+ text-align: left;
972
+ padding: 12px 16px;
973
+ font-size: 10px;
974
+ font-weight: 700;
975
+ font-family: var(--font-ui);
976
+ letter-spacing: 1px;
977
+ text-transform: uppercase;
978
+ color: var(--text-dim);
979
+ border-bottom: 1px solid var(--border);
980
+ overflow: hidden;
981
+ text-overflow: ellipsis;
982
+ white-space: nowrap;
983
+ }
984
+
985
+ .st td {
986
+ padding: 14px 16px;
987
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
988
+ vertical-align: middle;
989
+ overflow: hidden;
990
+ text-overflow: ellipsis;
991
+ white-space: nowrap;
992
+ }
993
+
994
+ .st tr {
995
+ transition: background 0.2s;
996
+ }
997
+
998
+ .st tr:hover td {
999
+ background: rgba(255, 255, 255, 0.02);
1000
+ }
1001
+
1002
+ .earr {
1003
+ color: var(--text-dim);
1004
+ }
1005
+
1006
+ .ebadge {
1007
+ font-size: 10px;
1008
+ background: rgba(244, 63, 94, 0.15);
1009
+ color: var(--accent-rose);
1010
+ padding: 4px 8px;
1011
+ border-radius: 4px;
1012
+ font-family: var(--font-ui);
1013
+ font-weight: 500;
1014
+ display: inline-block;
1015
+ max-width: 100%;
1016
+ overflow: hidden;
1017
+ text-overflow: ellipsis;
1018
+ vertical-align: middle;
1019
+ }
1020
+
1021
+ .st-wrap {
1022
+ width: 100%;
1023
+ overflow-x: auto;
1024
+ }
1025
+
1026
+ .jb {
1027
+ background: #0f172a;
1028
+ border: 1px solid #1e293b;
1029
+ border-radius: var(--radius-md);
1030
+ padding: 24px;
1031
+ font-size: 13px;
1032
+ font-family: var(--font-mono);
1033
+ line-height: 1.6;
1034
+ color: #94a3b8;
1035
+ white-space: pre;
1036
+ overflow-x: auto;
1037
+ }
1038
+
1039
+ #welcome {
1040
+ flex: 1;
1041
+ display: flex;
1042
+ flex-direction: column;
1043
+ align-items: center;
1044
+ justify-content: center;
1045
+ gap: 24px;
1046
+ padding: 64px;
1047
+ text-align: center;
1048
+ height: 100%;
1049
+ }
1050
+
1051
+ .wg {
1052
+ font-size: 80px;
1053
+ background: linear-gradient(135deg, var(--accent-orange), var(--accent-amber));
1054
+ background-clip: text;
1055
+ -webkit-background-clip: text;
1056
+ -webkit-text-fill-color: transparent;
1057
+ line-height: 1;
1058
+ filter: drop-shadow(0 0 32px rgba(251, 133, 0, 0.4));
1059
+ animation: float 6s ease-in-out infinite;
1060
+ }
1061
+
1062
+ @keyframes float {
1063
+
1064
+ 0%,
1065
+ 100% {
1066
+ transform: translateY(0);
1067
+ }
1068
+
1069
+ 50% {
1070
+ transform: translateY(-10px);
1071
+ }
1072
+ }
1073
+
1074
+ .wt {
1075
+ font-family: var(--font-display);
1076
+ font-size: 36px;
1077
+ font-weight: 800;
1078
+ letter-spacing: -1px;
1079
+ color: var(--text);
1080
+ }
1081
+
1082
+ .ws {
1083
+ font-size: 16px;
1084
+ color: var(--text-muted);
1085
+ max-width: 480px;
1086
+ line-height: 1.6;
1087
+ }
1088
+
1089
+ .cb {
1090
+ background: var(--surface-elevated);
1091
+ border: 1px solid var(--border-lit);
1092
+ border-radius: var(--radius-lg);
1093
+ padding: 24px 32px;
1094
+ font-size: 14px;
1095
+ text-align: left;
1096
+ color: var(--accent-orange);
1097
+ line-height: 1.8;
1098
+ font-family: var(--font-mono);
1099
+ backdrop-filter: blur(8px);
1100
+ box-shadow: var(--shadow);
1101
+ }
1102
+
1103
+ .cb .cm {
1104
+ color: var(--text-dim);
1105
+ }
1106
+
1107
+ .ck {
1108
+ color: var(--accent-amber);
1109
+ }
1110
+
1111
+ .wsteps {
1112
+ display: flex;
1113
+ gap: 20px;
1114
+ margin-top: 16px;
1115
+ }
1116
+
1117
+ @media (max-width: 768px) {
1118
+ #welcome {
1119
+ padding: 32px 16px;
1120
+ }
1121
+
1122
+ .wt {
1123
+ font-size: 24px;
1124
+ }
1125
+
1126
+ .wg {
1127
+ font-size: 50px;
1128
+ }
1129
+
1130
+ .wsteps {
1131
+ flex-direction: column;
1132
+ width: 100%;
1133
+ }
1134
+
1135
+ .wstep {
1136
+ min-width: 0;
1137
+ }
1138
+ }
1139
+
1140
+ .wstep {
1141
+ background: var(--surface-elevated);
1142
+ border: 1px solid var(--border);
1143
+ border-radius: var(--radius-md);
1144
+ padding: 20px 24px;
1145
+ text-align: left;
1146
+ min-width: 200px;
1147
+ backdrop-filter: blur(8px);
1148
+ transition: transform 0.3s, border-color 0.3s;
1149
+ box-shadow: var(--shadow);
1150
+ }
1151
+
1152
+ .wstep:hover {
1153
+ transform: translateY(-4px);
1154
+ border-color: var(--accent-orange);
1155
+ }
1156
+
1157
+ .wstep-n {
1158
+ font-size: 10px;
1159
+ color: var(--accent-orange);
1160
+ font-weight: 700;
1161
+ letter-spacing: 1px;
1162
+ text-transform: uppercase;
1163
+ margin-bottom: 8px;
1164
+ }
1165
+
1166
+ .wstep-t {
1167
+ color: var(--text);
1168
+ font-weight: 600;
1169
+ font-size: 16px;
1170
+ margin-bottom: 6px;
1171
+ }
1172
+
1173
+ .wstep-d {
1174
+ color: var(--text-muted);
1175
+ line-height: 1.5;
1176
+ font-size: 13px;
1177
+ }
1178
+
1179
+ ::-webkit-scrollbar {
1180
+ width: 6px;
1181
+ height: 6px;
1182
+ }
1183
+
1184
+ ::-webkit-scrollbar-track {
1185
+ background: transparent;
1186
+ }
1187
+
1188
+ ::-webkit-scrollbar-thumb {
1189
+ background: var(--border-lit);
1190
+ border-radius: 99px;
1191
+ }
1192
+
1193
+ ::-webkit-scrollbar-thumb:hover {
1194
+ background: var(--text-dim);
1195
+ }
1196
+
1197
+ .mermaid rect {
1198
+ rx: 8;
1199
+ ry: 8;
1200
+ }
1201
+
1202
+ .mermaid .actor {
1203
+ transition: all 0.3s;
1204
+ }
1205
+
1206
+ .mermaid .actor:hover {
1207
+ filter: brightness(1.2);
1208
+ }
1209
+
1210
+ .view-toggle {
1211
+ display: flex;
1212
+ background: var(--surface);
1213
+ border: 1px solid var(--border);
1214
+ border-radius: 99px;
1215
+ padding: 4px;
1216
+ justify-self: center;
1217
+ }
1218
+
1219
+ .vt-btn {
1220
+ background: transparent;
1221
+ border: none;
1222
+ padding: 6px 16px;
1223
+ border-radius: 99px;
1224
+ color: var(--text-muted);
1225
+ font-family: var(--font-ui);
1226
+ font-weight: 600;
1227
+ font-size: 13px;
1228
+ cursor: pointer;
1229
+ transition: all 0.2s;
1230
+ }
1231
+
1232
+ .vt-btn:hover {
1233
+ color: var(--text);
1234
+ }
1235
+
1236
+ .vt-btn.active {
1237
+ background: var(--surface-elevated);
1238
+ color: var(--accent-orange);
1239
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
1240
+ }
1241
+
1242
+ #api-tester-view {
1243
+ grid-area: main;
1244
+ display: none;
1245
+ flex-direction: column;
1246
+ padding: 24px;
1247
+ gap: 20px;
1248
+ height: calc(100vh - 84px);
1249
+ overflow: hidden;
1250
+ }
1251
+
1252
+ .at-bar {
1253
+ display: flex;
1254
+ gap: 12px;
1255
+ background: var(--surface-elevated);
1256
+ padding: 12px;
1257
+ border-radius: var(--radius-lg);
1258
+ border: 1px solid var(--border);
1259
+ box-shadow: var(--shadow);
1260
+ }
1261
+
1262
+ .at-method {
1263
+ background: var(--bg);
1264
+ border: 1px solid var(--border);
1265
+ border-radius: var(--radius-sm);
1266
+ color: var(--accent-orange);
1267
+ font-family: var(--font-mono);
1268
+ font-weight: 700;
1269
+ padding: 0 16px;
1270
+ font-size: 14px;
1271
+ outline: none;
1272
+ cursor: pointer;
1273
+ }
1274
+
1275
+ .at-url {
1276
+ flex: 1;
1277
+ background: var(--bg);
1278
+ border: 1px solid var(--border);
1279
+ border-radius: var(--radius-sm);
1280
+ padding: 12px 16px;
1281
+ color: var(--text);
1282
+ font-family: var(--font-mono);
1283
+ font-size: 14px;
1284
+ outline: none;
1285
+ transition: border-color 0.2s;
1286
+ }
1287
+
1288
+ .at-url:focus {
1289
+ border-color: var(--accent-orange);
1290
+ }
1291
+
1292
+ .at-btn {
1293
+ background: linear-gradient(135deg, var(--accent-orange), var(--accent-amber));
1294
+ color: #050505;
1295
+ border: none;
1296
+ border-radius: var(--radius-sm);
1297
+ padding: 0 32px;
1298
+ font-family: var(--font-ui);
1299
+ font-weight: 800;
1300
+ font-size: 14px;
1301
+ cursor: pointer;
1302
+ box-shadow: 0 4px 16px rgba(251, 133, 0, 0.2);
1303
+ transition: all 0.2s;
1304
+ }
1305
+
1306
+ .at-btn:hover {
1307
+ transform: translateY(-1px);
1308
+ box-shadow: 0 6px 20px rgba(251, 133, 0, 0.4);
1309
+ filter: brightness(1.1);
1310
+ }
1311
+
1312
+ .at-btn:active {
1313
+ transform: translateY(1px);
1314
+ }
1315
+
1316
+ .at-panes {
1317
+ display: flex;
1318
+ gap: 20px;
1319
+ flex: 1;
1320
+ min-height: 0;
1321
+ }
1322
+
1323
+ .at-pane {
1324
+ position: relative;
1325
+ flex: 1;
1326
+ display: flex;
1327
+ flex-direction: column;
1328
+ background: var(--bg);
1329
+ border: 1px solid var(--border);
1330
+ border-radius: var(--radius-lg);
1331
+ overflow: hidden;
1332
+ }
1333
+
1334
+ .at-pane::before {
1335
+ content: '';
1336
+ position: absolute;
1337
+ inset: -2px;
1338
+ background: linear-gradient(45deg, var(--accent-orange), transparent, var(--accent-amber), transparent);
1339
+ background-size: 400% 400%;
1340
+ z-index: -1;
1341
+ filter: blur(15px);
1342
+ opacity: 0.15;
1343
+ transition: opacity 0.3s;
1344
+ animation: active-glow 8s linear infinite;
1345
+ }
1346
+
1347
+ .at-pane:focus-within::before {
1348
+ opacity: 0.6;
1349
+ filter: blur(20px);
1350
+ }
1351
+
1352
+ @keyframes active-glow {
1353
+ 0% {
1354
+ background-position: 0% 50%;
1355
+ }
1356
+
1357
+ 50% {
1358
+ background-position: 100% 50%;
1359
+ }
1360
+
1361
+ 100% {
1362
+ background-position: 0% 50%;
1363
+ }
1364
+ }
1365
+
1366
+ @media (max-width: 768px) {
1367
+ .at-panes {
1368
+ flex-direction: column;
1369
+ }
1370
+
1371
+ .at-bar {
1372
+ flex-direction: column;
1373
+ align-items: stretch;
1374
+ height: auto;
1375
+ }
1376
+
1377
+ .at-method {
1378
+ height: 44px;
1379
+ }
1380
+ }
1381
+
1382
+ .at-pane {
1383
+ flex: 1;
1384
+ display: flex;
1385
+ flex-direction: column;
1386
+ background: var(--surface-elevated);
1387
+ border: 1px solid var(--border);
1388
+ border-radius: var(--radius-lg);
1389
+ overflow: hidden;
1390
+ box-shadow: var(--shadow);
1391
+ }
1392
+
1393
+ .at-pane-head {
1394
+ display: flex;
1395
+ justify-content: space-between;
1396
+ align-items: center;
1397
+ padding: 12px 20px;
1398
+ border-bottom: 1px solid var(--border);
1399
+ background: var(--bg);
1400
+ }
1401
+
1402
+ .at-pane-title {
1403
+ font-family: var(--font-display);
1404
+ font-weight: 600;
1405
+ font-size: 14px;
1406
+ color: var(--text);
1407
+ }
1408
+
1409
+ .at-pane-body {
1410
+ flex: 1;
1411
+ display: flex;
1412
+ flex-direction: column;
1413
+ }
1414
+
1415
+ .at-textarea {
1416
+ flex: 1;
1417
+ width: 100%;
1418
+ background: transparent;
1419
+ border: none;
1420
+ color: var(--accent-cyan);
1421
+ font-family: var(--font-mono);
1422
+ font-size: 13px;
1423
+ padding: 20px;
1424
+ outline: none;
1425
+ resize: none;
1426
+ line-height: 1.5;
1427
+ }
1428
+
1429
+ .at-res-info {
1430
+ display: flex;
1431
+ gap: 16px;
1432
+ font-family: var(--font-mono);
1433
+ font-size: 12px;
1434
+ color: var(--text-muted);
1435
+ }
1436
+
1437
+ .at-res-stat[data-s="ok"] {
1438
+ color: var(--accent-emerald);
1439
+ }
1440
+
1441
+ .at-res-stat[data-s="err"] {
1442
+ color: var(--accent-rose);
1443
+ }
1444
+
1445
+ .at-res-json {
1446
+ color: var(--text-muted);
1447
+ }
1448
+ </style>
1449
+ </head>
1450
+
1451
+ <body>
1452
+
1453
+ <header>
1454
+ <button class="bun-menu" onclick="toggleSidebar()">
1455
+ <div class="bun-bar"></div>
1456
+ <div class="bun-bar"></div>
1457
+ <div class="bun-bar"></div>
1458
+ </button>
1459
+ <a class="logo" href="/">
1460
+ <div class="logo-mark">⚡</div>Flow<em>Trace</em>
1461
+ </a>
1462
+
1463
+ <div class="view-toggle">
1464
+ <button class="vt-btn active" id="btn-view-observer" onclick="switchMainView('observer')">Observer</button>
1465
+ <button class="vt-btn" id="btn-view-tester" onclick="switchMainView('tester')">API Tester</button>
1466
+ </div>
1467
+
1468
+ <div class="h-right">
1469
+ <div class="hcenter">
1470
+ <span class="hstat">Traces <strong><span id="h-total">0</span></strong></span>
1471
+ <span class="hstat">Avg <strong><span id="h-avg">—</span></strong></span>
1472
+ <span class="hstat">Errors <strong id="h-errors" style="color:inherit">0</strong></span>
1473
+ <div class="live">
1474
+ <div class="dot"></div><span id="h-time">Live</span>
1475
+ </div>
1476
+ </div>
1477
+ <div class="hbtns">
1478
+ <button class="btn" onclick="toggleTheme()" id="theme-btn" title="Toggle Light/Dark Theme">
1479
+ <svg id="moon-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
1480
+ stroke-width="2">
1481
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
1482
+ </svg>
1483
+ <svg id="sun-icon" style="display:none" width="14" height="14" viewBox="0 0 24 24" fill="none"
1484
+ stroke="currentColor" stroke-width="2">
1485
+ <circle cx="12" cy="12" r="5"></circle>
1486
+ <line x1="12" y1="1" x2="12" y2="3"></line>
1487
+ <line x1="12" y1="21" x2="12" y2="23"></line>
1488
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
1489
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
1490
+ <line x1="1" y1="12" x2="3" y2="12"></line>
1491
+ <line x1="21" y1="12" x2="23" y2="12"></line>
1492
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
1493
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
1494
+ </svg>
1495
+ </button>
1496
+ <button class="btn" onclick="loadTraces()">
1497
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1498
+ <path d="M21 2v6h-6" />
1499
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
1500
+ </svg>
1501
+ Refresh
1502
+ </button>
1503
+ <button class="btn d" onclick="clearAll()">
1504
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1505
+ <line x1="18" y1="6" x2="6" y2="18"></line>
1506
+ <line x1="6" y1="6" x2="18" y2="18"></line>
1507
+ </svg>
1508
+ Clear
1509
+ </button>
1510
+ </div>
1511
+ </div>
1512
+ </header>
1513
+
1514
+ <div id="sidebar-overlay" onclick="toggleSidebar()"></div>
1515
+ <aside id="sidebar">
1516
+ <div class="sidebar-top">
1517
+ <div class="sidebar-nav">
1518
+ <button class="vt-btn active" id="m-view-observer" onclick="switchMainView('observer')">Observer Mode</button>
1519
+ <button class="vt-btn" id="m-view-tester" onclick="switchMainView('tester')">API Tester</button>
1520
+ </div>
1521
+ <div class="filters" id="filters">
1522
+ <div class="fc active" data-m="ALL" onclick="setFilter('ALL')">ALL</div>
1523
+ <div class="fc" data-m="GET" onclick="setFilter('GET')">GET</div>
1524
+ <div class="fc" data-m="POST" onclick="setFilter('POST')">POST</div>
1525
+ <div class="fc" data-m="PUT" onclick="setFilter('PUT')">PUT</div>
1526
+ <div class="fc" data-m="DELETE" onclick="setFilter('DELETE')">DELETE</div>
1527
+ <div class="fc" data-m="JAVA" onclick="setFilter('JAVA')">☕ JAVA</div>
1528
+ </div>
1529
+ <input class="search" id="search" placeholder="Filter traces by URL…" oninput="renderSidebar()">
1530
+ </div>
1531
+ <div id="trace-list">
1532
+ <div class="empty">No traces yet.<br>Awaiting requests...</div>
1533
+ </div>
1534
+ </aside>
1535
+
1536
+ <main>
1537
+ <div id="welcome">
1538
+ <div class="wg">◈</div>
1539
+ <div class="wt">Mission Control Ready</div>
1540
+ <div class="ws">Awaiting telemetry from your distributed services. Generate traffic to see real-time Mermaid
1541
+ diagrams and trace lifecycles.</div>
1542
+ <div class="cb">
1543
+ <span class="cm">// Node.js SDK Initialization</span><br>
1544
+ <span class="ck">const</span> FlowTrace = require(<span
1545
+ style="color:var(--accent-amber)">"flowtrace"</span>);<br>
1546
+ FlowTrace.<span style="color:var(--accent-amber)">start</span>({ serviceName: <span
1547
+ style="color:var(--accent-amber)">'api-gateway'</span> });<br><br>
1548
+ <span class="cm">// Java Agent JVM Attachment</span><br>
1549
+ <span class="ck">java</span> -javaagent:flowtrace.jar -jar app.jar
1550
+ </div>
1551
+ <div class="wsteps">
1552
+ <div class="wstep">
1553
+ <div class="wstep-n">Step 1</div>
1554
+ <div class="wstep-t">Instrument</div>
1555
+ <div class="wstep-d">Add the SDK or Agent to your service.</div>
1556
+ </div>
1557
+ <div class="wstep">
1558
+ <div class="wstep-n">Step 2</div>
1559
+ <div class="wstep-t">Generate</div>
1560
+ <div class="wstep-d">Trigger API calls via the Tester or Client.</div>
1561
+ </div>
1562
+ <div class="wstep">
1563
+ <div class="wstep-n">Step 3</div>
1564
+ <div class="wstep-t">Observe</div>
1565
+ <div class="wstep-d">Analyze flows in high-contrast sequence.</div>
1566
+ </div>
1567
+ </div>
1568
+ <div style="margin-top:40px;display:flex;flex-direction:column;gap:12px;align-items:center;">
1569
+ <p style="color:var(--text-dim);font-size:14px;font-weight:500;">Just want to explore the toolset first?</p>
1570
+ <button class="btn"
1571
+ style="border-color:var(--accent-orange);color:var(--accent-orange);background:var(--accent-orange-dim);padding:12px 24px"
1572
+ onclick="switchMainView('tester')">Skip to API Tester ⚡</button>
1573
+ </div>
1574
+ </div>
1575
+
1576
+ <div class="toolbar" id="toolbar" style="display:none">
1577
+ <div class="tbc">
1578
+ <span id="tb-mb" class="mb"></span>
1579
+ <span id="tb-url" style="color:var(--text); font-weight: 600;"></span>
1580
+ <span class="tbc-sep">/</span>
1581
+ <span class="tbc-id" id="tb-id"></span>
1582
+ </div>
1583
+ <div class="tabs">
1584
+ <button class="tab active" onclick="switchTab('diagram')">Diagram</button>
1585
+ <button class="tab" onclick="switchTab('waterfall')">Waterfall</button>
1586
+ <button class="tab" onclick="switchTab('spans')">Spans</button>
1587
+ <button class="tab" onclick="switchTab('json')">JSON</button>
1588
+ </div>
1589
+ </div>
1590
+
1591
+ <div class="stats" id="statsbar" style="display:none">
1592
+ <div class="sc">
1593
+ <div class="sc-label">Total Duration</div>
1594
+ <div class="sc-val" id="s-dur">—</div>
1595
+ <div class="sc-sub">end-to-end latency</div>
1596
+ </div>
1597
+ <div class="sc">
1598
+ <div class="sc-label">Functions</div>
1599
+ <div class="sc-val" id="s-spans">—</div>
1600
+ <div class="sc-sub">spans captured</div>
1601
+ </div>
1602
+ <div class="sc">
1603
+ <div class="sc-label">Slowest Execution</div>
1604
+ <div class="sc-val" id="s-slow">—</div>
1605
+ <div class="sc-sub" id="s-slow-n">—</div>
1606
+ </div>
1607
+ <div class="sc">
1608
+ <div class="sc-label">Final Status</div>
1609
+ <div class="sc-val" id="s-status">—</div>
1610
+ <div class="sc-sub" id="s-src">—</div>
1611
+ </div>
1612
+ </div>
1613
+
1614
+ <div class="panel">
1615
+ <div id="dv">
1616
+ <div class="dw">
1617
+ <div class="mermaid" id="mmd"></div>
1618
+ </div>
1619
+ </div>
1620
+ <div id="wv">
1621
+ <div class="wh">
1622
+ <div>Component.Method</div>
1623
+ <div>Caller</div>
1624
+ <div>Duration</div>
1625
+ <div>Timeline Execution</div>
1626
+ </div>
1627
+ <div id="wrows"></div>
1628
+ </div>
1629
+ <div id="sv">
1630
+ <div class="st-wrap">
1631
+ <table class="st">
1632
+ <thead>
1633
+ <tr>
1634
+ <th>#</th>
1635
+ <th>Origin</th>
1636
+ <th></th>
1637
+ <th>Target</th>
1638
+ <th>Method</th>
1639
+ <th>Latency</th>
1640
+ <th>Exception</th>
1641
+ </tr>
1642
+ </thead>
1643
+ <tbody id="stbody"></tbody>
1644
+ </table>
1645
+ </div>
1646
+ </div>
1647
+ <div id="jv">
1648
+ <pre class="jb" id="jout"></pre>
1649
+ </div>
1650
+ </div>
1651
+ </main>
1652
+
1653
+ <div id="api-tester-view">
1654
+ <div class="at-bar">
1655
+ <select class="at-method" id="at-method">
1656
+ <option value="GET">GET</option>
1657
+ <option value="POST" selected>POST</option>
1658
+ <option value="PUT">PUT</option>
1659
+ <option value="DELETE">DELETE</option>
1660
+ <option value="PATCH">PATCH</option>
1661
+ </select>
1662
+ <input type="text" class="at-url" id="at-url" placeholder="http://localhost:3000/api/users"
1663
+ value="http://localhost:3000/">
1664
+ <button class="at-btn" onclick="executeApiTest()">Send Request</button>
1665
+ </div>
1666
+
1667
+ <div class="at-panes">
1668
+ <div class="at-pane">
1669
+ <div class="at-pane-head">
1670
+ <div class="at-pane-title">Request Body</div>
1671
+ <div class="tabs">
1672
+ <button class="tab active" onclick="switchAtTab('body')">JSON</button>
1673
+ <button class="tab" onclick="switchAtTab('headers')">Headers</button>
1674
+ </div>
1675
+ </div>
1676
+ <div class="at-pane-body">
1677
+ <textarea id="at-body" class="at-textarea"
1678
+ placeholder="{&#10; &quot;name&quot;: &quot;FlowTrace&quot;&#10;}"></textarea>
1679
+ <textarea id="at-headers" class="at-textarea" style="display:none"
1680
+ placeholder="{&#10; &quot;Authorization&quot;: &quot;Bearer token&quot;&#10;}"></textarea>
1681
+ </div>
1682
+ </div>
1683
+
1684
+ <div class="at-pane">
1685
+ <div class="at-pane-head">
1686
+ <div class="at-pane-title">Response</div>
1687
+ <div class="at-res-info" id="at-res-info" style="display:none">
1688
+ <span class="at-res-stat" id="at-res-stat">200 OK</span>
1689
+ <span id="at-res-time">45ms</span>
1690
+ </div>
1691
+ </div>
1692
+ <div class="at-pane-body">
1693
+ <textarea id="at-res-json" class="at-textarea" readonly placeholder="Response will appear here..."></textarea>
1694
+ </div>
1695
+ </div>
1696
+ </div>
1697
+ </div>
1698
+
1699
+ <script>
1700
+ let allTraces = [], selected = null, activeTab = 'diagram', activeFilter = 'ALL';
1701
+
1702
+ async function loadTraces() {
1703
+ try {
1704
+ allTraces = await (await fetch('/traces')).json();
1705
+ headerStats(); renderSidebar();
1706
+ document.getElementById('h-time').textContent = new Date().toLocaleTimeString();
1707
+ if (selected) { const f = allTraces.find(t => t.traceId === selected.traceId); if (f) renderDetail(f); }
1708
+ } catch (e) { }
1709
+ }
1710
+
1711
+ async function clearAll() {
1712
+ try { await fetch('/traces', { method: 'DELETE' }); } catch (e) { }
1713
+ allTraces = []; selected = null; headerStats(); renderSidebar(); showWelcome();
1714
+ }
1715
+
1716
+ setInterval(loadTraces, 2500); loadTraces();
1717
+
1718
+ function headerStats() {
1719
+ document.getElementById('h-total').textContent = allTraces.length;
1720
+ const errs = allTraces.filter(t => t.statusCode >= 400).length;
1721
+ const errEl = document.getElementById('h-errors');
1722
+ errEl.textContent = errs;
1723
+ errEl.style.color = errs > 0 ? 'var(--accent-rose)' : 'inherit';
1724
+ if (allTraces.length) {
1725
+ const avg = allTraces.reduce((s, t) => s + (t.totalDuration || 0), 0) / allTraces.length;
1726
+ document.getElementById('h-avg').textContent = avg.toFixed(0) + 'ms';
1727
+ } else { document.getElementById('h-avg').textContent = '—'; }
1728
+ }
1729
+
1730
+ function setFilter(m) {
1731
+ activeFilter = m;
1732
+ document.querySelectorAll('.fc').forEach(c => c.classList.toggle('active', c.dataset.m === m));
1733
+ renderSidebar();
1734
+ }
1735
+
1736
+ function renderSidebar() {
1737
+ const q = document.getElementById('search').value.toLowerCase();
1738
+ const list = document.getElementById('trace-list');
1739
+ const items = allTraces.filter(t => {
1740
+ const method = t.source === 'java' ? 'JAVA' : (t.method || 'GET');
1741
+ if (activeFilter !== 'ALL' && method !== activeFilter) return false;
1742
+ if (q && !t.url?.toLowerCase().includes(q)) return false;
1743
+ if (t.url?.includes('favicon.ico')) return false;
1744
+ return true;
1745
+ });
1746
+ items.sort((a, b) => b.startTime - a.startTime);
1747
+
1748
+ if (!items.length) { list.innerHTML = '<div class="empty">No traces to display.<br><span style="font-size:11px;opacity:0.6;display:block;margin-top:8px">Try adjusting filters</span></div>'; return; }
1749
+ list.innerHTML = items.map(t => {
1750
+ const method = t.source === 'java' ? 'JAVA' : (t.method || 'GET');
1751
+ const sc = t.statusCode >= 500 ? 'serr' : t.statusCode >= 400 ? 'swarn' : 'sok';
1752
+ return `<div class="ti ${selected?.traceId === t.traceId ? 'active' : ''} ${t.source === 'java' ? 'java-t' : ''} ${t.statusCode >= 400 ? 'err-t' : ''}" onclick="selectTrace('${t.traceId}')">
1753
+ <div class="ti-top"><span class="mb mb-${method}">${method}</span><span class="ti-dur">${t.totalDuration != null ? t.totalDuration + 'ms' : '—'}</span></div>
1754
+ <div class="ti-url" title="${t.url || '/'}">${t.url || '/'}</div>
1755
+ <div class="ti-meta"><span class="${sc}"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" style="margin-right:3px"><circle cx="12" cy="12" r="10"></circle></svg>${t.statusCode || '—'}</span><span><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:3px"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>${t.spanCount || 0} spans</span><span>${new Date(t.startTime).toLocaleTimeString()}</span></div>
1756
+ </div>`;
1757
+ }).join('');
1758
+ }
1759
+
1760
+ function selectTrace(id) {
1761
+ selected = allTraces.find(t => t.traceId === id);
1762
+ if (!selected) return;
1763
+ renderSidebar(); renderDetail(selected);
1764
+ }
1765
+
1766
+ function durClass(ms) { return !ms ? '' : ms < 50 ? 'good' : ms < 200 ? 'warn' : 'bad'; }
1767
+
1768
+ async function renderDetail(t) {
1769
+ selected = t;
1770
+ if (window.innerWidth <= 992) {
1771
+ document.getElementById('sidebar').classList.remove('open');
1772
+ }
1773
+ document.getElementById('welcome').style.display = 'none';
1774
+ document.getElementById('toolbar').style.display = 'flex';
1775
+ document.getElementById('statsbar').style.display = 'flex';
1776
+ const method = t.source === 'java' ? 'JAVA' : (t.method || 'GET');
1777
+ document.getElementById('tb-mb').textContent = method;
1778
+ document.getElementById('tb-mb').className = `mb mb-${method}`;
1779
+ document.getElementById('tb-url').textContent = t.url || '/';
1780
+ document.getElementById('tb-id').textContent = t.traceId.slice(0, 16) + '…';
1781
+ document.getElementById('s-dur').textContent = (t.totalDuration || 0) + 'ms';
1782
+ document.getElementById('s-dur').className = 'sc-val ' + durClass(t.totalDuration);
1783
+ document.getElementById('s-spans').textContent = t.spanCount || 0;
1784
+ document.getElementById('s-status').textContent = t.statusCode || '—';
1785
+ document.getElementById('s-status').className = 'sc-val ' + (t.statusCode >= 400 ? 'bad' : 'good');
1786
+ document.getElementById('s-src').textContent = t.source === 'java' ? '☕ Java Agent Telemetry' : '⚡ Node.js Interceptor';
1787
+ const spans = t.spans || [];
1788
+ if (spans.length) {
1789
+ const sl = spans.reduce((a, b) => (b.duration || 0) > (a.duration || 0) ? b : a, spans[0]);
1790
+ document.getElementById('s-slow').textContent = (sl.duration || 0) + 'ms';
1791
+ document.getElementById('s-slow').className = 'sc-val ' + durClass(sl.duration);
1792
+ document.getElementById('s-slow-n').textContent = (sl.label || sl.targetClass || '?') + '.' + (sl.method || sl.methodName || '?');
1793
+ } else {
1794
+ document.getElementById('s-slow').textContent = '—';
1795
+ document.getElementById('s-slow').className = 'sc-val';
1796
+ document.getElementById('s-slow-n').textContent = 'No spans';
1797
+ }
1798
+ renderDiagram(t); renderWaterfall(t); renderSpans(t); renderJSON(t);
1799
+ switchTab(activeTab);
1800
+ }
1801
+
1802
+ function showWelcome() {
1803
+ document.getElementById('welcome').style.display = 'flex';
1804
+ ['toolbar', 'statsbar', 'dv', 'wv', 'sv', 'jv'].forEach(id => document.getElementById(id).style.display = 'none');
1805
+ }
1806
+
1807
+ function renderDiagram(t) {
1808
+ const el = document.getElementById('mmd');
1809
+ el.removeAttribute('data-processed');
1810
+ el.innerHTML = buildMermaid(t);
1811
+ try { mermaid.run({ nodes: [el] }); } catch (e) { console.error("Mermaid error", e); }
1812
+ }
1813
+
1814
+ function san(s) { return (s || 'Unknown').replace(/[^a-zA-Z0-9_\-]/g, ''); }
1815
+
1816
+ function buildMermaid(t) {
1817
+ const spans = t.spans || [];
1818
+ if (!spans.length) return `sequenceDiagram\n participant HTTP\n Note over HTTP: No function spans recorded`;
1819
+ const parts = new Set();
1820
+ const entry = san(spans[0]?.callerLabel || spans[0]?.className || 'Controller');
1821
+ parts.add('Client'); parts.add(entry);
1822
+ spans.forEach(s => {
1823
+ if (s.callerLabel || s.className) parts.add(san(s.callerLabel || s.className));
1824
+ if (s.label || s.targetClass) parts.add(san(s.label || s.targetClass));
1825
+ });
1826
+ let d = 'sequenceDiagram\n autonumber\n';
1827
+ parts.forEach(p => { d += ` participant ${p}\n`; });
1828
+ d += ` Client->>${entry}: ⚡ ${t.method} ${t.url}\n activate ${entry}\n`;
1829
+ spans.forEach(s => {
1830
+ const from = san(s.callerLabel || s.className || 'Controller');
1831
+ const to = san(s.label || s.targetClass || 'Service');
1832
+ const argStr = (s.args || []).join(', ').replace(/"/g, "'").replace(/\n/g, " ");
1833
+ const m = (s.method || s.methodName || 'invoke') + '(' + (argStr.length > 30 ? argStr.slice(0, 30) + '…' : argStr) + ')';
1834
+ d += ` ${from}->>${to}: ${m}\n activate ${to}\n`;
1835
+ if (s.error) {
1836
+ d += ` Note right of ${to}: ❌ Exception\n`;
1837
+ d += ` ${to}--x${from}: ✕ ${String(s.error).replace(/"/g, "'").slice(0, 35)}\n deactivate ${to}\n`;
1838
+ } else {
1839
+ d += ` Note right of ${to}: ⏱️ ${s.duration != null ? s.duration : 0}ms\n`;
1840
+ d += ` ${to}-->>${from}: ✓ Return\n deactivate ${to}\n`;
1841
+ }
1842
+ });
1843
+ d += ` ${entry}-->>Client: ${t.statusCode || 200} OK\n deactivate ${entry}\n`;
1844
+ return d;
1845
+ }
1846
+
1847
+ function renderWaterfall(t) {
1848
+ const spans = t.spans || [];
1849
+ if (!spans.length) { document.getElementById('wrows').innerHTML = '<div style="padding:40px;text-align:center;color:var(--text-dim);">No functional spans.</div>'; return; }
1850
+ const maxD = spans.reduce((m, s) => Math.max(m, s.duration || 0), 1);
1851
+ document.getElementById('wrows').innerHTML = spans.map((s, i) => {
1852
+ const pct = Math.min(((s.duration || 0) / maxD * 100), 100).toFixed(1);
1853
+ const cls = (s.duration || 0) < 50 ? 'fast' : (s.duration || 0) < 200 ? 'med' : 'slow';
1854
+ return `<div class="wr">
1855
+ <div class="wl" title="${san(s.label || s.targetClass || '?')}.${s.method || s.methodName || '?'}">${san(s.label || s.targetClass || '?')}.<span style="color:var(--text);font-weight:600;">${s.method || s.methodName || '?'}</span></div>
1856
+ <div class="wl caller">${s.callerLabel || s.className || '—'}</div>
1857
+ <div class="wd">${s.duration != null ? s.duration + 'ms' : '—'}</div>
1858
+ <div class="wtrack"><div class="wbar ${cls}" style="width:${pct}%"></div></div>
1859
+ </div>`;
1860
+ }).join('');
1861
+ }
1862
+
1863
+ function renderSpans(t) {
1864
+ const spans = t.spans || [];
1865
+ document.getElementById('stbody').innerHTML = !spans.length
1866
+ ? `<tr><td colspan="7" style="text-align:center;color:var(--text-dim);padding:40px">No structured spans</td></tr>`
1867
+ : spans.map((s, i) => `<tr style="${s.error ? 'background:rgba(244,63,94,0.05)' : ''}">
1868
+ <td style="color:var(--text-muted)">${i + 1}</td>
1869
+ <td style="color:var(--text-dim)">${s.callerLabel || s.className || '—'}</td>
1870
+ <td class="earr">→</td>
1871
+ <td style="color:var(--text);font-weight:500">${s.label || s.targetClass || '—'}</td>
1872
+ <td style="color:var(--accent-cyan);font-weight:600">${s.method || s.methodName || '—'}</td>
1873
+ <td style="color:${(s.duration || 0) > 200 ? 'var(--accent-rose)' : (s.duration || 0) > 50 ? 'var(--accent-amber)' : 'var(--accent-emerald)'}">${s.duration != null ? s.duration + 'ms' : '—'}</td>
1874
+ <td>${s.error ? `<span class="ebadge">${s.error.slice(0, 60)}</span>` : '<span style="color:var(--text-dim)">—</span>'}</td>
1875
+ </tr>`).join('');
1876
+ }
1877
+
1878
+ function renderJSON(t) { document.getElementById('jout').textContent = JSON.stringify(t, null, 2); }
1879
+
1880
+ function switchTab(tab) {
1881
+ activeTab = tab;
1882
+ document.querySelectorAll('.toolbar .tab').forEach((el, i) => el.classList.toggle('active', ['diagram', 'waterfall', 'spans', 'json'][i] === tab));
1883
+ document.getElementById('dv').style.display = tab === 'diagram' ? 'flex' : 'none';
1884
+ document.getElementById('wv').style.display = tab === 'waterfall' ? 'block' : 'none';
1885
+ document.getElementById('sv').style.display = tab === 'spans' ? 'block' : 'none';
1886
+ document.getElementById('jv').style.display = tab === 'json' ? 'block' : 'none';
1887
+ }
1888
+
1889
+ function switchMainView(view) {
1890
+ const isObserver = view === 'observer';
1891
+ ['btn-view-observer', 'm-view-observer'].forEach(id => {
1892
+ const el = document.getElementById(id);
1893
+ if (el) el.classList.toggle('active', isObserver);
1894
+ });
1895
+ ['btn-view-tester', 'm-view-tester'].forEach(id => {
1896
+ const el = document.getElementById(id);
1897
+ if (el) el.classList.toggle('active', !isObserver);
1898
+ });
1899
+
1900
+ document.body.classList.toggle('tester-mode', !isObserver);
1901
+ const aside = document.getElementById('sidebar');
1902
+ aside.style.display = isObserver ? 'flex' : 'none';
1903
+ if (window.innerWidth <= 992) {
1904
+ aside.style.display = 'flex';
1905
+ aside.classList.remove('open');
1906
+ const o = document.getElementById('sidebar-overlay');
1907
+ if (o) o.classList.remove('open');
1908
+ }
1909
+ const mainArea = document.querySelector('main');
1910
+ if (mainArea) mainArea.style.display = isObserver ? 'flex' : 'none';
1911
+ const testerView = document.getElementById('api-tester-view');
1912
+ if (testerView) testerView.style.display = isObserver ? 'none' : 'flex';
1913
+ }
1914
+
1915
+ function toggleSidebar() {
1916
+ const s = document.getElementById('sidebar');
1917
+ const o = document.getElementById('sidebar-overlay');
1918
+ s.classList.toggle('open');
1919
+ if (o) o.classList.toggle('open');
1920
+ }
1921
+
1922
+ function switchAtTab(tab) {
1923
+ const isBody = tab === 'body';
1924
+ document.querySelectorAll('#api-tester-view .tabs .tab')[0].classList.toggle('active', isBody);
1925
+ document.querySelectorAll('#api-tester-view .tabs .tab')[1].classList.toggle('active', !isBody);
1926
+ document.getElementById('at-body').style.display = isBody ? 'block' : 'none';
1927
+ document.getElementById('at-headers').style.display = isBody ? 'none' : 'block';
1928
+ }
1929
+
1930
+ async function executeApiTest() {
1931
+ const btn = document.querySelector('.at-btn');
1932
+ const oriText = btn.textContent;
1933
+ btn.textContent = "Sending...";
1934
+ btn.style.opacity = "0.7";
1935
+
1936
+ const url = document.getElementById('at-url').value.trim();
1937
+ const method = document.getElementById('at-method').value;
1938
+ const bodyRaw = document.getElementById('at-body').value.trim();
1939
+ const headersRaw = document.getElementById('at-headers').value.trim();
1940
+
1941
+ let body = null;
1942
+ let headers = {};
1943
+
1944
+ try { if (bodyRaw) body = JSON.parse(bodyRaw); } catch (e) { body = bodyRaw; }
1945
+ try { if (headersRaw) headers = JSON.parse(headersRaw); } catch (e) { }
1946
+ if (!headers['Content-Type'] && typeof body === 'object') {
1947
+ headers['Content-Type'] = 'application/json';
1948
+ }
1949
+
1950
+ try {
1951
+ const res = await fetch('/api/proxy', {
1952
+ method: 'POST',
1953
+ headers: { 'Content-Type': 'application/json' },
1954
+ body: JSON.stringify({ url, method, headers, body })
1955
+ });
1956
+ const json = await res.json();
1957
+
1958
+ document.getElementById('at-res-info').style.display = 'flex';
1959
+
1960
+ const st = document.getElementById('at-res-stat');
1961
+ st.textContent = `${json.status} ${json.statusText}`;
1962
+ st.dataset.s = json.status < 400 ? 'ok' : 'err';
1963
+
1964
+ document.getElementById('at-res-time').textContent = `${json.latency}ms`;
1965
+ document.getElementById('at-res-json').value = JSON.stringify(json.data || json.error, null, 2);
1966
+ } catch (err) {
1967
+ document.getElementById('at-res-info').style.display = 'flex';
1968
+ document.getElementById('at-res-stat').textContent = 'Proxy Error';
1969
+ document.getElementById('at-res-stat').dataset.s = 'err';
1970
+ document.getElementById('at-res-json').value = err.message;
1971
+ } finally {
1972
+ btn.textContent = oriText;
1973
+ btn.style.opacity = "1";
1974
+ }
1975
+ }
1976
+
1977
+ function initTheme() {
1978
+ const saved = localStorage.getItem('flowtrace-theme') || 'light';
1979
+ document.documentElement.setAttribute('data-theme', saved);
1980
+ updateThemeUI(saved);
1981
+ }
1982
+
1983
+ function toggleTheme() {
1984
+ const current = document.documentElement.getAttribute('data-theme') || 'light';
1985
+ const next = current === 'light' ? 'dark' : 'light';
1986
+ document.documentElement.setAttribute('data-theme', next);
1987
+ localStorage.setItem('flowtrace-theme', next);
1988
+ updateThemeUI(next);
1989
+ initMermaid(next);
1990
+ if (selected) renderDiagram(selected);
1991
+ }
1992
+
1993
+ function updateThemeUI(theme) {
1994
+ const isDark = theme === 'dark';
1995
+ document.getElementById('sun-icon').style.display = isDark ? 'inline' : 'none';
1996
+ document.getElementById('moon-icon').style.display = isDark ? 'none' : 'inline';
1997
+ }
1998
+
1999
+ function initMermaid(theme) {
2000
+ const isDark = theme === 'dark';
2001
+ mermaid.initialize({
2002
+ startOnLoad: false,
2003
+ theme: 'base',
2004
+ themeVariables: {
2005
+ fontFamily: 'Inter, sans-serif',
2006
+ primaryColor: '#1a1a1b',
2007
+ primaryTextColor: '#f8fafc',
2008
+ primaryBorderColor: '#2d2d30',
2009
+ lineColor: '#fb8500',
2010
+ secondaryColor: '#0f0f10',
2011
+ tertiaryColor: '#121214',
2012
+ sequenceNumberColor: '#ffb703',
2013
+ actorBkg: '#0f0f10',
2014
+ actorBorder: '#fb8500',
2015
+ actorTextColor: '#f8fafc',
2016
+ actorLineColor: '#2d2d30',
2017
+ signalColor: '#94a3b8',
2018
+ signalTextColor: '#f8fafc',
2019
+ noteBkgColor: '#26262b',
2020
+ noteTextColor: '#ffb703',
2021
+ noteBorderColor: '#3d3d42',
2022
+ activationBorderColor: '#fb8500',
2023
+ activationBkgColor: '#fb8500',
2024
+ sequenceNumberBkgColor: '#1a1a1b'
2025
+ },
2026
+ sequence: {
2027
+ actorMargin: 60,
2028
+ messageMargin: 36,
2029
+ useMaxWidth: true,
2030
+ showSequenceNumbers: true,
2031
+ activationWidth: 10
2032
+ }
2033
+ });
2034
+ }
2035
+
2036
+ function toggleSidebar() {
2037
+ document.getElementById('sidebar').classList.toggle('open');
2038
+ }
2039
+
2040
+ initTheme();
2041
+ initMermaid(document.documentElement.getAttribute('data-theme') || 'light');
2042
+ </script>
2043
+ </body>
2044
+
2045
+ </html>