@glyphlab/diagram-react 0.0.0-stage → 0.1.1

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.
package/diagram.css ADDED
@@ -0,0 +1,892 @@
1
+ /* The canvas of @glyphlab/diagram-react. It reads CSS variables for everything themed: see theme.css for the list and default values, or
2
+ define them in the host's own theme. Colours a node, group or edge can have are --c-<preset> (see COLOR_PRESETS). */
3
+
4
+ /* The buttons the canvas has in itself (zoom). */
5
+ .dg-btn,
6
+ .dg-icon-btn {
7
+ border: 1px solid var(--border);
8
+ background: var(--surface);
9
+ color: inherit;
10
+ font: inherit;
11
+ border-radius: 8px;
12
+ cursor: pointer;
13
+ transition: background 0.12s, border-color 0.12s;
14
+ }
15
+
16
+ .dg-btn {
17
+ padding: 7px 14px;
18
+ font-weight: 500;
19
+ }
20
+
21
+ .dg-icon-btn {
22
+ display: inline-grid;
23
+ place-items: center;
24
+ width: 36px;
25
+ height: 36px;
26
+ font-size: 18px;
27
+ line-height: 1;
28
+ }
29
+
30
+ .dg-btn:hover,
31
+ .dg-icon-btn:hover {
32
+ background: var(--surface-2);
33
+ }
34
+
35
+ /* A node's content when the host gives no Markdown renderer. */
36
+ .dg-plain {
37
+ font-size: 12.5px;
38
+ color: var(--muted);
39
+ white-space: pre-wrap;
40
+ overflow-wrap: anywhere;
41
+ }
42
+
43
+ /* Canvas */
44
+ .canvas {
45
+ position: relative;
46
+ flex: 1;
47
+ min-width: 0;
48
+ container-type: inline-size;
49
+ overflow: hidden;
50
+ cursor: grab;
51
+ touch-action: none;
52
+ background-color: var(--bg);
53
+ background-image: radial-gradient(circle, var(--dot) 1.2px, transparent 1.3px);
54
+ }
55
+
56
+ .canvas.dragging {
57
+ cursor: grabbing;
58
+ }
59
+
60
+ .snapshot-canvas .node.movable {
61
+ cursor: grab;
62
+ user-select: none;
63
+ }
64
+
65
+ .snapshot-canvas .node.moving {
66
+ cursor: grabbing;
67
+ }
68
+
69
+ .canvas-head {
70
+ position: absolute;
71
+ inset: 14px 16px auto 16px;
72
+ z-index: 3;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: space-between;
76
+ gap: 12px;
77
+ pointer-events: none;
78
+ }
79
+
80
+ .canvas-head h1 {
81
+ font-size: 20px;
82
+ font-weight: 700;
83
+ white-space: nowrap;
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ }
87
+
88
+ .canvas-tools {
89
+ display: flex;
90
+ flex: none;
91
+ align-items: center;
92
+ gap: 8px;
93
+ }
94
+
95
+ .edge-style-control {
96
+ display: flex;
97
+ flex: none;
98
+ align-items: center;
99
+ gap: 2px;
100
+ padding: 4px;
101
+ border: 1px solid var(--border);
102
+ border-radius: 999px;
103
+ background: var(--surface);
104
+ box-shadow: var(--node-shadow);
105
+ pointer-events: auto;
106
+ }
107
+
108
+ .edge-style-btn {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ height: 22px;
113
+ padding: 0 8px;
114
+ border: 0;
115
+ border-radius: 999px;
116
+ background: transparent;
117
+ color: var(--muted);
118
+ cursor: pointer;
119
+ }
120
+
121
+ .edge-style-btn:hover {
122
+ color: var(--text);
123
+ }
124
+
125
+ .edge-style-btn[aria-pressed='true'] {
126
+ background: var(--surface-2);
127
+ color: var(--text);
128
+ box-shadow: inset 0 0 0 1.5px var(--accent);
129
+ }
130
+
131
+ .edge-style-btn:focus-visible {
132
+ outline: 2px solid var(--accent);
133
+ outline-offset: 2px;
134
+ }
135
+
136
+ .color-control {
137
+ display: flex;
138
+ flex: none;
139
+ align-items: center;
140
+ gap: 6px;
141
+ padding: 6px 8px;
142
+ border: 1px solid var(--border);
143
+ border-radius: 999px;
144
+ background: var(--surface);
145
+ box-shadow: var(--node-shadow);
146
+ pointer-events: auto;
147
+ }
148
+
149
+ .color-swatch {
150
+ position: relative;
151
+ display: inline-flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ width: 18px;
155
+ height: 18px;
156
+ padding: 0;
157
+ border: 1px solid color-mix(in srgb, var(--text) 25%, transparent);
158
+ border-radius: 50%;
159
+ background: transparent;
160
+ color: var(--muted);
161
+ cursor: pointer;
162
+ }
163
+
164
+ .color-swatch[aria-pressed='true'],
165
+ .color-swatch.on {
166
+ outline: 2px solid var(--accent);
167
+ outline-offset: 2px;
168
+ }
169
+
170
+ .color-swatch:focus-visible {
171
+ outline: 2px solid var(--accent);
172
+ outline-offset: 2px;
173
+ }
174
+
175
+ .color-swatch.custom {
176
+ overflow: hidden;
177
+ background: conic-gradient(#ef4444, #f59e0b, #10b981, #3b82f6, #8b5cf6, #ef4444);
178
+ }
179
+
180
+ .color-swatch.custom input {
181
+ position: absolute;
182
+ inset: 0;
183
+ width: 100%;
184
+ height: 100%;
185
+ padding: 0;
186
+ border: 0;
187
+ opacity: 0;
188
+ cursor: pointer;
189
+ }
190
+
191
+ .world {
192
+ position: absolute;
193
+ top: 0;
194
+ left: 0;
195
+ transform-origin: 0 0;
196
+ will-change: transform;
197
+ }
198
+
199
+ .group {
200
+ position: absolute;
201
+ border-radius: 16px;
202
+ border: 1px solid color-mix(in srgb, var(--layer) 35%, var(--border));
203
+ background: var(--group-bg);
204
+ transition: opacity 0.3s ease;
205
+ /* Only the title bar and border take the pointer (below), so the inside still pans the canvas. */
206
+ pointer-events: none;
207
+ }
208
+
209
+ .group.selected {
210
+ border-color: var(--accent);
211
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent);
212
+ }
213
+
214
+ .swimlane {
215
+ border-radius: 4px;
216
+ }
217
+
218
+ .swimlane .group-bar {
219
+ height: 88px;
220
+ border-radius: 4px 4px 0 0;
221
+ border-bottom: 1px solid var(--border);
222
+ background: var(--surface);
223
+ }
224
+
225
+ .lane-description {
226
+ position: absolute;
227
+ inset: 38px 20px 8px;
228
+ overflow: hidden;
229
+ white-space: pre-wrap;
230
+ overflow-wrap: anywhere;
231
+ font-size: 12px;
232
+ color: var(--muted);
233
+ }
234
+
235
+ .lane-error {
236
+ position: absolute;
237
+ top: 96px;
238
+ left: 24px;
239
+ z-index: 2;
240
+ padding: 8px;
241
+ background: var(--surface);
242
+ color: var(--text);
243
+ }
244
+
245
+ .lane-content {
246
+ position: absolute;
247
+ inset: 88px 0 0;
248
+ pointer-events: auto;
249
+ }
250
+
251
+ .lane-edit {
252
+ position: absolute;
253
+ z-index: 8;
254
+ display: grid;
255
+ gap: 8px;
256
+ padding: 12px;
257
+ border: 1px solid var(--accent);
258
+ border-radius: 8px;
259
+ background: var(--surface);
260
+ box-sizing: border-box;
261
+ pointer-events: auto;
262
+ }
263
+
264
+ .lane-edit label {
265
+ display: grid;
266
+ gap: 4px;
267
+ }
268
+
269
+ .lane-edit input,
270
+ .lane-edit textarea {
271
+ color: var(--text);
272
+ background: var(--bg);
273
+ border: 1px solid var(--border);
274
+ padding: 6px;
275
+ font: inherit;
276
+ }
277
+
278
+ .swimlane-controls {
279
+ display: flex;
280
+ flex-wrap: wrap;
281
+ align-items: center;
282
+ gap: 4px;
283
+ pointer-events: auto;
284
+ flex-basis: 100%;
285
+ }
286
+
287
+ .canvas-head:has(.swimlane-controls) {
288
+ flex-wrap: wrap;
289
+ }
290
+
291
+ .canvas-head:has(.swimlane-controls) h1 {
292
+ margin: 0;
293
+ min-width: 0;
294
+ flex: 1;
295
+ }
296
+
297
+ .swimlane-controls .dg-btn,
298
+ .swimlane-controls select {
299
+ font-size: 12px;
300
+ padding: 5px 8px;
301
+ color: var(--text);
302
+ background: var(--surface);
303
+ border: 1px solid var(--border);
304
+ border-radius: 6px;
305
+ }
306
+
307
+ .group-bar,
308
+ .group-edge {
309
+ position: absolute;
310
+ pointer-events: auto;
311
+ cursor: grab;
312
+ }
313
+
314
+ .group-bar {
315
+ top: 0;
316
+ left: 0;
317
+ right: 0;
318
+ height: 44px;
319
+ border-radius: 16px 16px 0 0;
320
+ }
321
+
322
+ .group-edge-s { left: 0; right: 0; bottom: -4px; height: 9px; }
323
+ .group-edge-w { top: 44px; bottom: 0; left: -4px; width: 9px; }
324
+ .group-edge-e { top: 44px; bottom: 0; right: -4px; width: 9px; }
325
+
326
+ .group-title {
327
+ position: absolute;
328
+ top: 16px;
329
+ left: 20px;
330
+ user-select: none;
331
+ font-size: 13px;
332
+ font-weight: 600;
333
+ letter-spacing: 0.02em;
334
+ color: var(--layer);
335
+ }
336
+
337
+ /* A node or group is drawn in --layer: its own colour when it has one (set inline), else the neutral default. */
338
+ .node,
339
+ .group {
340
+ --layer: var(--node-default);
341
+ }
342
+
343
+ .edges,
344
+ .labels {
345
+ position: absolute;
346
+ top: 0;
347
+ left: 0;
348
+ overflow: visible;
349
+ pointer-events: none;
350
+ }
351
+
352
+ .edge {
353
+ fill: none;
354
+ stroke: var(--edge);
355
+ stroke-width: 1.6;
356
+ opacity: 0.85;
357
+ transition: opacity 0.3s ease, stroke-width 0.3s ease;
358
+ }
359
+
360
+ .edge.dashed {
361
+ stroke: var(--edge-dashed);
362
+ stroke-dasharray: 7 5;
363
+ }
364
+
365
+ .edge.optional {
366
+ stroke: var(--edge-optional);
367
+ }
368
+
369
+ .edge.colored {
370
+ stroke: var(--edge-color);
371
+ }
372
+
373
+ .edge.selected {
374
+ stroke: var(--accent);
375
+ stroke-width: 3;
376
+ opacity: 1;
377
+ }
378
+
379
+ .edge.hl-visited {
380
+ stroke: var(--tools);
381
+ stroke-width: 2.4;
382
+ opacity: 0.95;
383
+ }
384
+
385
+ /* The edge a scenario is travelling right now: dashes flow toward the target. */
386
+ .edge.hl-active {
387
+ stroke: var(--tools);
388
+ stroke-width: 4.2;
389
+ stroke-dasharray: 9 7;
390
+ opacity: 1;
391
+ filter: drop-shadow(0 0 5px color-mix(in srgb, var(--tools) 75%, transparent));
392
+ animation: edge-flow 0.7s linear infinite;
393
+ }
394
+
395
+ .edge-hit {
396
+ fill: none;
397
+ stroke: transparent;
398
+ stroke-width: 16;
399
+ pointer-events: stroke;
400
+ cursor: pointer;
401
+ }
402
+
403
+ .edge-label.selected .label-bg {
404
+ stroke: var(--accent);
405
+ }
406
+
407
+ .edge-label.selected .label-text {
408
+ fill: var(--text);
409
+ }
410
+
411
+ .arrow-head {
412
+ fill: var(--edge);
413
+ }
414
+
415
+ .label-bg {
416
+ fill: var(--surface);
417
+ stroke: var(--border);
418
+ }
419
+
420
+ .label-text {
421
+ font-size: 10.5px;
422
+ fill: var(--muted);
423
+ font-family: var(--font);
424
+ }
425
+
426
+ .edge-label {
427
+ transition: opacity 0.3s ease;
428
+ }
429
+
430
+ .edge-label.hl-visited .label-bg {
431
+ stroke: var(--tools);
432
+ }
433
+
434
+ /* The label of the edge being travelled stands out like a tag on it. */
435
+ .edge-label.hl-active .label-bg {
436
+ fill: var(--tools);
437
+ stroke: var(--tools);
438
+ }
439
+
440
+ .edge-label.hl-active .label-text {
441
+ fill: #1b1405;
442
+ font-weight: 700;
443
+ }
444
+
445
+ .resize-box {
446
+ position: absolute;
447
+ pointer-events: none;
448
+ }
449
+
450
+ .resize-handle {
451
+ position: absolute;
452
+ width: calc(10px * var(--inv, 1));
453
+ height: calc(10px * var(--inv, 1));
454
+ margin: calc(-5px * var(--inv, 1)) 0 0 calc(-5px * var(--inv, 1));
455
+ border: calc(1.5px * var(--inv, 1)) solid var(--accent);
456
+ border-radius: 2px;
457
+ background: var(--bg, #fff);
458
+ pointer-events: auto;
459
+ touch-action: none;
460
+ }
461
+
462
+ /* Where each resize handle sits on the node or group it resizes. */
463
+ .resize-nw { left: 0; top: 0; cursor: nwse-resize; }
464
+ .resize-n { left: 50%; top: 0; cursor: ns-resize; }
465
+ .resize-ne { left: 100%; top: 0; cursor: nesw-resize; }
466
+ .resize-e { left: 100%; top: 50%; cursor: ew-resize; }
467
+ .resize-se { left: 100%; top: 100%; cursor: nwse-resize; }
468
+ .resize-s { left: 50%; top: 100%; cursor: ns-resize; }
469
+ .resize-sw { left: 0; top: 100%; cursor: nesw-resize; }
470
+ .resize-w { left: 0; top: 50%; cursor: ew-resize; }
471
+
472
+ .connect-box {
473
+ position: absolute;
474
+ pointer-events: none;
475
+ }
476
+
477
+ .connect-handle {
478
+ position: absolute;
479
+ width: calc(14px * var(--inv, 1));
480
+ height: calc(14px * var(--inv, 1));
481
+ margin: calc(-7px * var(--inv, 1)) 0 0 calc(-7px * var(--inv, 1));
482
+ border-radius: 50%;
483
+ border: calc(2px * var(--inv, 1)) solid var(--accent);
484
+ background: var(--bg, #fff);
485
+ cursor: crosshair;
486
+ pointer-events: auto;
487
+ touch-action: none;
488
+ }
489
+
490
+ /* Outside the node, past the resize handles that sit on its edges. */
491
+ .connect-n { left: 50%; top: calc(-18px * var(--inv, 1)); }
492
+ .connect-s { left: 50%; top: calc(100% + 18px * var(--inv, 1)); }
493
+ .connect-w { left: calc(-18px * var(--inv, 1)); top: 50%; }
494
+ .connect-e { left: calc(100% + 18px * var(--inv, 1)); top: 50%; }
495
+
496
+ .connect-handle:hover {
497
+ background: var(--accent);
498
+ }
499
+
500
+ .connect-preview {
501
+ position: absolute;
502
+ left: 0;
503
+ top: 0;
504
+ overflow: visible;
505
+ pointer-events: none;
506
+ }
507
+
508
+ .connect-line {
509
+ fill: none;
510
+ stroke: var(--accent);
511
+ stroke-width: 2;
512
+ stroke-dasharray: 6 4;
513
+ }
514
+
515
+ .connect-line.ready {
516
+ stroke-dasharray: none;
517
+ }
518
+
519
+ .node.connect-target {
520
+ outline: 3px solid var(--accent);
521
+ outline-offset: 3px;
522
+ }
523
+
524
+ .node {
525
+ position: absolute;
526
+ padding: 14px 16px;
527
+ border-radius: 10px;
528
+ background: var(--node-bg);
529
+ border: 1.5px solid var(--layer);
530
+ box-shadow: var(--node-shadow);
531
+ overflow: hidden;
532
+ cursor: grab;
533
+ user-select: none;
534
+ transition: opacity 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease;
535
+ }
536
+
537
+ .node.selected {
538
+ outline: 3px solid var(--accent);
539
+ outline-offset: 3px;
540
+ }
541
+
542
+ .node:has(.node-expand) h2 {
543
+ padding-right: 26px;
544
+ }
545
+
546
+ .node-expand {
547
+ position: absolute;
548
+ top: 9px;
549
+ right: 9px;
550
+ display: grid;
551
+ place-items: center;
552
+ width: 26px;
553
+ height: 26px;
554
+ padding: 0;
555
+ color: var(--text);
556
+ background: var(--node-bg);
557
+ border: 1px solid var(--border);
558
+ border-radius: 5px;
559
+ cursor: pointer;
560
+ }
561
+
562
+ .node-expand:hover,
563
+ .node-expand:focus-visible {
564
+ background: var(--surface-2);
565
+ outline: 2px solid var(--accent);
566
+ }
567
+
568
+ /* Staleness of the code a node links to: top left, and absent when everything is fine. Shape differs per state, colour adds to it. */
569
+ .node:has(.node-link-badge) h2 {
570
+ padding-left: 24px;
571
+ }
572
+
573
+ .node-link-badge {
574
+ position: absolute;
575
+ top: 10px;
576
+ left: 9px;
577
+ display: inline-flex;
578
+ align-items: center;
579
+ gap: 2px;
580
+ height: 20px;
581
+ padding: 0 2px;
582
+ color: var(--muted);
583
+ background: var(--node-bg);
584
+ border: 0;
585
+ border-radius: 999px;
586
+ font: inherit;
587
+ font-size: 11px;
588
+ font-weight: 700;
589
+ cursor: pointer;
590
+ }
591
+
592
+ .node-link-badge.link-changed { color: #b45309; }
593
+ .node-link-badge.link-missing { color: #dc2626; }
594
+ .node-link-badge.link-moved { color: #2563eb; }
595
+
596
+ .node-link-badge:hover,
597
+ .node-link-badge:focus-visible {
598
+ background: var(--surface-2);
599
+ outline: 2px solid var(--accent);
600
+ }
601
+
602
+ /* A node with a decision log carries a small tag in its corner that opens it. */
603
+ .node-decisions {
604
+ position: absolute;
605
+ right: 9px;
606
+ bottom: 9px;
607
+ display: inline-flex;
608
+ align-items: center;
609
+ gap: 4px;
610
+ padding: 2px 7px;
611
+ color: var(--muted);
612
+ background: var(--node-bg);
613
+ border: 1px solid var(--border);
614
+ border-radius: 999px;
615
+ font: inherit;
616
+ font-size: 11px;
617
+ cursor: pointer;
618
+ }
619
+
620
+ .node-decisions:hover,
621
+ .node-decisions:focus-visible {
622
+ color: var(--text);
623
+ background: var(--surface-2);
624
+ outline: 2px solid var(--accent);
625
+ }
626
+
627
+ /* Emphasis driven by the highlight store (for example while a scenario plays) */
628
+ .node.hl-visited {
629
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--tools) 45%, transparent), var(--node-shadow);
630
+ }
631
+
632
+ /* The current step lifts off the canvas: slightly larger, brighter ring, deeper shadow. */
633
+ .node.hl-active,
634
+ .node.hl-success,
635
+ .node.hl-failure,
636
+ .node.hl-blocked {
637
+ z-index: 6;
638
+ transform: scale(1.04);
639
+ }
640
+
641
+ .node.hl-active {
642
+ outline: 3px solid var(--tools);
643
+ outline-offset: 3px;
644
+ box-shadow:
645
+ 0 0 0 6px color-mix(in srgb, var(--tools) 26%, transparent),
646
+ 0 16px 36px rgba(0, 0, 0, 0.4);
647
+ animation: node-pulse 1.1s ease-in-out infinite;
648
+ }
649
+
650
+ .node.hl-success {
651
+ outline: 3px solid var(--ok);
652
+ outline-offset: 3px;
653
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--ok) 24%, transparent), 0 16px 36px rgba(0, 0, 0, 0.4);
654
+ }
655
+
656
+ .node.hl-failure {
657
+ outline: 3px solid var(--danger);
658
+ outline-offset: 3px;
659
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--danger) 24%, transparent), 0 16px 36px rgba(0, 0, 0, 0.4);
660
+ }
661
+
662
+ .node.hl-blocked {
663
+ outline: 3px dashed var(--tools);
664
+ outline-offset: 3px;
665
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--tools) 20%, transparent), 0 16px 36px rgba(0, 0, 0, 0.4);
666
+ }
667
+
668
+ /* Focus: while a run is on screen, fade whatever it has not touched so the flow stands
669
+ out. Three tiers: the current step is full strength, the trail already played is
670
+ readable, everything else recedes. */
671
+ .world.focus .group {
672
+ opacity: 0.4;
673
+ }
674
+
675
+ .world.focus .node:not([class*='hl-']) {
676
+ opacity: 0.18;
677
+ filter: grayscale(0.7);
678
+ }
679
+
680
+ .world.focus .node.hl-visited {
681
+ opacity: 0.72;
682
+ }
683
+
684
+ .world.focus .edge:not([class*='hl-']) {
685
+ opacity: 0.1;
686
+ }
687
+
688
+ .world.focus .edge.hl-visited {
689
+ opacity: 0.7;
690
+ }
691
+
692
+ .world.focus .edge-label:not([class*='hl-']) {
693
+ opacity: 0.08;
694
+ }
695
+
696
+ .world.focus .edge-label.hl-visited {
697
+ opacity: 0.65;
698
+ }
699
+
700
+ .node.editing {
701
+ cursor: auto;
702
+ user-select: auto;
703
+ display: flex;
704
+ flex-direction: column;
705
+ }
706
+
707
+ .node-editor {
708
+ display: flex;
709
+ flex-direction: column;
710
+ gap: 8px;
711
+ flex: 1;
712
+ min-height: 0;
713
+ cursor: auto;
714
+ }
715
+
716
+ .node-edit-title,
717
+ .node-edit-body,
718
+ .edge-edit {
719
+ font: inherit;
720
+ color: var(--text, inherit);
721
+ background: var(--bg, #fff);
722
+ border: 1px solid var(--accent);
723
+ border-radius: 6px;
724
+ padding: 4px 8px;
725
+ outline: none;
726
+ }
727
+
728
+ .node-edit-title {
729
+ font-size: 15px;
730
+ font-weight: 650;
731
+ }
732
+
733
+ .node-edit-body {
734
+ flex: 1;
735
+ min-height: 0;
736
+ resize: none;
737
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
738
+ font-size: 12px;
739
+ line-height: 1.45;
740
+ }
741
+
742
+ .group-edit {
743
+ position: absolute;
744
+ z-index: 5;
745
+ font-size: 13px;
746
+ font-weight: 600;
747
+ letter-spacing: 0.02em;
748
+ color: var(--text);
749
+ background: var(--bg);
750
+ border: 1px solid var(--accent);
751
+ border-radius: 6px;
752
+ padding: 3px 8px;
753
+ outline: none;
754
+ }
755
+
756
+ .edge-edit {
757
+ position: absolute;
758
+ transform: translate(-50%, -50%);
759
+ font-size: 12px;
760
+ z-index: 5;
761
+ }
762
+
763
+ .node h2 {
764
+ font-size: 15px;
765
+ font-weight: 650;
766
+ margin-bottom: 8px;
767
+ }
768
+
769
+ .zoom {
770
+ position: absolute;
771
+ left: 16px;
772
+ bottom: 16px;
773
+ z-index: 3;
774
+ display: flex;
775
+ align-items: center;
776
+ gap: 2px;
777
+ padding: 3px;
778
+ border: 1px solid var(--border);
779
+ border-radius: 8px;
780
+ background: var(--surface);
781
+ font-size: 12px;
782
+ cursor: default;
783
+ }
784
+
785
+ .zoom .dg-icon-btn {
786
+ width: 22px;
787
+ height: 22px;
788
+ border-radius: 5px;
789
+ font-size: 14px;
790
+ }
791
+
792
+ .zoom .dg-btn {
793
+ padding: 0 8px;
794
+ height: 22px;
795
+ border-radius: 5px;
796
+ font-size: 12px;
797
+ }
798
+
799
+ .zoom-pct {
800
+ min-width: 36px;
801
+ text-align: center;
802
+ font-variant-numeric: tabular-nums;
803
+ font-weight: 500;
804
+ color: var(--muted, inherit);
805
+ }
806
+
807
+ @media (max-width: 760px) {
808
+
809
+ .canvas {
810
+ flex: 1 1 0;
811
+ min-height: 0;
812
+ }
813
+
814
+ .canvas-head h1 {
815
+ font-size: 16px;
816
+ }
817
+ }
818
+
819
+
820
+ .canvas-empty {
821
+ position: absolute;
822
+ left: 50%;
823
+ top: 46%;
824
+ transform: translate(-50%, -50%);
825
+ display: flex;
826
+ flex-direction: column;
827
+ gap: 6px;
828
+ max-width: 340px;
829
+ padding: 16px 18px;
830
+ border: 1px dashed var(--border);
831
+ border-radius: 12px;
832
+ background: color-mix(in srgb, var(--surface) 88%, transparent);
833
+ color: var(--muted);
834
+ font-size: 13px;
835
+ text-align: center;
836
+ pointer-events: none;
837
+ }
838
+
839
+ .canvas-empty strong {
840
+ color: var(--text);
841
+ font-size: 14px;
842
+ }
843
+
844
+ .zoom .dg-btn {
845
+ white-space: nowrap;
846
+ }
847
+
848
+ /* The right-click menu on the canvas */
849
+ .canvas-menu {
850
+ position: fixed;
851
+ z-index: 50;
852
+ display: grid;
853
+ min-width: 170px;
854
+ padding: 4px;
855
+ border: 1px solid var(--border);
856
+ border-radius: 8px;
857
+ background: var(--surface);
858
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
859
+ }
860
+
861
+ .canvas-menu button {
862
+ display: flex;
863
+ align-items: center;
864
+ gap: 8px;
865
+ padding: 7px 10px;
866
+ border: 0;
867
+ border-radius: 6px;
868
+ background: transparent;
869
+ color: var(--text);
870
+ font: inherit;
871
+ font-size: 13px;
872
+ text-align: left;
873
+ cursor: pointer;
874
+ }
875
+
876
+ .canvas-menu button:hover,
877
+ .canvas-menu button:focus-visible {
878
+ background: color-mix(in srgb, var(--accent) 14%, transparent);
879
+ outline: none;
880
+ }
881
+ @keyframes edge-flow {
882
+ to {
883
+ stroke-dashoffset: -16;
884
+ }
885
+ }
886
+
887
+ @keyframes node-pulse {
888
+ 50% {
889
+ outline-offset: 6px;
890
+ outline-color: color-mix(in srgb, var(--tools) 55%, transparent);
891
+ }
892
+ }