libpetri 1.8.5 → 2.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 (46) hide show
  1. package/README.md +47 -0
  2. package/dist/{chunk-B2D5DMTO.js → chunk-B2CV5M3L.js} +228 -9
  3. package/dist/chunk-B2CV5M3L.js.map +1 -0
  4. package/dist/chunk-H62Z76FY.js +1346 -0
  5. package/dist/chunk-H62Z76FY.js.map +1 -0
  6. package/dist/chunk-RNWTYK5B.js +419 -0
  7. package/dist/chunk-RNWTYK5B.js.map +1 -0
  8. package/dist/chunk-SXK2Z45Z.js +50 -0
  9. package/dist/chunk-SXK2Z45Z.js.map +1 -0
  10. package/dist/debug/index.d.ts +50 -3
  11. package/dist/debug/index.js +64 -6
  12. package/dist/debug/index.js.map +1 -1
  13. package/dist/doclet/index.d.ts +152 -31
  14. package/dist/doclet/index.js +458 -57
  15. package/dist/doclet/index.js.map +1 -1
  16. package/dist/doclet/resources/petrinet-diagrams.css +640 -7
  17. package/dist/doclet/resources/petrinet-diagrams.js +7238 -114
  18. package/dist/dot-exporter-WJMCJEFK.js +8 -0
  19. package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
  20. package/dist/export/index.d.ts +18 -4
  21. package/dist/export/index.js +1 -1
  22. package/dist/index.d.ts +41 -5
  23. package/dist/index.js +1221 -35
  24. package/dist/index.js.map +1 -1
  25. package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
  26. package/dist/render-QK57X4TP.js +73 -0
  27. package/dist/render-QK57X4TP.js.map +1 -0
  28. package/dist/verification/index.d.ts +3 -144
  29. package/dist/verification/index.js +30 -1214
  30. package/dist/verification/index.js.map +1 -1
  31. package/dist/viewer/index.d.ts +227 -0
  32. package/dist/viewer/index.js +877 -0
  33. package/dist/viewer/index.js.map +1 -0
  34. package/dist/viewer/layout/index.d.ts +200 -0
  35. package/dist/viewer/layout/index.js +15 -0
  36. package/dist/viewer/layout/index.js.map +1 -0
  37. package/dist/viewer/viewer-static.iife.js +3 -0
  38. package/dist/viewer/viewer.css +759 -0
  39. package/dist/viewer/viewer.iife.js +7243 -0
  40. package/package.json +28 -8
  41. package/dist/chunk-B2D5DMTO.js.map +0 -1
  42. package/dist/chunk-VQ4XMJTD.js +0 -107
  43. package/dist/chunk-VQ4XMJTD.js.map +0 -1
  44. package/dist/dot-exporter-3CVCH6J4.js +0 -8
  45. package/dist/petri-net-D-GN9g_D.d.ts +0 -570
  46. /package/dist/{dot-exporter-3CVCH6J4.js.map → dot-exporter-WJMCJEFK.js.map} +0 -0
@@ -1,15 +1,62 @@
1
+ /* ============================================================
2
+ * Canonical libpetri Petri-net diagram viewer stylesheet.
3
+ *
4
+ * Source of truth: typescript/src/viewer/resources/viewer.css.
5
+ * This file is the union of the old petrinet-diagrams.css (Javadoc taglet)
6
+ * and the cluster-overlay rules from debug-ui/src/styles.css. It is
7
+ * shipped as `petrinet-diagrams.css` to all consumers via the build
8
+ * script (scripts/build-viewer.sh). Edit HERE, not in the consumer dirs.
9
+ *
10
+ * All visual constants surface as CSS custom properties (`--lpv-*`) so
11
+ * individual consumers can theme without forking. Defaults match the
12
+ * doclet palette + the user's "clusters must be visually obvious"
13
+ * feedback (deeper tint, thicker stroke, more prominent dash).
14
+ * ============================================================ */
15
+
16
+ :root,
17
+ .petrinet-diagram,
18
+ .libpetri-viewer {
19
+ --lpv-bg: #fafafa;
20
+ --lpv-header-bg: #f0f0f0;
21
+ --lpv-border: #ddd;
22
+ --lpv-text: #111827;
23
+ --lpv-muted: #6b7280;
24
+
25
+ /* Cluster visuals — user feedback: clusters must be visually obvious.
26
+ Bumped the cluster background from a near-white tint to a saturated
27
+ indigo wash, thickened the stroke, and lengthened the dashes. */
28
+ --lpv-cluster-bg: #eef2ff;
29
+ --lpv-cluster-bg-collapsed: #cbd5e1;
30
+ --lpv-cluster-stroke-width: 2.2;
31
+ --lpv-cluster-stroke-dash: 6 3;
32
+ --lpv-cluster-fill-tint: rgba(99, 102, 241, 0.08);
33
+
34
+ --lpv-dim-opacity: 0.2;
35
+ --lpv-active-filter-outline: rgba(245, 158, 11, 0.4);
36
+
37
+ --lpv-legend-bg: rgba(255, 255, 255, 0.95);
38
+ --lpv-chip-bg: #ffffff;
39
+ --lpv-chip-active-bg: #111827;
40
+ }
41
+
42
+ /* ============================================================
43
+ * Top-level diagram container
44
+ * Kept for backward compatibility with the existing Javadoc/Rustdoc
45
+ * taglet markup. Live consumers (debug-ui, dev-preview) don't use the
46
+ * outer .petrinet-diagram chrome; their styling lives in debug-ui CSS.
47
+ * ============================================================ */
1
48
  .petrinet-diagram {
2
49
  margin: 1.5em 0;
3
- border: 1px solid #ddd;
50
+ border: 1px solid var(--lpv-border);
4
51
  border-radius: 8px;
5
- background: #fafafa;
52
+ background: var(--lpv-bg);
6
53
  }
7
54
 
8
55
  .petrinet-diagram h4 {
9
56
  margin: 0;
10
57
  padding: 12px 16px;
11
- background: #f0f0f0;
12
- border-bottom: 1px solid #ddd;
58
+ background: var(--lpv-header-bg);
59
+ border-bottom: 1px solid var(--lpv-border);
13
60
  border-radius: 8px 8px 0 0;
14
61
  }
15
62
 
@@ -36,7 +83,35 @@
36
83
  height: 100%;
37
84
  }
38
85
 
39
- .petrinet-diagram .diagram-controls {
86
+ /* 2.0 mount target — the host div emitted by the Java/Rust/TS doclet
87
+ taglets. Needs the same sizing constraints as the legacy
88
+ .diagram-container, otherwise the host grows to the Graphviz-emitted
89
+ SVG intrinsic size and produces a 9000+ px tall column. With this
90
+ rule the SVG fills the constrained host and viewBox auto-fits. */
91
+ .petrinet-diagram-viewer {
92
+ display: block;
93
+ position: relative;
94
+ width: 100%;
95
+ height: 70vh;
96
+ min-height: 400px;
97
+ max-height: 700px;
98
+ overflow: hidden;
99
+ background: var(--lpv-bg);
100
+ border: 1px solid var(--lpv-border);
101
+ border-radius: 4px;
102
+ }
103
+
104
+ /* Override Graphviz's explicit pt width/height on the rendered SVG.
105
+ Without this the SVG retains its intrinsic 5021pt × 7203pt size,
106
+ defeating the host height constraint. */
107
+ .petrinet-diagram-viewer > svg {
108
+ display: block;
109
+ width: 100% !important;
110
+ height: 100% !important;
111
+ }
112
+
113
+ .petrinet-diagram .diagram-controls,
114
+ .libpetri-viewer .diagram-controls {
40
115
  position: absolute;
41
116
  top: 8px;
42
117
  right: 8px;
@@ -45,7 +120,8 @@
45
120
  z-index: 10;
46
121
  }
47
122
 
48
- .petrinet-diagram .diagram-btn {
123
+ .petrinet-diagram .diagram-btn,
124
+ .libpetri-viewer .diagram-btn {
49
125
  padding: 6px 10px;
50
126
  border: 1px solid #ccc;
51
127
  border-radius: 4px;
@@ -55,7 +131,8 @@
55
131
  min-width: 32px;
56
132
  }
57
133
 
58
- .petrinet-diagram .diagram-btn:hover {
134
+ .petrinet-diagram .diagram-btn:hover,
135
+ .libpetri-viewer .diagram-btn:hover {
59
136
  background: #e8e8e8;
60
137
  }
61
138
 
@@ -107,6 +184,15 @@
107
184
  max-height: none;
108
185
  }
109
186
 
187
+ /* 2.0 host is the mount target itself (no .diagram-container wrapper),
188
+ so size the direct child SVG to fill the viewport in fullscreen. */
189
+ .libpetri-viewer.diagram-fullscreen > svg {
190
+ flex: 1;
191
+ width: 100%;
192
+ height: 100%;
193
+ min-height: 0;
194
+ }
195
+
110
196
  /* Zoom indicator */
111
197
  .zoom-indicator {
112
198
  position: absolute;
@@ -124,3 +210,550 @@
124
210
  .zoom-indicator.visible {
125
211
  opacity: 1;
126
212
  }
213
+
214
+ /* ============================================================
215
+ * Subnet header (badges row above the diagram)
216
+ * ============================================================ */
217
+ .petrinet-diagram .subnet-header {
218
+ padding: 12px 16px;
219
+ border-bottom: 1px solid #e5e7eb;
220
+ background: #f8fafc;
221
+ }
222
+
223
+ .subnet-header-row {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 8px;
227
+ font-size: 14px;
228
+ flex-wrap: wrap;
229
+ }
230
+
231
+ .subnet-header-label {
232
+ font-weight: 600;
233
+ color: var(--lpv-muted);
234
+ text-transform: uppercase;
235
+ font-size: 11px;
236
+ letter-spacing: 0.05em;
237
+ }
238
+
239
+ .subnet-header-name {
240
+ font-weight: 600;
241
+ color: var(--lpv-text);
242
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
243
+ }
244
+
245
+ .subnet-header-of {
246
+ color: var(--lpv-muted);
247
+ font-size: 12px;
248
+ }
249
+
250
+ .subnet-header-param,
251
+ .subnet-header-params {
252
+ color: #6d28d9;
253
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
254
+ font-size: 12px;
255
+ }
256
+
257
+ .subnet-header-params-null {
258
+ color: #9ca3af;
259
+ font-style: italic;
260
+ }
261
+
262
+ .subnet-header-badge {
263
+ padding: 2px 8px;
264
+ border-radius: 4px;
265
+ background: #fef3c7;
266
+ color: #92400e;
267
+ font-size: 11px;
268
+ font-weight: 500;
269
+ }
270
+
271
+ .subnet-header-badge-interface {
272
+ background: #e0e7ff;
273
+ color: #3730a3;
274
+ }
275
+
276
+ .subnet-header-badges {
277
+ display: flex;
278
+ gap: 6px;
279
+ margin-top: 8px;
280
+ flex-wrap: wrap;
281
+ }
282
+
283
+ .interface-port-badge,
284
+ .interface-channel-badge {
285
+ display: inline-flex;
286
+ align-items: center;
287
+ gap: 4px;
288
+ padding: 3px 8px;
289
+ border-radius: 12px;
290
+ font-size: 11px;
291
+ border: 1px solid;
292
+ background: white;
293
+ }
294
+
295
+ .interface-port-badge .badge-direction,
296
+ .interface-channel-badge .badge-direction {
297
+ font-weight: 600;
298
+ text-transform: uppercase;
299
+ font-size: 10px;
300
+ letter-spacing: 0.04em;
301
+ }
302
+
303
+ .interface-port-badge .badge-name,
304
+ .interface-channel-badge .badge-name {
305
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
306
+ font-weight: 500;
307
+ }
308
+
309
+ .interface-port-badge .badge-type {
310
+ color: var(--lpv-muted);
311
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
312
+ font-size: 10px;
313
+ }
314
+
315
+ .interface-port-badge-input { border-color: #1d4ed8; color: #1d4ed8; background: #e7f1ff; }
316
+ .interface-port-badge-output { border-color: #047857; color: #047857; background: #d1fae5; }
317
+ .interface-port-badge-inout { border-color: #b45309; color: #b45309; background: #fef3c7; }
318
+ .interface-channel-badge { border-color: #6d28d9; color: #6d28d9; background: #f3e8ff; }
319
+
320
+ /* ============================================================
321
+ * Cluster legend sidebar
322
+ * ============================================================ */
323
+ .petrinet-diagram .diagram-legend,
324
+ .libpetri-viewer .diagram-legend {
325
+ position: absolute;
326
+ top: 8px;
327
+ left: 8px;
328
+ background: var(--lpv-legend-bg);
329
+ border: 1px solid #e5e7eb;
330
+ border-radius: 6px;
331
+ padding: 8px;
332
+ font-size: 12px;
333
+ z-index: 9;
334
+ max-height: calc(100% - 16px);
335
+ overflow-y: auto;
336
+ min-width: 160px;
337
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
338
+ }
339
+
340
+ .diagram-legend .legend-title {
341
+ font-weight: 600;
342
+ color: var(--lpv-muted);
343
+ text-transform: uppercase;
344
+ font-size: 10px;
345
+ letter-spacing: 0.05em;
346
+ margin-bottom: 6px;
347
+ }
348
+
349
+ .diagram-legend .legend-row {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 6px;
353
+ padding: 4px 6px;
354
+ border-radius: 4px;
355
+ cursor: pointer;
356
+ transition: background 0.15s;
357
+ }
358
+
359
+ .diagram-legend .legend-row:hover {
360
+ background: #f3f4f6;
361
+ }
362
+
363
+ .diagram-legend .legend-ribbon {
364
+ width: 4px;
365
+ height: 16px;
366
+ border-radius: 2px;
367
+ flex: 0 0 auto;
368
+ }
369
+
370
+ .diagram-legend .legend-label {
371
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
372
+ flex: 1 1 auto;
373
+ color: var(--lpv-text);
374
+ }
375
+
376
+ .diagram-legend .legend-count {
377
+ color: #9ca3af;
378
+ font-size: 10px;
379
+ }
380
+
381
+ /* ============================================================
382
+ * Filter chips
383
+ * ============================================================ */
384
+ .petrinet-diagram .diagram-filter-strip,
385
+ .libpetri-viewer .diagram-filter-strip {
386
+ position: absolute;
387
+ bottom: 8px;
388
+ left: 8px;
389
+ right: 8px;
390
+ display: flex;
391
+ flex-wrap: wrap;
392
+ align-items: center;
393
+ gap: 6px;
394
+ padding: 8px;
395
+ background: var(--lpv-legend-bg);
396
+ border: 1px solid #e5e7eb;
397
+ border-radius: 6px;
398
+ z-index: 8;
399
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
400
+ }
401
+
402
+ .filter-strip-label {
403
+ font-size: 11px;
404
+ font-weight: 600;
405
+ color: var(--lpv-muted);
406
+ text-transform: uppercase;
407
+ letter-spacing: 0.04em;
408
+ }
409
+
410
+ .filter-chip {
411
+ display: inline-flex;
412
+ align-items: center;
413
+ gap: 4px;
414
+ padding: 3px 8px;
415
+ border-radius: 14px;
416
+ font-size: 11px;
417
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
418
+ background: var(--lpv-chip-bg);
419
+ border: 1px solid #d1d5db;
420
+ cursor: pointer;
421
+ transition: background 0.15s, transform 0.15s;
422
+ }
423
+
424
+ .filter-chip:hover {
425
+ background: #f3f4f6;
426
+ }
427
+
428
+ .filter-chip-active {
429
+ background: var(--lpv-chip-active-bg);
430
+ color: white;
431
+ border-color: var(--lpv-chip-active-bg);
432
+ }
433
+
434
+ .filter-chip-active .chip-dot {
435
+ outline: 2px solid rgba(255, 255, 255, 0.4);
436
+ }
437
+
438
+ .filter-chip .chip-dot {
439
+ width: 8px;
440
+ height: 8px;
441
+ border-radius: 50%;
442
+ display: inline-block;
443
+ }
444
+
445
+ /* ============================================================
446
+ * Cluster visual baseline — make clusters visually obvious
447
+ *
448
+ * These rules apply globally to any rendered SVG that lives inside
449
+ * a `.petrinet-diagram` OR a `.libpetri-viewer` host. Tinted fill,
450
+ * thicker stroke, more prominent dash. Stroke colour is injected
451
+ * inline by the cluster-overlay (deterministic HSL per prefix).
452
+ * ============================================================ */
453
+ g.cluster > polygon,
454
+ g.cluster > path {
455
+ fill: var(--lpv-cluster-bg);
456
+ stroke-width: var(--lpv-cluster-stroke-width);
457
+ stroke-dasharray: var(--lpv-cluster-stroke-dash);
458
+ transition: fill 0.2s ease, stroke 0.2s ease;
459
+ }
460
+
461
+ /* ============================================================
462
+ * Cluster collapse / expand state
463
+ * ============================================================ */
464
+ .cluster-collapsed > polygon,
465
+ .cluster-collapsed > path {
466
+ fill: var(--lpv-cluster-bg-collapsed) !important;
467
+ stroke-dasharray: 3 3;
468
+ }
469
+
470
+ .cluster-collapsed-badge {
471
+ font-style: italic;
472
+ pointer-events: none;
473
+ }
474
+
475
+ /* Sibling nodes/edges hidden while their cluster is collapsed. The cluster
476
+ * <g> retains its layout box (border, label, badge); the interior elements
477
+ * disappear so the box reads as a single tile. */
478
+ g.node.petri-collapsed-inside,
479
+ g.edge.petri-collapsed-inside {
480
+ display: none;
481
+ }
482
+
483
+ /* ============================================================
484
+ * Filter dim state (CSS-driven, applied per node by JS)
485
+ * ============================================================ */
486
+ g.node.petri-dimmed,
487
+ g.edge.petri-dimmed {
488
+ opacity: var(--lpv-dim-opacity);
489
+ transition: opacity 0.2s ease;
490
+ }
491
+
492
+ g.node:not(.petri-dimmed),
493
+ g.edge:not(.petri-dimmed) {
494
+ transition: opacity 0.2s ease;
495
+ }
496
+
497
+ /* When the filter is active, give the SVG a hairline ring so the user
498
+ has a global signal that they're seeing a filtered view, not a partial
499
+ net. Matches the debug-ui's previous treatment. */
500
+ svg.has-active-filter {
501
+ outline: 1px solid var(--lpv-active-filter-outline);
502
+ outline-offset: -1px;
503
+ }
504
+
505
+ /* ============================================================
506
+ * C0 layout — left sidebar chrome
507
+ *
508
+ * The chip sidebar replaces the legacy `.diagram-legend` (top-right)
509
+ * and `.diagram-filter-strip` (bottom). Built by
510
+ * `viewer/chrome/sidebar.ts` when `mount({ chrome: true, layout: 'elk' })`.
511
+ * Lives inside the viewer container so it travels into fullscreen.
512
+ * ============================================================ */
513
+ :root,
514
+ .petrinet-diagram,
515
+ .libpetri-viewer {
516
+ --lpv-sidebar-bg: #fafafa;
517
+ --lpv-sidebar-border: #e2e2e2;
518
+ --lpv-sidebar-text: #333333;
519
+ --lpv-sidebar-muted: #6b7280;
520
+ --lpv-sidebar-chip-bg: #ffffff;
521
+ --lpv-sidebar-chip-hover-bg: #f4f4ff;
522
+ --lpv-sidebar-chip-off-opacity: 0.45;
523
+ --lpv-shared-glyph-color: #6b46c1;
524
+ --lpv-replica-fill: #f3f4f6;
525
+ --lpv-replica-stroke: #6b46c1;
526
+ --lpv-highlight-stroke: #d97706;
527
+ --lpv-highlight-glow: rgba(217, 119, 6, 0.55);
528
+ --lpv-neighbor-stroke: #2563eb;
529
+ --lpv-faded-node-opacity: 0.14;
530
+ --lpv-faded-edge-opacity: 0.08;
531
+ --lpv-faded-cluster-opacity: 0.25;
532
+ }
533
+
534
+ .libpetri-viewer .libpetri-sidebar {
535
+ position: absolute;
536
+ top: 8px;
537
+ left: 8px;
538
+ width: 220px;
539
+ max-height: calc(100% - 16px);
540
+ overflow-y: auto;
541
+ background: var(--lpv-sidebar-bg);
542
+ border: 1px solid var(--lpv-sidebar-border);
543
+ border-radius: 6px;
544
+ padding: 10px;
545
+ font-size: 12px;
546
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
547
+ color: var(--lpv-sidebar-text);
548
+ z-index: 9;
549
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
550
+ }
551
+
552
+ .libpetri-sidebar .libpetri-sidebar-title {
553
+ margin: 0 0 8px 0;
554
+ font-weight: 700;
555
+ font-size: 13px;
556
+ color: var(--lpv-sidebar-text);
557
+ }
558
+
559
+ .libpetri-sidebar .libpetri-sidebar-actions {
560
+ display: flex;
561
+ gap: 4px;
562
+ margin-bottom: 8px;
563
+ }
564
+
565
+ .libpetri-sidebar .libpetri-sidebar-actions button {
566
+ flex: 1 1 auto;
567
+ padding: 3px 8px;
568
+ font-size: 11px;
569
+ font-family: inherit;
570
+ border: 1px solid #ccc;
571
+ background: #fff;
572
+ border-radius: 4px;
573
+ cursor: pointer;
574
+ color: var(--lpv-sidebar-text);
575
+ }
576
+
577
+ .libpetri-sidebar .libpetri-sidebar-actions button:hover {
578
+ background: #f0f0f0;
579
+ }
580
+
581
+ .libpetri-sidebar .libpetri-sidebar-checkbox {
582
+ display: flex;
583
+ align-items: center;
584
+ gap: 6px;
585
+ margin: 6px 0;
586
+ font-size: 11.5px;
587
+ color: var(--lpv-sidebar-text);
588
+ cursor: pointer;
589
+ }
590
+
591
+ .libpetri-sidebar .libpetri-sidebar-hint {
592
+ font-size: 10.5px;
593
+ color: var(--lpv-sidebar-muted);
594
+ line-height: 1.4;
595
+ margin: 6px 0 10px 0;
596
+ border-top: 1px solid var(--lpv-sidebar-border);
597
+ padding-top: 8px;
598
+ }
599
+
600
+ .libpetri-sidebar .libpetri-sidebar-chips {
601
+ display: flex;
602
+ flex-direction: column;
603
+ gap: 4px;
604
+ margin: 0;
605
+ padding: 0;
606
+ list-style: none;
607
+ }
608
+
609
+ .libpetri-sidebar .libpetri-sidebar-chip {
610
+ display: flex;
611
+ align-items: center;
612
+ gap: 6px;
613
+ padding: 4px 8px;
614
+ border-radius: 4px;
615
+ cursor: pointer;
616
+ user-select: none;
617
+ border-left: 4px solid var(--lpv-shared-glyph-color);
618
+ background: var(--lpv-sidebar-chip-bg);
619
+ }
620
+
621
+ .libpetri-sidebar .libpetri-sidebar-chip:hover {
622
+ background: var(--lpv-sidebar-chip-hover-bg);
623
+ }
624
+
625
+ .libpetri-sidebar .libpetri-sidebar-chip.is-off {
626
+ opacity: var(--lpv-sidebar-chip-off-opacity);
627
+ border-left-color: #ccc;
628
+ }
629
+
630
+ .libpetri-sidebar .libpetri-sidebar-chip-dot {
631
+ flex: 0 0 10px;
632
+ width: 10px;
633
+ height: 10px;
634
+ border-radius: 50%;
635
+ }
636
+
637
+ .libpetri-sidebar .libpetri-sidebar-chip-name {
638
+ flex: 1 1 auto;
639
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
640
+ font-weight: 500;
641
+ }
642
+
643
+ .libpetri-sidebar .libpetri-sidebar-chip-count {
644
+ color: var(--lpv-sidebar-muted);
645
+ font-size: 10.5px;
646
+ }
647
+
648
+ /* ============================================================
649
+ * Replica visuals
650
+ *
651
+ * Replicas of shared places get a dashed light-gray ring and a small
652
+ * ⇄ glyph in the upper-right corner. The glyph is injected by
653
+ * `replica-tagging.ts` as a sibling `<text>` inside `<g class="node">`.
654
+ * ============================================================ */
655
+ g.node.petri-replica > ellipse,
656
+ g.node.petri-replica > circle {
657
+ fill: var(--lpv-replica-fill) !important;
658
+ stroke: var(--lpv-replica-stroke) !important;
659
+ stroke-width: 1.2 !important;
660
+ stroke-dasharray: 3 2 !important;
661
+ }
662
+
663
+ g.node.petri-replica > text.libpetri-replica-glyph {
664
+ fill: var(--lpv-shared-glyph-color);
665
+ font-weight: 700;
666
+ pointer-events: none;
667
+ }
668
+
669
+ /* ============================================================
670
+ * Highlight + visibility states
671
+ *
672
+ * `applyHighlight` (highlight.ts) sets `.is-highlight` on focus +
673
+ * every copy, `.is-neighbor` on real-place neighbors, `.is-faded`
674
+ * on the rest. `applyVisibility` (visibility.ts) sets `.is-hidden`
675
+ * on clusters / nodes / edges that the current selection excludes.
676
+ * Constants match v3 colors exactly.
677
+ * ============================================================ */
678
+ .libpetri-viewer svg g.node,
679
+ .libpetri-viewer svg g.edge {
680
+ transition: opacity 0.15s ease-out;
681
+ }
682
+
683
+ .libpetri-viewer svg g.cluster.is-hidden,
684
+ .libpetri-viewer svg g.edge.is-hidden,
685
+ .libpetri-viewer svg g.node.is-hidden {
686
+ display: none !important;
687
+ }
688
+
689
+ .libpetri-viewer svg g.node.is-faded {
690
+ opacity: var(--lpv-faded-node-opacity) !important;
691
+ }
692
+
693
+ .libpetri-viewer svg g.edge.is-faded {
694
+ opacity: var(--lpv-faded-edge-opacity) !important;
695
+ }
696
+
697
+ /* When a cluster is faded, fade only its own border + label, NOT its
698
+ * descendant nodes/edges — intra-cluster highlighted edges must still pop. */
699
+ .libpetri-viewer svg g.cluster.is-faded > polygon,
700
+ .libpetri-viewer svg g.cluster.is-faded > path,
701
+ .libpetri-viewer svg g.cluster.is-faded > text {
702
+ opacity: var(--lpv-faded-cluster-opacity);
703
+ }
704
+
705
+ .libpetri-viewer svg g.node.is-highlight > ellipse,
706
+ .libpetri-viewer svg g.node.is-highlight > circle,
707
+ .libpetri-viewer svg g.node.is-highlight > polygon,
708
+ .libpetri-viewer svg g.node.is-highlight > rect {
709
+ stroke: var(--lpv-highlight-stroke) !important;
710
+ stroke-width: 3 !important;
711
+ filter: drop-shadow(0 0 6px var(--lpv-highlight-glow));
712
+ }
713
+
714
+ .libpetri-viewer svg g.node.is-neighbor > ellipse,
715
+ .libpetri-viewer svg g.node.is-neighbor > circle,
716
+ .libpetri-viewer svg g.node.is-neighbor > polygon,
717
+ .libpetri-viewer svg g.node.is-neighbor > rect {
718
+ stroke: var(--lpv-neighbor-stroke) !important;
719
+ stroke-width: 2.5 !important;
720
+ }
721
+
722
+ .libpetri-viewer svg g.edge.is-highlight {
723
+ opacity: 1 !important;
724
+ }
725
+
726
+ .libpetri-viewer svg g.edge.is-highlight path {
727
+ stroke-width: 2.4 !important;
728
+ opacity: 1 !important;
729
+ filter: drop-shadow(0 0 4px rgba(217, 119, 6, 0.35));
730
+ }
731
+
732
+ /* ============================================================
733
+ * Dark theme overrides for the sidebar
734
+ *
735
+ * Triggered by `data-lpv-theme="dark"` on any ancestor (typically
736
+ * the viewer container itself). Debug-ui sets this so the sidebar
737
+ * matches the surrounding dark chrome.
738
+ * ============================================================ */
739
+ [data-lpv-theme="dark"] {
740
+ --lpv-sidebar-bg: #1f2937;
741
+ --lpv-sidebar-border: #374151;
742
+ --lpv-sidebar-text: #f3f4f6;
743
+ --lpv-sidebar-muted: #9ca3af;
744
+ --lpv-sidebar-chip-bg: #111827;
745
+ --lpv-sidebar-chip-hover-bg: #312e81;
746
+ --lpv-replica-fill: #312e81;
747
+ --lpv-replica-stroke: #a78bfa;
748
+ --lpv-shared-glyph-color: #a78bfa;
749
+ }
750
+
751
+ [data-lpv-theme="dark"] .libpetri-sidebar .libpetri-sidebar-actions button {
752
+ background: #111827;
753
+ color: var(--lpv-sidebar-text);
754
+ border-color: #374151;
755
+ }
756
+
757
+ [data-lpv-theme="dark"] .libpetri-sidebar .libpetri-sidebar-actions button:hover {
758
+ background: #374151;
759
+ }