@ryanstark24/sfgraph-web 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1063 @@
1
+ /* ============================================================
2
+ sfgraph explorer — design tokens
3
+ ============================================================ */
4
+ :root {
5
+ /* surface */
6
+ --bg-0: #05070d;
7
+ --bg-1: #080c18;
8
+ --bg-2: rgba(18, 26, 46, 0.55);
9
+ --bg-3: rgba(28, 38, 64, 0.7);
10
+ --stroke: rgba(120, 160, 255, 0.12);
11
+ --stroke-strong: rgba(120, 160, 255, 0.28);
12
+ --stroke-hot: rgba(94, 236, 255, 0.6);
13
+
14
+ /* type */
15
+ --fg: #e8edff;
16
+ --fg-dim: #aab4d4;
17
+ --fg-mute: #6b7798;
18
+
19
+ /* accents */
20
+ --cyan: #5eecff;
21
+ --cyan-deep: #1c8ab0;
22
+ --magenta: #ff5ec8;
23
+ --lime: #b9ff5e;
24
+ --amber: #ffb86b;
25
+
26
+ /* per-node-label colours (used in WebGL + legend) */
27
+ --c-apex: #ff8a4c;
28
+ --c-lwc: #5eecff;
29
+ --c-flow: #b794f4;
30
+ --c-field: #4ade80;
31
+ --c-object: #f5d76e;
32
+ --c-profile: #ff5ec8;
33
+ --c-perm: #ff5ec8;
34
+ --c-cred: #b9ff5e;
35
+ --c-other: #8b95b8;
36
+
37
+ /* spacing scale */
38
+ --s-1: 4px;
39
+ --s-2: 8px;
40
+ --s-3: 12px;
41
+ --s-4: 16px;
42
+ --s-5: 24px;
43
+ --s-6: 32px;
44
+ --s-7: 48px;
45
+
46
+ /* radii */
47
+ --r-1: 8px;
48
+ --r-2: 12px;
49
+ --r-3: 18px;
50
+ --r-full: 999px;
51
+
52
+ /* shadows */
53
+ --shadow-glass: 0 12px 40px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
54
+ --shadow-pop: 0 24px 60px rgba(0, 0, 0, 0.65), inset 0 1px 0 rgba(255, 255, 255, 0.08);
55
+
56
+ /* transitions */
57
+ --ease: cubic-bezier(0.16, 1, 0.3, 1);
58
+ }
59
+
60
+ * {
61
+ box-sizing: border-box;
62
+ }
63
+ html,
64
+ body {
65
+ margin: 0;
66
+ padding: 0;
67
+ height: 100%;
68
+ background: var(--bg-0);
69
+ color: var(--fg);
70
+ font: 14px/1.5 "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
71
+ font-feature-settings: "cv02", "cv03", "cv04", "cv11", "ss01";
72
+ -webkit-font-smoothing: antialiased;
73
+ -moz-osx-font-smoothing: grayscale;
74
+ overflow: hidden;
75
+ }
76
+ button {
77
+ font: inherit;
78
+ color: inherit;
79
+ }
80
+ input,
81
+ select {
82
+ font: inherit;
83
+ color: inherit;
84
+ }
85
+ kbd {
86
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
87
+ font-size: 11px;
88
+ padding: 2px 5px;
89
+ background: rgba(255, 255, 255, 0.06);
90
+ border: 1px solid var(--stroke);
91
+ border-radius: 4px;
92
+ color: var(--fg-dim);
93
+ }
94
+
95
+ /* ============================================================
96
+ ambient backdrop — animated gradient blobs + film grain
97
+ ============================================================ */
98
+ .ambient {
99
+ position: fixed;
100
+ inset: 0;
101
+ z-index: 0;
102
+ pointer-events: none;
103
+ overflow: hidden;
104
+ background:
105
+ radial-gradient(ellipse 1200px 800px at 80% -10%, rgba(94, 236, 255, 0.08), transparent 60%),
106
+ radial-gradient(ellipse 1000px 700px at 10% 100%, rgba(255, 94, 200, 0.06), transparent 60%),
107
+ linear-gradient(180deg, #05070d 0%, #080c18 100%);
108
+ }
109
+ .blob {
110
+ position: absolute;
111
+ border-radius: 50%;
112
+ filter: blur(80px);
113
+ opacity: 0.4;
114
+ mix-blend-mode: screen;
115
+ animation: drift 24s var(--ease) infinite alternate;
116
+ }
117
+ .blob-a {
118
+ width: 600px;
119
+ height: 600px;
120
+ background: radial-gradient(circle, rgba(94, 236, 255, 0.35), transparent 70%);
121
+ top: -200px;
122
+ right: -100px;
123
+ }
124
+ .blob-b {
125
+ width: 520px;
126
+ height: 520px;
127
+ background: radial-gradient(circle, rgba(255, 94, 200, 0.28), transparent 70%);
128
+ bottom: -180px;
129
+ left: 10%;
130
+ animation-duration: 32s;
131
+ animation-delay: -8s;
132
+ }
133
+ .blob-c {
134
+ width: 440px;
135
+ height: 440px;
136
+ background: radial-gradient(circle, rgba(185, 255, 94, 0.18), transparent 70%);
137
+ top: 40%;
138
+ left: 60%;
139
+ animation-duration: 28s;
140
+ animation-delay: -16s;
141
+ }
142
+ @keyframes drift {
143
+ 0% { transform: translate(0, 0) scale(1); }
144
+ 50% { transform: translate(40px, -30px) scale(1.1); }
145
+ 100% { transform: translate(-40px, 30px) scale(0.95); }
146
+ }
147
+ .grain {
148
+ position: absolute;
149
+ inset: 0;
150
+ opacity: 0.04;
151
+ mix-blend-mode: overlay;
152
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
153
+ }
154
+
155
+ /* ============================================================
156
+ glass utility
157
+ ============================================================ */
158
+ .glass {
159
+ background: var(--bg-2);
160
+ /* 14px blur is the sweet spot — gives the glass effect without the macOS
161
+ * Safari composition glitches that show up at 20px+ on retina. */
162
+ backdrop-filter: blur(14px) saturate(135%);
163
+ -webkit-backdrop-filter: blur(14px) saturate(135%);
164
+ border: 1px solid var(--stroke);
165
+ box-shadow: var(--shadow-glass);
166
+ /* Promote to its own layer so it doesn't repaint everything beneath
167
+ * on each frame of the ambient blob animation. */
168
+ isolation: isolate;
169
+ }
170
+
171
+ /* ============================================================
172
+ topbar
173
+ ============================================================ */
174
+ .topbar {
175
+ position: fixed;
176
+ top: var(--s-4);
177
+ left: var(--s-4);
178
+ right: var(--s-4);
179
+ height: 60px;
180
+ z-index: 10;
181
+ border-radius: var(--r-2);
182
+ display: grid;
183
+ grid-template-columns: 1fr auto 1fr;
184
+ align-items: center;
185
+ padding: 0 var(--s-5);
186
+ gap: var(--s-5);
187
+ }
188
+ .brand {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: var(--s-3);
192
+ }
193
+ .logo-mark {
194
+ position: relative;
195
+ width: 34px;
196
+ height: 34px;
197
+ border-radius: var(--r-1);
198
+ background: linear-gradient(135deg, var(--cyan) 0%, #4a8cff 70%, var(--magenta) 130%);
199
+ display: grid;
200
+ place-items: center;
201
+ box-shadow: 0 8px 20px rgba(94, 236, 255, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.3);
202
+ }
203
+ .logo-glyph {
204
+ font-size: 11px;
205
+ font-weight: 800;
206
+ color: #061018;
207
+ letter-spacing: -0.2px;
208
+ z-index: 2;
209
+ }
210
+ .logo-pulse {
211
+ position: absolute;
212
+ inset: -3px;
213
+ border-radius: 11px;
214
+ border: 1px solid var(--cyan);
215
+ opacity: 0;
216
+ animation: pulse 2.6s var(--ease) infinite;
217
+ }
218
+ @keyframes pulse {
219
+ 0%, 100% { transform: scale(1); opacity: 0; }
220
+ 40% { transform: scale(1.15); opacity: 0.5; }
221
+ 80% { transform: scale(1.3); opacity: 0; }
222
+ }
223
+ .brand-text {
224
+ display: flex;
225
+ flex-direction: column;
226
+ line-height: 1;
227
+ }
228
+ .brand-name {
229
+ font-size: 15px;
230
+ font-weight: 700;
231
+ letter-spacing: -0.2px;
232
+ }
233
+ .brand-sub {
234
+ font-size: 11px;
235
+ color: var(--fg-mute);
236
+ text-transform: uppercase;
237
+ letter-spacing: 1.4px;
238
+ margin-top: 3px;
239
+ }
240
+
241
+ /* tab pill */
242
+ .tab-pill {
243
+ position: relative;
244
+ display: flex;
245
+ gap: 4px;
246
+ padding: 4px;
247
+ background: rgba(5, 10, 22, 0.6);
248
+ border: 1px solid var(--stroke);
249
+ border-radius: var(--r-full);
250
+ }
251
+ .tab {
252
+ position: relative;
253
+ z-index: 2;
254
+ background: transparent;
255
+ border: 0;
256
+ padding: 8px 18px;
257
+ border-radius: var(--r-full);
258
+ color: var(--fg-dim);
259
+ font-size: 13px;
260
+ font-weight: 500;
261
+ cursor: pointer;
262
+ transition: color 0.2s var(--ease);
263
+ }
264
+ .tab:hover {
265
+ color: var(--fg);
266
+ }
267
+ .tab.active {
268
+ color: #06121e;
269
+ font-weight: 600;
270
+ }
271
+ .tab-glow {
272
+ position: absolute;
273
+ top: 4px;
274
+ bottom: 4px;
275
+ border-radius: var(--r-full);
276
+ background: linear-gradient(135deg, var(--cyan), #80cfff);
277
+ box-shadow: 0 4px 14px rgba(94, 236, 255, 0.5);
278
+ transition: left 0.4s var(--ease), width 0.4s var(--ease);
279
+ z-index: 1;
280
+ }
281
+
282
+ /* org picker */
283
+ .org-picker {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: var(--s-3);
287
+ justify-content: flex-end;
288
+ }
289
+ .org-picker label {
290
+ font-size: 10px;
291
+ color: var(--fg-mute);
292
+ letter-spacing: 1.6px;
293
+ font-weight: 600;
294
+ }
295
+ .select-wrap {
296
+ position: relative;
297
+ display: flex;
298
+ align-items: center;
299
+ }
300
+ .select-wrap select {
301
+ appearance: none;
302
+ -webkit-appearance: none;
303
+ background: rgba(5, 10, 22, 0.7);
304
+ border: 1px solid var(--stroke);
305
+ color: var(--fg);
306
+ font-size: 13px;
307
+ padding: 8px 32px 8px 12px;
308
+ border-radius: var(--r-1);
309
+ min-width: 280px;
310
+ cursor: pointer;
311
+ transition: border-color 0.2s var(--ease);
312
+ }
313
+ .select-wrap select:hover {
314
+ border-color: var(--stroke-strong);
315
+ }
316
+ .select-wrap select:focus {
317
+ outline: none;
318
+ border-color: var(--cyan);
319
+ box-shadow: 0 0 0 3px rgba(94, 236, 255, 0.1);
320
+ }
321
+ .select-wrap .chev {
322
+ position: absolute;
323
+ right: 10px;
324
+ width: 12px;
325
+ height: 12px;
326
+ color: var(--fg-mute);
327
+ pointer-events: none;
328
+ }
329
+ .org-meta {
330
+ font-family: "JetBrains Mono", monospace;
331
+ font-size: 11px;
332
+ color: var(--fg-mute);
333
+ letter-spacing: 0.2px;
334
+ }
335
+
336
+ /* ============================================================
337
+ drawer (controls)
338
+ ============================================================ */
339
+ .drawer {
340
+ position: fixed;
341
+ top: 96px;
342
+ left: var(--s-4);
343
+ bottom: var(--s-4);
344
+ width: 320px;
345
+ z-index: 9;
346
+ border-radius: var(--r-2);
347
+ padding: var(--s-5);
348
+ display: flex;
349
+ flex-direction: column;
350
+ gap: var(--s-5);
351
+ overflow-y: auto;
352
+ transition: transform 0.45s var(--ease), opacity 0.3s var(--ease);
353
+ }
354
+ .drawer.collapsed {
355
+ transform: translateX(calc(-100% - var(--s-4) - 4px));
356
+ }
357
+ /* Drawer toggle lives OUTSIDE the drawer (sibling, fixed-position) so it
358
+ * remains visible after the drawer slides off-screen. Its left edge tracks
359
+ * the drawer's right edge in the open state and snaps to the viewport in
360
+ * the collapsed state, transitioning with the same easing as the drawer. */
361
+ .drawer-toggle {
362
+ position: fixed;
363
+ /* drawer width 320 + drawer left s-4 + 6px breathing space, minus half
364
+ * the toggle width so it sits half-on / half-off the drawer edge. */
365
+ left: calc(320px + var(--s-4) + 6px - 14px);
366
+ top: 110px;
367
+ width: 28px;
368
+ height: 28px;
369
+ border-radius: 50%;
370
+ background: var(--bg-3);
371
+ border: 1px solid var(--stroke-strong);
372
+ color: var(--fg-dim);
373
+ cursor: pointer;
374
+ display: grid;
375
+ place-items: center;
376
+ backdrop-filter: blur(14px);
377
+ -webkit-backdrop-filter: blur(14px);
378
+ z-index: 11; /* above drawer */
379
+ transition:
380
+ left 0.45s var(--ease),
381
+ color 0.2s var(--ease),
382
+ border-color 0.2s var(--ease),
383
+ box-shadow 0.2s var(--ease);
384
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
385
+ padding: 0;
386
+ }
387
+ .drawer-toggle.collapsed {
388
+ left: var(--s-3);
389
+ color: var(--cyan);
390
+ border-color: rgba(94, 236, 255, 0.4);
391
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5), 0 0 0 4px rgba(94, 236, 255, 0.06);
392
+ }
393
+ .drawer-toggle svg {
394
+ width: 14px;
395
+ height: 14px;
396
+ position: absolute;
397
+ transition: opacity 0.2s var(--ease), transform 0.3s var(--ease);
398
+ }
399
+ .drawer-toggle .ic-collapse {
400
+ opacity: 1;
401
+ }
402
+ .drawer-toggle .ic-expand {
403
+ opacity: 0;
404
+ }
405
+ .drawer-toggle.collapsed .ic-collapse {
406
+ opacity: 0;
407
+ }
408
+ .drawer-toggle.collapsed .ic-expand {
409
+ opacity: 1;
410
+ }
411
+ .drawer-toggle:hover {
412
+ color: var(--cyan);
413
+ border-color: var(--stroke-hot);
414
+ }
415
+
416
+ .ctrl-panel {
417
+ display: none;
418
+ flex-direction: column;
419
+ gap: var(--s-4);
420
+ }
421
+ .ctrl-panel.active {
422
+ display: flex;
423
+ animation: fadeUp 0.28s var(--ease);
424
+ }
425
+ @keyframes fadeUp {
426
+ from { opacity: 0; transform: translateY(6px); }
427
+ to { opacity: 1; transform: translateY(0); }
428
+ }
429
+ .ctrl-head h3 {
430
+ margin: 0 0 var(--s-1);
431
+ font-size: 18px;
432
+ font-weight: 600;
433
+ letter-spacing: -0.2px;
434
+ background: linear-gradient(135deg, var(--fg), var(--cyan));
435
+ -webkit-background-clip: text;
436
+ background-clip: text;
437
+ -webkit-text-fill-color: transparent;
438
+ }
439
+ .ctrl-head p {
440
+ margin: 0;
441
+ font-size: 12px;
442
+ color: var(--fg-mute);
443
+ line-height: 1.5;
444
+ }
445
+
446
+ .field {
447
+ display: flex;
448
+ flex-direction: column;
449
+ gap: var(--s-2);
450
+ position: relative;
451
+ }
452
+ .field label {
453
+ font-size: 10px;
454
+ text-transform: uppercase;
455
+ letter-spacing: 1.4px;
456
+ color: var(--fg-mute);
457
+ font-weight: 600;
458
+ }
459
+
460
+ .search-wrap {
461
+ position: relative;
462
+ }
463
+ .search-wrap input {
464
+ width: 100%;
465
+ background: rgba(5, 10, 22, 0.7);
466
+ border: 1px solid var(--stroke);
467
+ border-radius: var(--r-1);
468
+ padding: 10px 36px 10px 12px;
469
+ color: var(--fg);
470
+ font-size: 13px;
471
+ transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
472
+ }
473
+ .search-wrap input::placeholder {
474
+ color: var(--fg-mute);
475
+ }
476
+ .search-wrap input:focus {
477
+ outline: none;
478
+ border-color: var(--cyan);
479
+ box-shadow: 0 0 0 3px rgba(94, 236, 255, 0.1);
480
+ }
481
+ .search-wrap kbd {
482
+ position: absolute;
483
+ right: 8px;
484
+ top: 50%;
485
+ transform: translateY(-50%);
486
+ }
487
+
488
+ .autocomplete {
489
+ list-style: none;
490
+ margin: var(--s-2) 0 0;
491
+ padding: 6px;
492
+ background: var(--bg-3);
493
+ border: 1px solid var(--stroke-strong);
494
+ border-radius: var(--r-1);
495
+ max-height: 280px;
496
+ overflow-y: auto;
497
+ display: none;
498
+ backdrop-filter: blur(20px);
499
+ box-shadow: var(--shadow-pop);
500
+ }
501
+ .autocomplete.show {
502
+ display: block;
503
+ }
504
+ .autocomplete li {
505
+ display: flex;
506
+ align-items: center;
507
+ gap: 8px;
508
+ padding: 8px 10px;
509
+ border-radius: 6px;
510
+ cursor: pointer;
511
+ font-size: 12px;
512
+ color: var(--fg);
513
+ transition: background 0.15s var(--ease);
514
+ }
515
+ .autocomplete li:hover,
516
+ .autocomplete li.active {
517
+ background: rgba(94, 236, 255, 0.1);
518
+ }
519
+ .autocomplete .node-dot {
520
+ width: 8px;
521
+ height: 8px;
522
+ border-radius: 50%;
523
+ flex-shrink: 0;
524
+ box-shadow: 0 0 8px currentColor;
525
+ }
526
+ .autocomplete .lab {
527
+ margin-left: auto;
528
+ font-size: 10px;
529
+ color: var(--fg-mute);
530
+ text-transform: uppercase;
531
+ letter-spacing: 0.8px;
532
+ }
533
+
534
+ .pill-group {
535
+ display: flex;
536
+ gap: 4px;
537
+ background: rgba(5, 10, 22, 0.7);
538
+ border: 1px solid var(--stroke);
539
+ border-radius: var(--r-full);
540
+ padding: 4px;
541
+ }
542
+ .pill-group button {
543
+ flex: 1;
544
+ background: transparent;
545
+ border: 0;
546
+ padding: 7px;
547
+ border-radius: var(--r-full);
548
+ color: var(--fg-dim);
549
+ font-size: 12px;
550
+ cursor: pointer;
551
+ transition: all 0.2s var(--ease);
552
+ }
553
+ .pill-group button:hover {
554
+ color: var(--fg);
555
+ }
556
+ .pill-group button.active {
557
+ background: linear-gradient(135deg, var(--cyan), #80cfff);
558
+ color: #06121e;
559
+ font-weight: 600;
560
+ box-shadow: 0 4px 12px rgba(94, 236, 255, 0.4);
561
+ }
562
+
563
+ .multi-select summary {
564
+ list-style: none;
565
+ cursor: pointer;
566
+ padding: 10px 12px;
567
+ background: rgba(5, 10, 22, 0.7);
568
+ border: 1px solid var(--stroke);
569
+ border-radius: var(--r-1);
570
+ font-size: 12px;
571
+ color: var(--fg);
572
+ user-select: none;
573
+ display: flex;
574
+ justify-content: space-between;
575
+ align-items: center;
576
+ }
577
+ .multi-select summary::-webkit-details-marker { display: none; }
578
+ .multi-select summary::after {
579
+ content: "▾";
580
+ color: var(--fg-mute);
581
+ font-size: 10px;
582
+ transition: transform 0.2s var(--ease);
583
+ }
584
+ .multi-select[open] summary::after {
585
+ transform: rotate(180deg);
586
+ }
587
+ .multi-select summary:hover {
588
+ border-color: var(--stroke-strong);
589
+ }
590
+ .checklist {
591
+ margin-top: var(--s-2);
592
+ max-height: 260px;
593
+ overflow-y: auto;
594
+ display: flex;
595
+ flex-direction: column;
596
+ gap: 2px;
597
+ padding: 6px;
598
+ background: var(--bg-3);
599
+ border: 1px solid var(--stroke);
600
+ border-radius: var(--r-1);
601
+ }
602
+ .checklist.labels {
603
+ max-height: none;
604
+ }
605
+ .checklist label {
606
+ display: flex;
607
+ align-items: center;
608
+ gap: 8px;
609
+ padding: 6px 8px;
610
+ font-size: 12px;
611
+ color: var(--fg);
612
+ cursor: pointer;
613
+ border-radius: 6px;
614
+ letter-spacing: 0;
615
+ text-transform: none;
616
+ font-weight: 400;
617
+ }
618
+ .checklist label:hover {
619
+ background: rgba(94, 236, 255, 0.08);
620
+ }
621
+ .checklist input[type="checkbox"] {
622
+ appearance: none;
623
+ width: 14px;
624
+ height: 14px;
625
+ border: 1px solid var(--stroke-strong);
626
+ border-radius: 3px;
627
+ background: rgba(5, 10, 22, 0.6);
628
+ cursor: pointer;
629
+ position: relative;
630
+ transition: all 0.15s var(--ease);
631
+ flex-shrink: 0;
632
+ }
633
+ .checklist input[type="checkbox"]:checked {
634
+ background: var(--cyan);
635
+ border-color: var(--cyan);
636
+ }
637
+ .checklist input[type="checkbox"]:checked::after {
638
+ content: "";
639
+ position: absolute;
640
+ left: 3px;
641
+ top: 0px;
642
+ width: 4px;
643
+ height: 8px;
644
+ border: solid #06121e;
645
+ border-width: 0 2px 2px 0;
646
+ transform: rotate(45deg);
647
+ }
648
+ .checklist .dot {
649
+ width: 8px;
650
+ height: 8px;
651
+ border-radius: 50%;
652
+ box-shadow: 0 0 8px currentColor;
653
+ flex-shrink: 0;
654
+ }
655
+
656
+ input[type="number"] {
657
+ width: 100%;
658
+ background: rgba(5, 10, 22, 0.7);
659
+ border: 1px solid var(--stroke);
660
+ border-radius: var(--r-1);
661
+ padding: 10px 12px;
662
+ color: var(--fg);
663
+ font-size: 13px;
664
+ font-family: "JetBrains Mono", monospace;
665
+ }
666
+ input[type="number"]:focus {
667
+ outline: none;
668
+ border-color: var(--cyan);
669
+ box-shadow: 0 0 0 3px rgba(94, 236, 255, 0.1);
670
+ }
671
+
672
+ /* CTA buttons */
673
+ .cta {
674
+ position: relative;
675
+ display: inline-flex;
676
+ align-items: center;
677
+ justify-content: center;
678
+ gap: 8px;
679
+ width: 100%;
680
+ padding: 12px 20px;
681
+ border: 0;
682
+ border-radius: var(--r-1);
683
+ background: linear-gradient(135deg, var(--cyan) 0%, #4a8cff 100%);
684
+ color: #06121e;
685
+ font-size: 13px;
686
+ font-weight: 600;
687
+ cursor: pointer;
688
+ letter-spacing: 0.2px;
689
+ box-shadow: 0 8px 24px rgba(94, 236, 255, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.3);
690
+ transition: transform 0.15s var(--ease), box-shadow 0.2s var(--ease);
691
+ }
692
+ .cta:hover {
693
+ transform: translateY(-1px);
694
+ box-shadow: 0 12px 32px rgba(94, 236, 255, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.4);
695
+ }
696
+ .cta:active {
697
+ transform: translateY(0);
698
+ }
699
+ .cta svg {
700
+ width: 14px;
701
+ height: 14px;
702
+ }
703
+ .cta.secondary {
704
+ background: rgba(94, 236, 255, 0.1);
705
+ color: var(--cyan);
706
+ box-shadow: inset 0 0 0 1px rgba(94, 236, 255, 0.3);
707
+ }
708
+ .cta.secondary:hover {
709
+ background: rgba(94, 236, 255, 0.15);
710
+ box-shadow: inset 0 0 0 1px rgba(94, 236, 255, 0.5);
711
+ }
712
+
713
+ .hint {
714
+ margin: 0;
715
+ font-size: 11px;
716
+ color: var(--fg-mute);
717
+ font-family: "JetBrains Mono", monospace;
718
+ min-height: 16px;
719
+ }
720
+
721
+ /* legend */
722
+ .legend {
723
+ margin-top: auto;
724
+ padding-top: var(--s-4);
725
+ border-top: 1px solid var(--stroke);
726
+ }
727
+ .legend h4 {
728
+ margin: 0 0 var(--s-2);
729
+ font-size: 10px;
730
+ text-transform: uppercase;
731
+ letter-spacing: 1.4px;
732
+ color: var(--fg-mute);
733
+ font-weight: 600;
734
+ }
735
+ #legendList {
736
+ display: grid;
737
+ grid-template-columns: 1fr 1fr;
738
+ gap: 6px 10px;
739
+ }
740
+ #legendList .li {
741
+ display: flex;
742
+ align-items: center;
743
+ gap: 6px;
744
+ font-size: 11px;
745
+ color: var(--fg-dim);
746
+ }
747
+ #legendList .li .dot {
748
+ width: 8px;
749
+ height: 8px;
750
+ border-radius: 50%;
751
+ box-shadow: 0 0 6px currentColor;
752
+ }
753
+
754
+ /* ============================================================
755
+ canvas
756
+ ============================================================ */
757
+ .canvas-host {
758
+ position: fixed;
759
+ inset: 0;
760
+ z-index: 1;
761
+ }
762
+ #graph {
763
+ width: 100%;
764
+ height: 100%;
765
+ }
766
+ .canvas-hint {
767
+ position: absolute;
768
+ inset: 0;
769
+ display: grid;
770
+ place-items: center;
771
+ pointer-events: none;
772
+ z-index: 2;
773
+ transition: opacity 0.5s var(--ease);
774
+ }
775
+ .canvas-hint.hidden { opacity: 0; }
776
+ .hint-inner {
777
+ display: flex;
778
+ align-items: center;
779
+ gap: 16px;
780
+ padding: 20px 26px;
781
+ background: var(--bg-2);
782
+ backdrop-filter: blur(20px);
783
+ border: 1px solid var(--stroke);
784
+ border-radius: var(--r-2);
785
+ color: var(--fg-dim);
786
+ }
787
+ .hint-inner svg {
788
+ width: 36px;
789
+ height: 36px;
790
+ color: var(--cyan);
791
+ }
792
+ .hint-inner strong {
793
+ display: block;
794
+ color: var(--fg);
795
+ font-weight: 500;
796
+ font-size: 14px;
797
+ margin-bottom: 4px;
798
+ }
799
+ .hint-inner span {
800
+ display: block;
801
+ font-size: 11px;
802
+ color: var(--fg-mute);
803
+ font-family: "JetBrains Mono", monospace;
804
+ }
805
+
806
+ /* ============================================================
807
+ stats chip
808
+ ============================================================ */
809
+ .stats-chip {
810
+ position: fixed;
811
+ bottom: var(--s-4);
812
+ left: 50%;
813
+ transform: translateX(-50%);
814
+ z-index: 8;
815
+ padding: 10px 16px;
816
+ border-radius: var(--r-full);
817
+ display: flex;
818
+ align-items: center;
819
+ gap: var(--s-3);
820
+ font-size: 12px;
821
+ color: var(--fg-dim);
822
+ font-family: "JetBrains Mono", monospace;
823
+ }
824
+ .stats-chip b {
825
+ color: var(--cyan);
826
+ font-weight: 600;
827
+ margin-right: 4px;
828
+ }
829
+ .stats-chip em {
830
+ font-style: normal;
831
+ color: var(--fg-mute);
832
+ }
833
+ .stats-chip .sep {
834
+ width: 1px;
835
+ height: 12px;
836
+ background: var(--stroke);
837
+ }
838
+ .stats-chip .badge {
839
+ padding: 2px 8px;
840
+ background: rgba(255, 184, 107, 0.15);
841
+ color: var(--amber);
842
+ border-radius: var(--r-full);
843
+ font-size: 10px;
844
+ text-transform: uppercase;
845
+ letter-spacing: 1px;
846
+ }
847
+ .badge.hidden { display: none; }
848
+
849
+ /* ============================================================
850
+ inspector
851
+ ============================================================ */
852
+ .inspector {
853
+ position: fixed;
854
+ top: 96px;
855
+ right: var(--s-4);
856
+ width: 340px;
857
+ max-height: calc(100vh - 96px - var(--s-4));
858
+ z-index: 9;
859
+ border-radius: var(--r-2);
860
+ padding: var(--s-5);
861
+ display: flex;
862
+ flex-direction: column;
863
+ gap: var(--s-3);
864
+ overflow-y: auto;
865
+ overflow-x: hidden;
866
+ /* Smooth fade on first show. Using transition (not keyframe animation)
867
+ * avoids the re-trigger flicker when the inspector content is replaced
868
+ * after a new node click. */
869
+ opacity: 1;
870
+ transform: translateX(0);
871
+ transition: opacity 0.25s var(--ease), transform 0.3s var(--ease);
872
+ /* Stable contain so reflows inside the body don't jiggle the surroundings. */
873
+ contain: layout style;
874
+ will-change: opacity, transform;
875
+ }
876
+ .inspector.hidden {
877
+ pointer-events: none;
878
+ opacity: 0;
879
+ transform: translateX(16px);
880
+ /* display:none would kill the transition; visibility keeps it animatable. */
881
+ visibility: hidden;
882
+ }
883
+
884
+ /* Inspector controls cluster (collapse + close) in top-right. */
885
+ .insp-controls {
886
+ position: absolute;
887
+ top: 12px;
888
+ right: 12px;
889
+ display: flex;
890
+ gap: 6px;
891
+ z-index: 2;
892
+ }
893
+ .icon-btn {
894
+ position: relative;
895
+ width: 28px;
896
+ height: 28px;
897
+ border-radius: 50%;
898
+ background: rgba(255, 255, 255, 0.04);
899
+ border: 1px solid var(--stroke);
900
+ color: var(--fg-dim);
901
+ display: grid;
902
+ place-items: center;
903
+ cursor: pointer;
904
+ transition: all 0.15s var(--ease);
905
+ padding: 0;
906
+ }
907
+ .icon-btn svg {
908
+ width: 12px;
909
+ height: 12px;
910
+ position: absolute;
911
+ transition: opacity 0.2s var(--ease);
912
+ }
913
+ .icon-btn:hover {
914
+ background: rgba(94, 236, 255, 0.12);
915
+ color: var(--cyan);
916
+ border-color: rgba(94, 236, 255, 0.35);
917
+ }
918
+ .icon-btn.close:hover {
919
+ background: rgba(255, 94, 200, 0.15);
920
+ color: var(--magenta);
921
+ border-color: rgba(255, 94, 200, 0.4);
922
+ }
923
+ .icon-btn .ic-exp {
924
+ opacity: 0;
925
+ }
926
+ .inspector.collapsed .icon-btn .ic-coll {
927
+ opacity: 0;
928
+ }
929
+ .inspector.collapsed .icon-btn .ic-exp {
930
+ opacity: 1;
931
+ }
932
+
933
+ /* Collapsed inspector: slim rail with just the label pill + node short name
934
+ * rotated vertically. Click anywhere on the rail (or the chevron) expands. */
935
+ .inspector.collapsed {
936
+ width: 52px;
937
+ padding: var(--s-3) 10px;
938
+ overflow: hidden;
939
+ cursor: pointer;
940
+ gap: var(--s-3);
941
+ }
942
+ .inspector.collapsed .insp-head {
943
+ padding-right: 0;
944
+ align-items: center;
945
+ }
946
+ .inspector.collapsed .insp-head h2,
947
+ .inspector.collapsed .insp-body,
948
+ .inspector.collapsed #recenter {
949
+ display: none;
950
+ }
951
+ .inspector.collapsed .insp-pill {
952
+ writing-mode: vertical-rl;
953
+ transform: rotate(180deg);
954
+ margin-top: 36px;
955
+ padding: 8px 4px;
956
+ letter-spacing: 1.6px;
957
+ }
958
+ .inspector.collapsed .insp-controls {
959
+ flex-direction: column;
960
+ top: 8px;
961
+ right: 12px;
962
+ gap: 4px;
963
+ }
964
+ .inspector.collapsed .insp-controls .close {
965
+ display: none; /* close only visible when expanded */
966
+ }
967
+ .insp-head {
968
+ display: flex;
969
+ flex-direction: column;
970
+ gap: 8px;
971
+ padding-right: 32px;
972
+ }
973
+ .insp-pill {
974
+ display: inline-block;
975
+ width: fit-content;
976
+ padding: 3px 10px;
977
+ background: rgba(94, 236, 255, 0.12);
978
+ color: var(--cyan);
979
+ font-size: 10px;
980
+ text-transform: uppercase;
981
+ letter-spacing: 1.2px;
982
+ font-weight: 600;
983
+ border-radius: var(--r-full);
984
+ border: 1px solid rgba(94, 236, 255, 0.3);
985
+ }
986
+ .insp-head h2 {
987
+ margin: 0;
988
+ font-size: 13px;
989
+ font-weight: 500;
990
+ /* break-word + anywhere = break at any character only when needed, not
991
+ * mid-word for every long token. This is what fixes "Generato\nr.gener…". */
992
+ overflow-wrap: anywhere;
993
+ word-break: break-word;
994
+ color: var(--fg);
995
+ font-family: "JetBrains Mono", monospace;
996
+ line-height: 1.45;
997
+ }
998
+ .insp-body {
999
+ display: flex;
1000
+ flex-direction: column;
1001
+ gap: var(--s-3);
1002
+ min-height: 40px;
1003
+ }
1004
+ .edge-grp h4 {
1005
+ margin: 0 0 var(--s-2);
1006
+ font-size: 10px;
1007
+ text-transform: uppercase;
1008
+ letter-spacing: 1.4px;
1009
+ color: var(--fg-mute);
1010
+ font-weight: 600;
1011
+ display: flex;
1012
+ align-items: center;
1013
+ gap: 6px;
1014
+ }
1015
+ .edge-grp h4 .count {
1016
+ padding: 1px 7px;
1017
+ background: rgba(94, 236, 255, 0.1);
1018
+ color: var(--cyan);
1019
+ border-radius: var(--r-full);
1020
+ font-family: "JetBrains Mono", monospace;
1021
+ font-size: 10px;
1022
+ }
1023
+ .edge-grp ul {
1024
+ list-style: none;
1025
+ margin: 0;
1026
+ padding: 0;
1027
+ display: flex;
1028
+ flex-direction: column;
1029
+ gap: 2px;
1030
+ }
1031
+ .edge-grp li {
1032
+ padding: 7px 10px;
1033
+ background: rgba(255, 255, 255, 0.02);
1034
+ border: 1px solid var(--stroke);
1035
+ border-radius: 6px;
1036
+ font-size: 11px;
1037
+ font-family: "JetBrains Mono", monospace;
1038
+ color: var(--fg-dim);
1039
+ overflow-wrap: anywhere;
1040
+ word-break: break-word;
1041
+ line-height: 1.5;
1042
+ }
1043
+ .edge-grp li .rel {
1044
+ display: inline-block;
1045
+ padding: 1px 6px;
1046
+ margin-right: 6px;
1047
+ background: rgba(120, 160, 255, 0.1);
1048
+ color: #a8c5ff;
1049
+ border-radius: 3px;
1050
+ font-size: 9px;
1051
+ letter-spacing: 0.5px;
1052
+ }
1053
+
1054
+ /* scrollbars */
1055
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
1056
+ ::-webkit-scrollbar-track { background: transparent; }
1057
+ ::-webkit-scrollbar-thumb {
1058
+ background: rgba(120, 160, 255, 0.15);
1059
+ border-radius: 4px;
1060
+ }
1061
+ ::-webkit-scrollbar-thumb:hover {
1062
+ background: rgba(120, 160, 255, 0.3);
1063
+ }