archgraph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/bin/bgx.js +2 -0
  4. package/docs/examples/executors.example.json +15 -0
  5. package/docs/examples/view-spec.yaml +6 -0
  6. package/docs/release.md +15 -0
  7. package/docs/view-spec.md +28 -0
  8. package/integrations/README.md +20 -0
  9. package/integrations/claude/.claude/skills/backend-graphing/SKILL.md +56 -0
  10. package/integrations/claude/.claude/skills/backend-graphing-describe/SKILL.md +50 -0
  11. package/integrations/claude/.claude-plugin/marketplace.json +18 -0
  12. package/integrations/claude/.claude-plugin/plugin.json +9 -0
  13. package/integrations/claude/skills/backend-graphing/SKILL.md +56 -0
  14. package/integrations/claude/skills/backend-graphing-describe/SKILL.md +50 -0
  15. package/integrations/codex/skills/backend-graphing/SKILL.md +56 -0
  16. package/integrations/codex/skills/backend-graphing-describe/SKILL.md +50 -0
  17. package/package.json +49 -0
  18. package/packages/cli/src/index.js +415 -0
  19. package/packages/core/src/analyze-project.js +1238 -0
  20. package/packages/core/src/export.js +77 -0
  21. package/packages/core/src/index.js +4 -0
  22. package/packages/core/src/types.js +37 -0
  23. package/packages/core/src/view.js +86 -0
  24. package/packages/viewer/public/app.js +226 -0
  25. package/packages/viewer/public/canvas.js +181 -0
  26. package/packages/viewer/public/comments.js +193 -0
  27. package/packages/viewer/public/index.html +95 -0
  28. package/packages/viewer/public/layout.js +72 -0
  29. package/packages/viewer/public/minimap.js +92 -0
  30. package/packages/viewer/public/render.js +366 -0
  31. package/packages/viewer/public/sidebar.js +107 -0
  32. package/packages/viewer/public/styles.css +728 -0
  33. package/packages/viewer/public/theme.js +19 -0
  34. package/packages/viewer/public/tooltip.js +44 -0
  35. package/packages/viewer/src/index.js +590 -0
@@ -0,0 +1,728 @@
1
+ /* ──────────────────────────────────────────────────────────────────
2
+ BGX Viewer — Figma-like canvas layout
3
+ ────────────────────────────────────────────────────────────────── */
4
+
5
+ /* ─── Design tokens (light mode) ─────────────────────────────────── */
6
+ :root {
7
+ --bg: #f0f4fa;
8
+ --surface: #ffffff;
9
+ --surface-2: #f8fafc;
10
+ --line: #d8dfeb;
11
+ --line-2: #e8edf5;
12
+ --text: #132136;
13
+ --text-2: #354a63;
14
+ --muted: #61758f;
15
+ --endpoint: #0a6cf2;
16
+ --handler: #0f766e;
17
+ --condition: #b45309;
18
+ --agent: #6d28d9;
19
+ --mcp: #be185d;
20
+ --edge: #7a91aa;
21
+ --shadow: rgba(0, 0, 0, 0.08);
22
+ --shadow-md: rgba(0, 0, 0, 0.14);
23
+ --radius: 10px;
24
+ --radius-sm: 6px;
25
+ --radius-lg: 14px;
26
+ }
27
+
28
+ /* ─── Dark mode tokens ────────────────────────────────────────────── */
29
+ [data-theme="dark"] {
30
+ --bg: #0e1724;
31
+ --surface: #162030;
32
+ --surface-2: #1c2a3e;
33
+ --line: #253649;
34
+ --line-2: #1e2f44;
35
+ --text: #dceaf8;
36
+ --text-2: #8fa8c4;
37
+ --muted: #5a7a96;
38
+ --edge: #4a6a88;
39
+ --shadow: rgba(0, 0, 0, 0.3);
40
+ --shadow-md: rgba(0, 0, 0, 0.5);
41
+ }
42
+
43
+ /* ─── Base reset ──────────────────────────────────────────────────── */
44
+ *,
45
+ *::before,
46
+ *::after {
47
+ box-sizing: border-box;
48
+ margin: 0;
49
+ padding: 0;
50
+ }
51
+
52
+ html,
53
+ body {
54
+ width: 100%;
55
+ height: 100%;
56
+ overflow: hidden;
57
+ font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
58
+ font-size: 14px;
59
+ color: var(--text);
60
+ background: var(--bg);
61
+ }
62
+
63
+ /* ─── Canvas root — full screen ───────────────────────────────────── */
64
+ #canvasRoot {
65
+ position: fixed;
66
+ inset: 0;
67
+ overflow: hidden;
68
+ background: var(--bg);
69
+ background-image:
70
+ radial-gradient(circle, var(--line-2) 1px, transparent 1px);
71
+ background-size: 24px 24px;
72
+ }
73
+
74
+ #graphSvg {
75
+ width: 100%;
76
+ height: 100%;
77
+ display: block;
78
+ cursor: grab;
79
+ overflow: visible;
80
+ }
81
+
82
+ #graphSvg:active {
83
+ cursor: grabbing;
84
+ }
85
+
86
+ /* ─── Floating toolbar ────────────────────────────────────────────── */
87
+ #toolbar {
88
+ position: fixed;
89
+ top: 14px;
90
+ left: 50%;
91
+ transform: translateX(-50%);
92
+ z-index: 30;
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 12px;
96
+ padding: 8px 16px;
97
+ background: var(--surface);
98
+ border: 1px solid var(--line);
99
+ border-radius: 999px;
100
+ box-shadow: 0 4px 20px var(--shadow-md);
101
+ backdrop-filter: blur(12px);
102
+ white-space: nowrap;
103
+ max-width: calc(100vw - 32px);
104
+ overflow-x: auto;
105
+ }
106
+
107
+ .toolbar-title {
108
+ font-weight: 600;
109
+ font-size: 0.85rem;
110
+ color: var(--text);
111
+ padding-right: 8px;
112
+ border-right: 1px solid var(--line);
113
+ }
114
+
115
+ #toolbarControls {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 10px;
119
+ }
120
+
121
+ .ctrl-label {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 6px;
125
+ font-size: 0.78rem;
126
+ color: var(--muted);
127
+ }
128
+
129
+ .ctrl-name {
130
+ font-weight: 500;
131
+ }
132
+
133
+ .ctrl-label select,
134
+ .ctrl-label input {
135
+ font: inherit;
136
+ font-size: 0.82rem;
137
+ color: var(--text);
138
+ background: var(--surface-2);
139
+ border: 1px solid var(--line);
140
+ border-radius: var(--radius-sm);
141
+ padding: 4px 8px;
142
+ outline: none;
143
+ transition: border-color 0.15s;
144
+ }
145
+
146
+ .ctrl-label select:focus,
147
+ .ctrl-label input:focus {
148
+ border-color: var(--endpoint);
149
+ }
150
+
151
+ .ctrl-search input {
152
+ width: 160px;
153
+ }
154
+
155
+ #toolbarActions {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 8px;
159
+ padding-left: 8px;
160
+ border-left: 1px solid var(--line);
161
+ }
162
+
163
+ .btn-ghost {
164
+ font: inherit;
165
+ font-size: 0.78rem;
166
+ font-weight: 500;
167
+ color: var(--text-2);
168
+ background: transparent;
169
+ border: 1px solid var(--line);
170
+ border-radius: var(--radius-sm);
171
+ padding: 4px 10px;
172
+ cursor: pointer;
173
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
174
+ }
175
+
176
+ .btn-ghost:hover {
177
+ background: var(--surface-2);
178
+ border-color: var(--endpoint);
179
+ color: var(--endpoint);
180
+ }
181
+
182
+ .summary-badge {
183
+ font-size: 0.72rem;
184
+ color: var(--muted);
185
+ }
186
+
187
+ /* ─── Code sidebar ────────────────────────────────────────────────── */
188
+ .sidebar {
189
+ position: fixed;
190
+ top: 0;
191
+ right: 0;
192
+ width: 440px;
193
+ max-width: 90vw;
194
+ height: 100vh;
195
+ background: var(--surface);
196
+ border-left: 1px solid var(--line);
197
+ box-shadow: -4px 0 24px var(--shadow);
198
+ transform: translateX(100%);
199
+ transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
200
+ z-index: 50;
201
+ display: flex;
202
+ flex-direction: column;
203
+ overflow: hidden;
204
+ }
205
+
206
+ .sidebar--open {
207
+ transform: translateX(0);
208
+ }
209
+
210
+ .sidebar__header {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: space-between;
214
+ gap: 8px;
215
+ padding: 14px 16px;
216
+ border-bottom: 1px solid var(--line);
217
+ background: var(--surface-2);
218
+ flex-shrink: 0;
219
+ }
220
+
221
+ .sidebar__title {
222
+ font-weight: 600;
223
+ font-size: 0.9rem;
224
+ color: var(--text);
225
+ overflow: hidden;
226
+ text-overflow: ellipsis;
227
+ white-space: nowrap;
228
+ }
229
+
230
+ .sidebar__body {
231
+ flex: 1;
232
+ overflow-y: auto;
233
+ padding: 14px 16px;
234
+ }
235
+
236
+ .sidebar-file {
237
+ font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;
238
+ font-size: 0.72rem;
239
+ color: var(--muted);
240
+ margin-bottom: 10px;
241
+ word-break: break-all;
242
+ }
243
+
244
+ .code-block {
245
+ white-space: pre-wrap;
246
+ border: 1px solid var(--line);
247
+ border-radius: var(--radius);
248
+ padding: 10px 12px;
249
+ font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;
250
+ font-size: 0.75rem;
251
+ line-height: 1.55;
252
+ background: var(--surface-2);
253
+ overflow-x: auto;
254
+ margin: 10px 0;
255
+ }
256
+
257
+ .related-title {
258
+ font-size: 0.82rem;
259
+ font-weight: 600;
260
+ color: var(--text-2);
261
+ margin: 14px 0 6px;
262
+ }
263
+
264
+ .related-list details {
265
+ border: 1px solid var(--line);
266
+ border-radius: var(--radius-sm);
267
+ margin-bottom: 6px;
268
+ overflow: hidden;
269
+ }
270
+
271
+ .related-list summary {
272
+ padding: 6px 10px;
273
+ font-size: 0.78rem;
274
+ cursor: pointer;
275
+ background: var(--surface-2);
276
+ user-select: none;
277
+ }
278
+
279
+ .related-list pre {
280
+ padding: 8px 10px;
281
+ font-size: 0.72rem;
282
+ font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;
283
+ white-space: pre-wrap;
284
+ background: var(--surface);
285
+ }
286
+
287
+ .desc-card {
288
+ border: 1px solid var(--line);
289
+ border-radius: var(--radius);
290
+ padding: 10px 12px;
291
+ margin-bottom: 10px;
292
+ background: var(--surface-2);
293
+ font-size: 0.82rem;
294
+ }
295
+
296
+ .desc-card h4 {
297
+ font-size: 0.78rem;
298
+ font-weight: 600;
299
+ margin-bottom: 6px;
300
+ color: var(--text-2);
301
+ }
302
+
303
+ .desc-card p {
304
+ color: var(--text);
305
+ margin-bottom: 4px;
306
+ line-height: 1.4;
307
+ }
308
+
309
+ /* Syntax tokens */
310
+ .tok-keyword { color: #7c3aed; }
311
+ .tok-string { color: #047857; }
312
+ .tok-comment { color: #6b7280; font-style: italic; }
313
+ .tok-number { color: #b45309; }
314
+ [data-theme="dark"] .tok-keyword { color: #a78bfa; }
315
+ [data-theme="dark"] .tok-string { color: #34d399; }
316
+ [data-theme="dark"] .tok-comment { color: #6b7280; }
317
+ [data-theme="dark"] .tok-number { color: #fbbf24; }
318
+
319
+ /* ─── Comment overlay ─────────────────────────────────────────────── */
320
+ .overlay {
321
+ position: fixed;
322
+ width: 320px;
323
+ max-height: 520px;
324
+ background: var(--surface);
325
+ border: 1px solid var(--line);
326
+ border-radius: var(--radius-lg);
327
+ box-shadow: 0 8px 32px var(--shadow-md);
328
+ z-index: 60;
329
+ display: flex;
330
+ flex-direction: column;
331
+ overflow: hidden;
332
+ opacity: 0;
333
+ pointer-events: none;
334
+ transform: scale(0.96);
335
+ transform-origin: top left;
336
+ transition: opacity 0.14s ease, transform 0.14s ease;
337
+ }
338
+
339
+ .overlay--open {
340
+ opacity: 1;
341
+ pointer-events: all;
342
+ transform: scale(1);
343
+ }
344
+
345
+ .overlay__header {
346
+ display: flex;
347
+ align-items: center;
348
+ justify-content: space-between;
349
+ padding: 12px 14px;
350
+ border-bottom: 1px solid var(--line);
351
+ background: var(--surface-2);
352
+ flex-shrink: 0;
353
+ font-size: 0.85rem;
354
+ }
355
+
356
+ .overlay__thread {
357
+ flex: 1;
358
+ overflow-y: auto;
359
+ padding: 10px 12px;
360
+ }
361
+
362
+ .overlay__form {
363
+ border-top: 1px solid var(--line);
364
+ padding: 10px 12px;
365
+ background: var(--surface-2);
366
+ flex-shrink: 0;
367
+ }
368
+
369
+ .form-row {
370
+ margin-bottom: 8px;
371
+ }
372
+
373
+ #taskForm {
374
+ display: grid;
375
+ gap: 8px;
376
+ }
377
+
378
+ #taskForm textarea {
379
+ width: 100%;
380
+ font: inherit;
381
+ font-size: 0.82rem;
382
+ border: 1px solid var(--line);
383
+ border-radius: var(--radius-sm);
384
+ padding: 8px 10px;
385
+ resize: vertical;
386
+ background: var(--surface);
387
+ color: var(--text);
388
+ outline: none;
389
+ transition: border-color 0.14s;
390
+ }
391
+
392
+ #taskForm textarea:focus {
393
+ border-color: var(--endpoint);
394
+ }
395
+
396
+ .btn-primary {
397
+ font: inherit;
398
+ font-size: 0.82rem;
399
+ font-weight: 600;
400
+ color: #fff;
401
+ background: var(--endpoint);
402
+ border: none;
403
+ border-radius: var(--radius-sm);
404
+ padding: 7px 14px;
405
+ cursor: pointer;
406
+ transition: opacity 0.12s;
407
+ }
408
+
409
+ .btn-primary:hover {
410
+ opacity: 0.88;
411
+ }
412
+
413
+ .btn-icon {
414
+ font: inherit;
415
+ font-size: 0.9rem;
416
+ color: var(--muted);
417
+ background: transparent;
418
+ border: none;
419
+ cursor: pointer;
420
+ padding: 4px 6px;
421
+ border-radius: var(--radius-sm);
422
+ transition: background 0.12s, color 0.12s;
423
+ line-height: 1;
424
+ }
425
+
426
+ .btn-icon:hover {
427
+ background: var(--line-2);
428
+ color: var(--text);
429
+ }
430
+
431
+ /* ─── Task cards ──────────────────────────────────────────────────── */
432
+ .task-card {
433
+ border: 1px solid var(--line);
434
+ border-radius: var(--radius);
435
+ padding: 8px 10px;
436
+ margin-bottom: 8px;
437
+ background: var(--surface);
438
+ }
439
+
440
+ .task-head {
441
+ display: flex;
442
+ justify-content: space-between;
443
+ align-items: center;
444
+ gap: 6px;
445
+ margin-bottom: 4px;
446
+ }
447
+
448
+ .task-id {
449
+ font-family: 'IBM Plex Mono', monospace;
450
+ font-size: 0.68rem;
451
+ color: var(--muted);
452
+ }
453
+
454
+ .task-meta {
455
+ color: var(--muted);
456
+ font-size: 0.72rem;
457
+ margin-bottom: 4px;
458
+ }
459
+
460
+ .task-prompt {
461
+ font-size: 0.82rem;
462
+ color: var(--text);
463
+ margin-bottom: 8px;
464
+ line-height: 1.4;
465
+ }
466
+
467
+ .task-actions {
468
+ display: flex;
469
+ flex-wrap: wrap;
470
+ gap: 4px;
471
+ }
472
+
473
+ .task-actions button {
474
+ font: inherit;
475
+ font-size: 0.72rem;
476
+ padding: 3px 8px;
477
+ border: 1px solid var(--line);
478
+ border-radius: var(--radius-sm);
479
+ background: var(--surface-2);
480
+ color: var(--text-2);
481
+ cursor: pointer;
482
+ transition: background 0.12s, border-color 0.12s;
483
+ }
484
+
485
+ .task-actions button:hover {
486
+ background: var(--line-2);
487
+ border-color: var(--endpoint);
488
+ color: var(--endpoint);
489
+ }
490
+
491
+ .no-tasks {
492
+ color: var(--muted);
493
+ font-size: 0.82rem;
494
+ display: block;
495
+ text-align: center;
496
+ padding: 12px 0;
497
+ }
498
+
499
+ /* Status badges */
500
+ .status {
501
+ border-radius: 999px;
502
+ padding: 2px 8px;
503
+ font-size: 0.7rem;
504
+ font-weight: 500;
505
+ }
506
+
507
+ .status-open { background: #eaf2ff; color: #0a57c9; }
508
+ .status-approved { background: #ecfdf3; color: #157447; }
509
+ .status-running { background: #fff7e8; color: #a04b0f; }
510
+ .status-done { background: #e8faf5; color: #0f766e; }
511
+ .status-failed { background: #ffeceb; color: #b42318; }
512
+
513
+ [data-theme="dark"] .status-open { background: #0d2040; color: #60a5fa; }
514
+ [data-theme="dark"] .status-approved { background: #064e3b; color: #34d399; }
515
+ [data-theme="dark"] .status-running { background: #451a03; color: #fbbf24; }
516
+ [data-theme="dark"] .status-done { background: #022c22; color: #6ee7b7; }
517
+ [data-theme="dark"] .status-failed { background: #450a0a; color: #fca5a5; }
518
+
519
+ /* ─── Minimap ─────────────────────────────────────────────────────── */
520
+ #minimap {
521
+ position: fixed;
522
+ bottom: 16px;
523
+ right: 16px;
524
+ width: 180px;
525
+ height: 120px;
526
+ background: var(--surface);
527
+ border: 1px solid var(--line);
528
+ border-radius: var(--radius);
529
+ z-index: 40;
530
+ overflow: hidden;
531
+ box-shadow: 0 2px 10px var(--shadow);
532
+ }
533
+
534
+ #minimapSvg {
535
+ width: 100%;
536
+ height: 100%;
537
+ cursor: pointer;
538
+ }
539
+
540
+ .mm-node { opacity: 0.75; }
541
+ .mm-endpoint { fill: #0a6cf2; }
542
+ .mm-route_handler, .mm-handler { fill: #0f766e; }
543
+ .mm-agent, .mm-langgraph_node { fill: #6d28d9; }
544
+ .mm-condition { fill: #b45309; }
545
+ .mm-mcp_tool { fill: #be185d; }
546
+ .mm-default, .mm-function, .mm-method, .mm-class { fill: #61758f; }
547
+ .mm-viewport {
548
+ fill: rgba(10, 108, 242, 0.06);
549
+ stroke: #0a6cf2;
550
+ stroke-width: 1.5;
551
+ }
552
+
553
+ /* ─── Node tooltip ────────────────────────────────────────────────── */
554
+ .tooltip {
555
+ position: fixed;
556
+ z-index: 70;
557
+ pointer-events: none;
558
+ background: var(--text);
559
+ color: var(--surface);
560
+ padding: 6px 10px;
561
+ border-radius: var(--radius-sm);
562
+ font-size: 0.78rem;
563
+ max-width: 300px;
564
+ opacity: 0;
565
+ transition: opacity 0.1s ease;
566
+ line-height: 1.4;
567
+ word-break: break-word;
568
+ }
569
+
570
+ .tooltip--visible {
571
+ opacity: 1;
572
+ }
573
+
574
+ .tooltip-kind {
575
+ opacity: 0.7;
576
+ font-size: 0.68rem;
577
+ margin-top: 2px;
578
+ }
579
+
580
+ .tooltip-file {
581
+ font-family: 'IBM Plex Mono', monospace;
582
+ font-size: 0.64rem;
583
+ opacity: 0.65;
584
+ margin-top: 2px;
585
+ }
586
+
587
+ /* ─── SVG graph elements ──────────────────────────────────────────── */
588
+
589
+ /* Edges */
590
+ .edge {
591
+ stroke: var(--edge);
592
+ stroke-width: 1.6;
593
+ }
594
+
595
+ .edge-label {
596
+ fill: var(--muted);
597
+ font-size: 10px;
598
+ font-family: 'IBM Plex Sans', sans-serif;
599
+ }
600
+
601
+ .edge-condition {
602
+ stroke: var(--condition);
603
+ stroke-dasharray: 5 3;
604
+ }
605
+
606
+ .edge-handles {
607
+ stroke: var(--handler);
608
+ }
609
+
610
+ .edge-calls {
611
+ stroke: var(--endpoint);
612
+ stroke-opacity: 0.7;
613
+ }
614
+
615
+ .edge-langgraph {
616
+ stroke: var(--agent);
617
+ stroke-opacity: 0.8;
618
+ }
619
+
620
+ /* Node groups */
621
+ .node rect {
622
+ fill: var(--surface);
623
+ stroke: var(--line);
624
+ stroke-width: 1.2;
625
+ transition: stroke 0.12s, fill 0.12s;
626
+ }
627
+
628
+ .node {
629
+ cursor: pointer;
630
+ }
631
+
632
+ .node:hover rect {
633
+ stroke: var(--endpoint);
634
+ stroke-width: 1.8;
635
+ }
636
+
637
+ .node.selected rect {
638
+ stroke-width: 2.2;
639
+ stroke: var(--endpoint);
640
+ filter: drop-shadow(0 0 6px rgba(10, 108, 242, 0.35));
641
+ }
642
+
643
+ .node-endpoint rect { fill: #eaf2ff; stroke: #92b7ef; }
644
+ .node-handler rect { fill: #e8faf5; stroke: #88cdc0; }
645
+ .node-condition rect { fill: #fff5e6; stroke: #f2c58d; }
646
+ .node-agent rect { fill: #f3eeff; stroke: #bba7ef; }
647
+ .node-mcp rect { fill: #fdf2f8; stroke: #f0abcf; }
648
+ .node-default rect { fill: var(--surface-2); stroke: var(--line); }
649
+
650
+ [data-theme="dark"] .node-endpoint rect { fill: #0d2040; stroke: #1a4a90; }
651
+ [data-theme="dark"] .node-handler rect { fill: #022c28; stroke: #0f5c50; }
652
+ [data-theme="dark"] .node-condition rect { fill: #2a1400; stroke: #7a3a00; }
653
+ [data-theme="dark"] .node-agent rect { fill: #1e0a40; stroke: #5b2da8; }
654
+ [data-theme="dark"] .node-mcp rect { fill: #2a0a1e; stroke: #8c1f5e; }
655
+ [data-theme="dark"] .node-default rect { fill: var(--surface-2); stroke: var(--line); }
656
+
657
+ .node-kind {
658
+ fill: var(--muted);
659
+ font-size: 9px;
660
+ font-family: 'IBM Plex Sans', sans-serif;
661
+ text-transform: uppercase;
662
+ letter-spacing: 0.04em;
663
+ }
664
+
665
+ .node-label {
666
+ fill: var(--text);
667
+ font-size: 12px;
668
+ font-family: 'IBM Plex Sans', sans-serif;
669
+ font-weight: 500;
670
+ }
671
+
672
+ /* Collapse toggle */
673
+ .collapse-toggle {
674
+ cursor: pointer;
675
+ }
676
+
677
+ .collapse-toggle circle {
678
+ fill: var(--endpoint);
679
+ stroke: none;
680
+ opacity: 0.85;
681
+ transition: opacity 0.12s;
682
+ }
683
+
684
+ .collapse-toggle:hover circle {
685
+ opacity: 1;
686
+ }
687
+
688
+ .collapse-sign {
689
+ fill: #fff;
690
+ font-size: 11px;
691
+ font-family: 'IBM Plex Sans', sans-serif;
692
+ font-weight: 600;
693
+ dominant-baseline: central;
694
+ }
695
+
696
+ /* Comment pins */
697
+ .comment-pin {
698
+ cursor: pointer;
699
+ }
700
+
701
+ .comment-bubble {
702
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.18));
703
+ transition: r 0.12s;
704
+ }
705
+
706
+ .comment-pin:hover .comment-bubble {
707
+ r: 14;
708
+ }
709
+
710
+ .comment-count {
711
+ fill: #fff;
712
+ font-size: 10px;
713
+ font-weight: 700;
714
+ font-family: 'IBM Plex Sans', sans-serif;
715
+ }
716
+
717
+ /* Pin status colors (overridden by status class) */
718
+ .comment-bubble.status-open { fill: var(--endpoint); }
719
+ .comment-bubble.status-approved{ fill: #157447; }
720
+ .comment-bubble.status-running { fill: #d97706; }
721
+ .comment-bubble.status-done { fill: #0f766e; }
722
+ .comment-bubble.status-failed { fill: #dc2626; }
723
+
724
+ /* ─── Scrollbar styling ───────────────────────────────────────────── */
725
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
726
+ ::-webkit-scrollbar-track { background: transparent; }
727
+ ::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
728
+ ::-webkit-scrollbar-thumb:hover { background: var(--muted); }