@markdy/renderer-dom 0.8.10 → 0.8.12

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/README.md CHANGED
@@ -8,10 +8,11 @@ Web Animations API renderer for [MarkdyScript](../../docs/SYNTAX.md) scenes. Tra
8
8
  - **Auto-layout diagrams** — renders positioned nodes and orthogonal, obstacle-aware edges from a compiled `RenderPlan`
9
9
  - **Flow edges** — `->` request, `<-` response, `~>` event, `--` dependency, each with its own stroke, plus a pulse that travels the edge as it draws
10
10
  - **Beat-driven cues** — `show`, `hide`, `glow`, and `focus`, sequenced by named beats
11
+ - **Focused diagram visuals** — flowchart shapes, tree buses, state self-loops, sequence lifelines/messages, constellation orbits, group zones, and annotations
11
12
  - **Semantic node cards** — compact SVG glyphs for browsers, services, gateways, queues, workers, databases, storage, CDN, security, platform, and more
12
13
  - **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
13
14
  - **Playback-rate controls** — set timeline speed to slow down or speed up diagrams without rebuilding animations
14
- - **Semantic themes** — `midnight` and `paper`, with per-role node colors
15
+ - **Semantic themes** — `paper`, `editorial`, `nebula`, `midnight`, `blueprint`, and `graphite`, with per-role node and edge colors
15
16
  - **Single dependency** — only `@markdy/core`
16
17
 
17
18
  ## Installation
@@ -101,6 +102,11 @@ src/
101
102
  diagram.ts — Public API, rAF loop, progress bar, responsive scaling
102
103
  nodes.ts — Node element factory + scene title
103
104
  edges.ts — Flow-edge SVG runtime, routing, and cue animations
105
+ sequence.ts — Participant lifelines, messages, and activation spans
106
+ tree.ts — Shared parent/child bus connectors
107
+ groups.ts — Group boundary zones
108
+ annotations.ts — Editorial callouts and leader lines
109
+ constellation.ts — Nebula halos, orbit rings, and deterministic stars
104
110
  geometry/
105
111
  rect.ts — Rects, points, and hit-testing (DOM-free, unit tested)
106
112
  path.ts — Polyline measurement + obstacle-aware orthogonal routing
package/dist/index.d.ts CHANGED
@@ -41,4 +41,9 @@ interface Diagram {
41
41
  }
42
42
  declare function createDiagram(opts: DiagramOptions): Diagram;
43
43
 
44
- export { type Diagram, type DiagramOptions, createDiagram };
44
+ type SvgSpec = Array<[string, Record<string, string>]>;
45
+ type IconSpec = SvgSpec;
46
+ /** Read-only monochrome glyph registry; callers can inspect or choose keys without injecting markup. */
47
+ declare const ICON_REGISTRY: Readonly<Record<string, IconSpec>>;
48
+
49
+ export { type Diagram, type DiagramOptions, ICON_REGISTRY, type IconSpec, createDiagram };
package/dist/index.js CHANGED
@@ -49,8 +49,55 @@ function countPathIntersections(points, obstacles) {
49
49
  function round1(n) {
50
50
  return Math.round(n * 10) / 10;
51
51
  }
52
- function toPathD(points) {
53
- return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round1(p.x)} ${round1(p.y)}`).join(" ");
52
+ function toPathD(points, cornerRadius = 8) {
53
+ if (points.length < 2) return "";
54
+ if (points.length === 2) {
55
+ const [a, b] = points;
56
+ return `M ${round1(a.x)} ${round1(a.y)} L ${round1(b.x)} ${round1(b.y)}`;
57
+ }
58
+ if (cornerRadius <= 0) {
59
+ return points.map((p, i) => i === 0 ? `M ${round1(p.x)} ${round1(p.y)}` : `L ${round1(p.x)} ${round1(p.y)}`).join(" ");
60
+ }
61
+ const parts = [`M ${round1(points[0].x)} ${round1(points[0].y)}`];
62
+ for (let i = 1; i < points.length; i++) {
63
+ const prev = points[i - 1];
64
+ const cur = points[i];
65
+ const next = points[i + 1];
66
+ if (!next) {
67
+ parts.push(`L ${round1(cur.x)} ${round1(cur.y)}`);
68
+ continue;
69
+ }
70
+ const dx1 = cur.x - prev.x;
71
+ const dy1 = cur.y - prev.y;
72
+ const dx2 = next.x - cur.x;
73
+ const dy2 = next.y - cur.y;
74
+ const len1 = Math.hypot(dx1, dy1);
75
+ const len2 = Math.hypot(dx2, dy2);
76
+ if (len1 < 0.01 || len2 < 0.01) {
77
+ parts.push(`L ${round1(cur.x)} ${round1(cur.y)}`);
78
+ continue;
79
+ }
80
+ const r = Math.min(cornerRadius, len1 / 2, len2 / 2);
81
+ const bx = cur.x - dx1 / len1 * r;
82
+ const by = cur.y - dy1 / len1 * r;
83
+ const ax = cur.x + dx2 / len2 * r;
84
+ const ay = cur.y + dy2 / len2 * r;
85
+ parts.push(`L ${round1(bx)} ${round1(by)}`);
86
+ parts.push(`Q ${round1(cur.x)} ${round1(cur.y)} ${round1(ax)} ${round1(ay)}`);
87
+ }
88
+ return parts.join(" ");
89
+ }
90
+ function selfLoopPath(rect) {
91
+ const top = rect.y;
92
+ const left = rect.x + rect.width * 0.25;
93
+ const right = rect.x + rect.width * 0.75;
94
+ const apex = top - 36;
95
+ return [
96
+ { x: left, y: top },
97
+ { x: left, y: apex },
98
+ { x: right, y: apex },
99
+ { x: right, y: top }
100
+ ];
54
101
  }
55
102
  function polylineLength(points) {
56
103
  let total = 0;
@@ -125,6 +172,11 @@ function clampPointToScene(point, bounds) {
125
172
  y: round1(clamp(point.y, pad, bounds.height - pad))
126
173
  };
127
174
  }
175
+ function laneOffset(lane) {
176
+ if (lane <= 0) return 0;
177
+ const step = Math.ceil(lane / 2);
178
+ return (lane % 2 === 1 ? 1 : -1) * step * 18;
179
+ }
128
180
  function routeLength(points) {
129
181
  let total = 0;
130
182
  for (let i = 0; i < points.length - 1; i++) total += segmentLength(points[i], points[i + 1]);
@@ -145,12 +197,24 @@ function routeBends(points) {
145
197
  return bends;
146
198
  }
147
199
  function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
148
- const laneShift = lane * 18;
200
+ const laneShift = laneOffset(lane);
149
201
  const sourceCenter = rectCenter(sourceRect);
150
202
  const targetCenter = rectCenter(targetRect);
151
203
  const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
152
- const source = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? sourceRect.x2 : sourceRect.x1, y: sourceCenter.y } : { x: sourceCenter.x, y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1 };
153
- const target = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2, y: targetCenter.y } : { x: targetCenter.x, y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2 };
204
+ const source = horizontalPrimary ? {
205
+ x: targetCenter.x >= sourceCenter.x ? sourceRect.x2 : sourceRect.x1,
206
+ y: clamp(sourceCenter.y + laneShift, sourceRect.y1 + 12, sourceRect.y2 - 12)
207
+ } : {
208
+ x: clamp(sourceCenter.x + laneShift, sourceRect.x1 + 12, sourceRect.x2 - 12),
209
+ y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1
210
+ };
211
+ const target = horizontalPrimary ? {
212
+ x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2,
213
+ y: clamp(targetCenter.y + laneShift, targetRect.y1 + 12, targetRect.y2 - 12)
214
+ } : {
215
+ x: clamp(targetCenter.x + laneShift, targetRect.x1 + 12, targetRect.x2 - 12),
216
+ y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2
217
+ };
154
218
  const infl = obstacles.map((o) => inflateRect(o, 8));
155
219
  const candidates = [];
156
220
  if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
@@ -203,6 +267,11 @@ var INITIAL_CAMERA_TRANSFORM = "translate(0px, 0px) scale(1)";
203
267
  var DEFAULT_FRAME_ZOOM = 1.18;
204
268
  var MAX_FRAME_ZOOM = 1.75;
205
269
  var FRAME_PADDING = 88;
270
+ var edgeSceneCounter = 0;
271
+ function createEdgeSceneId() {
272
+ edgeSceneCounter += 1;
273
+ return `md-scene-${edgeSceneCounter}`;
274
+ }
206
275
  function dedupePoints(points) {
207
276
  const out = [];
208
277
  for (const p of points) {
@@ -243,8 +312,18 @@ function ensureDefs(svg, theme, id) {
243
312
  arrow.setAttribute("markerHeight", "7");
244
313
  arrow.setAttribute("orient", "auto-start-reverse");
245
314
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
246
- path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
247
- path.setAttribute("fill", color);
315
+ if (kind === "response") {
316
+ path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4");
317
+ path.setAttribute("fill", "none");
318
+ path.setAttribute("stroke", color);
319
+ path.setAttribute("stroke-width", "1.4");
320
+ } else if (kind === "event") {
321
+ path.setAttribute("d", "M 5 2 A 3 3 0 1 1 5 8 A 3 3 0 1 1 5 2");
322
+ path.setAttribute("fill", color);
323
+ } else {
324
+ path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
325
+ path.setAttribute("fill", color);
326
+ }
248
327
  arrow.appendChild(path);
249
328
  defs.appendChild(arrow);
250
329
  }
@@ -254,7 +333,8 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
254
333
  ensureDefs(svg, theme, sceneId);
255
334
  const color = theme.edges[kind];
256
335
  const style = EDGE_STYLES[kind];
257
- const points = dedupePoints(routeOrthogonal(boxRect(from), boxRect(to), routeObstacles, bounds, lane));
336
+ const isSelfLoop = from.id === to.id;
337
+ const points = isSelfLoop ? dedupePoints(selfLoopPath(from)) : dedupePoints(routeOrthogonal(boxRect(from), boxRect(to), routeObstacles, bounds, lane));
258
338
  const d = toPathD(points);
259
339
  const len = polylineLength(points);
260
340
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
@@ -272,7 +352,7 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
272
352
  path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
273
353
  }
274
354
  if (kind !== "dependency") {
275
- path.style.filter = `drop-shadow(0 0 3px ${color}33)`;
355
+ path.style.filter = `drop-shadow(0 0 3px ${translucentColor(color, "33")})`;
276
356
  }
277
357
  const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
278
358
  dot.setAttribute("r", "3.5");
@@ -314,7 +394,40 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
314
394
  labelEl.__plate = plate;
315
395
  }
316
396
  svg.appendChild(group);
317
- return { group, path, label: labelEl, dot, pathLen: len, points, labelRect };
397
+ return {
398
+ group,
399
+ path,
400
+ label: labelEl,
401
+ labelPlate: labelEl ? labelEl.__plate : void 0,
402
+ dot,
403
+ pathLen: len,
404
+ points,
405
+ labelRect,
406
+ kind,
407
+ color,
408
+ drawReveal: style.dash.length === 0
409
+ };
410
+ }
411
+ function setEdgeVisible(runtime, visible) {
412
+ runtime.group.style.opacity = visible ? "1" : "0";
413
+ if (runtime.label) runtime.label.style.opacity = visible ? "1" : "0";
414
+ if (runtime.labelPlate) runtime.labelPlate.style.opacity = visible ? "1" : "0";
415
+ }
416
+ function translucentColor(color, alpha = "aa") {
417
+ const value = color.trim();
418
+ if (/^#[0-9a-f]{3}$/i.test(value)) {
419
+ return `#${value.slice(1).split("").map((char) => char + char).join("")}${alpha}`;
420
+ }
421
+ if (/^#[0-9a-f]{6}$/i.test(value)) return `${value}${alpha}`;
422
+ if (/^#[0-9a-f]{8}$/i.test(value)) return value;
423
+ return `color-mix(in srgb, ${value} 67%, transparent)`;
424
+ }
425
+ function nextEdgeLane(lanes, from, to) {
426
+ const pair = [from, to].sort().join("|");
427
+ const keys = [`pair:${pair}`, `out:${from}`, `in:${to}`];
428
+ const lane = Math.max(...keys.map((key) => lanes.get(key) ?? 0));
429
+ for (const key of keys) lanes.set(key, (lanes.get(key) ?? 0) + 1);
430
+ return lane;
318
431
  }
319
432
  function dotTravelKeyframes(points) {
320
433
  const total = polylineLength(points);
@@ -337,21 +450,24 @@ function dotTravelKeyframes(points) {
337
450
  function animateEdgeReveal(runtime, startMs, durMs) {
338
451
  const anims = [];
339
452
  const { path, dot, label, group, pathLen, points } = runtime;
340
- path.style.strokeDasharray = String(pathLen);
341
- path.style.strokeDashoffset = String(pathLen);
453
+ if (runtime.drawReveal) {
454
+ path.style.strokeDasharray = String(pathLen);
455
+ path.style.strokeDashoffset = String(pathLen);
456
+ }
342
457
  const drawMs = Math.min(220, durMs * 0.5);
343
- anims.push(
344
- group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 120, delay: startMs, fill: "forwards" }),
345
- path.animate(
346
- [{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
347
- { duration: drawMs, delay: startMs, fill: "forwards", easing: "ease-out" }
348
- )
349
- );
458
+ anims.push(group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 120, delay: startMs, fill: "forwards" }));
459
+ if (runtime.drawReveal) {
460
+ anims.push(
461
+ path.animate(
462
+ [{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
463
+ { duration: drawMs, delay: startMs, fill: "forwards", easing: "ease-out" }
464
+ )
465
+ );
466
+ }
350
467
  if (label) {
351
468
  anims.push(label.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
352
- const plate = label.__plate;
353
- if (plate) {
354
- anims.push(plate.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
469
+ if (runtime.labelPlate) {
470
+ anims.push(runtime.labelPlate.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
355
471
  }
356
472
  }
357
473
  if (points.length >= 2) {
@@ -366,6 +482,16 @@ function animateEdgeReveal(runtime, startMs, durMs) {
366
482
  }
367
483
  return anims;
368
484
  }
485
+ function animateEdgeEmphasis(runtime, startMs, durMs, strength, color) {
486
+ const baseFilter = runtime.kind === "dependency" ? "none" : `drop-shadow(0 0 3px ${translucentColor(runtime.color, "33")})`;
487
+ const glowColor = color ?? runtime.color;
488
+ const radius = Math.max(4, Math.min(16, 5 + strength * 4));
489
+ const peakFilter = `drop-shadow(0 0 ${radius}px ${translucentColor(glowColor)}) brightness(${1 + Math.min(strength, 2) * 0.08})`;
490
+ return runtime.path.animate(
491
+ [{ filter: baseFilter }, { filter: peakFilter }, { filter: baseFilter }],
492
+ { duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
493
+ );
494
+ }
369
495
  function computeFrameTransform(targetIds, nodes, bounds, requestedZoom = DEFAULT_FRAME_ZOOM) {
370
496
  const targets = new Set(targetIds);
371
497
  const selected = nodes.filter((node) => targets.has(node.id));
@@ -387,7 +513,7 @@ function computeFrameTransform(targetIds, nodes, bounds, requestedZoom = DEFAULT
387
513
  const ty = Math.round((bounds.height / 2 - centerY * scale) * 10) / 10;
388
514
  return `translate(${tx}px, ${ty}px) scale(${Math.round(scale * 1e3) / 1e3})`;
389
515
  }
390
- function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds) {
516
+ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds, edges = [], edgeRuntimes = /* @__PURE__ */ new Map(), sceneId = createEdgeSceneId(), diagramType = "architecture") {
391
517
  const anims = [];
392
518
  const nodeById = new Map(nodes.map((n) => [n.id, n]));
393
519
  const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
@@ -395,10 +521,37 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
395
521
  const placedLabels = [];
396
522
  const laneByPair = /* @__PURE__ */ new Map();
397
523
  const svg = ensureEdgeLayer(scene);
398
- const sceneId = `md-${Math.random().toString(36).slice(2, 8)}`;
399
524
  let cameraTransform = scene.style.transform || INITIAL_CAMERA_TRANSFORM;
400
525
  scene.style.transformOrigin = "0 0";
401
526
  scene.style.transform = cameraTransform;
527
+ const edgeById = new Map(edges.map((edge) => [edge.id, edge]));
528
+ const edgeRects = [...allNodeRects];
529
+ const edgeRectById = new Map(nodes.map((node) => [node.id, boxRect(node)]));
530
+ const edgeLabels = [];
531
+ const edgeLanes = /* @__PURE__ */ new Map();
532
+ for (const edge of diagramType === "sequence" ? [] : edges) {
533
+ if (edge.structural || edgeRuntimes.has(edge.id)) continue;
534
+ const from = nodeById.get(edge.from);
535
+ const to = nodeById.get(edge.to);
536
+ if (!from || !to) continue;
537
+ const routeObstacles = [...edgeRectById.entries()].filter(([id]) => id !== from.id && id !== to.id).map(([, rect]) => rect);
538
+ const lane = nextEdgeLane(edgeLanes, edge.from, edge.to);
539
+ const runtime = createEdgeRuntime(
540
+ svg,
541
+ from,
542
+ to,
543
+ edge.kind,
544
+ edge.label,
545
+ theme,
546
+ sceneId,
547
+ routeObstacles,
548
+ [...edgeRects, ...edgeLabels],
549
+ bounds,
550
+ lane
551
+ );
552
+ if (runtime.labelRect) edgeLabels.push(runtime.labelRect);
553
+ edgeRuntimes.set(edge.id, runtime);
554
+ }
402
555
  anims.push(
403
556
  titleEl.animate(
404
557
  [{ opacity: 0, transform: "translateY(-6px)" }, { opacity: 1, transform: "translateY(0)" }],
@@ -411,37 +564,55 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
411
564
  if (cue.kind === "show") {
412
565
  cue.targets.forEach((id, idx) => {
413
566
  const el = nodeEls.get(id);
414
- if (!el) return;
415
567
  const delay = startMs + (typeof cue.params.stagger === "number" ? cue.params.stagger * 1e3 * idx : 0);
416
- anims.push(
417
- el.animate(
418
- [{ opacity: 0, transform: "translateY(8px)" }, { opacity: 1, transform: "translateY(0)" }],
419
- { duration: durMs, delay, fill: "forwards", easing: "ease-out" }
420
- )
421
- );
568
+ if (el) {
569
+ anims.push(
570
+ el.animate(
571
+ [{ opacity: 0, transform: "translateY(8px)" }, { opacity: 1, transform: "translateY(0)" }],
572
+ { duration: durMs, delay, fill: "forwards", easing: "ease-out" }
573
+ )
574
+ );
575
+ return;
576
+ }
577
+ const runtime = edgeRuntimes.get(id);
578
+ if (runtime) anims.push(runtime.group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durMs, delay, fill: "forwards", easing: "ease-out" }));
422
579
  });
423
580
  continue;
424
581
  }
425
582
  if (cue.kind === "hide") {
426
583
  for (const id of cue.targets) {
427
584
  const el = nodeEls.get(id);
428
- if (!el) continue;
429
- anims.push(el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
585
+ if (el) {
586
+ anims.push(el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
587
+ continue;
588
+ }
589
+ const runtime = edgeRuntimes.get(id);
590
+ if (runtime) anims.push(runtime.group.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
430
591
  }
431
592
  continue;
432
593
  }
433
594
  if (cue.kind === "glow") {
595
+ const strength = typeof cue.params.strength === "number" ? cue.params.strength : 1;
596
+ const peak = 1 + 0.12 * strength;
434
597
  for (const id of cue.targets) {
435
598
  const el = nodeEls.get(id);
436
- if (!el) continue;
437
- if (cue.params.color) el.style.setProperty("--md-glow-color", String(cue.params.color));
438
- el.dataset.glow = "1";
439
- anims.push(
440
- el.animate(
441
- [{ filter: "brightness(1)" }, { filter: "brightness(1.15)" }, { filter: "brightness(1)" }],
442
- { duration: durMs, delay: startMs, fill: "forwards" }
443
- )
444
- );
599
+ if (el) {
600
+ if (cue.params.color) el.style.setProperty("--md-glow-color", String(cue.params.color));
601
+ const glowColor = translucentColor(String(cue.params.color ?? "var(--md-accent)"));
602
+ anims.push(
603
+ el.animate(
604
+ [
605
+ { filter: "brightness(1)" },
606
+ { filter: `drop-shadow(0 0 ${Math.max(4, 4 + strength * 5)}px ${glowColor}) brightness(${peak})` },
607
+ { filter: "brightness(1)" }
608
+ ],
609
+ { duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
610
+ )
611
+ );
612
+ continue;
613
+ }
614
+ const runtime = edgeRuntimes.get(id);
615
+ if (runtime) anims.push(animateEdgeEmphasis(runtime, startMs, durMs, strength, typeof cue.params.color === "string" ? cue.params.color : void 0));
445
616
  }
446
617
  continue;
447
618
  }
@@ -449,20 +620,31 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
449
620
  const zoom = typeof cue.params.zoom === "number" && cue.params.zoom > 0 ? cue.params.zoom : 1.03;
450
621
  for (const id of cue.targets) {
451
622
  const el = nodeEls.get(id);
452
- if (!el) continue;
453
- el.dataset.focused = "1";
454
- anims.push(
455
- el.animate(
456
- [{ transform: "scale(1)" }, { transform: `scale(${zoom})` }, { transform: "scale(1)" }],
457
- { duration: durMs, delay: startMs, fill: "forwards" }
458
- )
459
- );
623
+ if (el) {
624
+ anims.push(
625
+ el.animate(
626
+ [
627
+ { transform: "scale(1)", filter: "none" },
628
+ { transform: `scale(${zoom})`, filter: "drop-shadow(0 0 7px var(--md-accent))" },
629
+ { transform: "scale(1)", filter: "none" }
630
+ ],
631
+ { duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
632
+ )
633
+ );
634
+ continue;
635
+ }
636
+ const runtime = edgeRuntimes.get(id);
637
+ if (runtime) anims.push(animateEdgeEmphasis(runtime, startMs, durMs, zoom, void 0));
460
638
  }
461
639
  continue;
462
640
  }
463
641
  if (cue.kind === "frame") {
642
+ const frameTargets = cue.targets.flatMap((id) => {
643
+ const edge = edgeById.get(id);
644
+ return edge ? [edge.from, edge.to] : [id];
645
+ });
464
646
  const nextTransform = computeFrameTransform(
465
- cue.targets,
647
+ frameTargets,
466
648
  nodes,
467
649
  bounds,
468
650
  typeof cue.params.zoom === "number" ? cue.params.zoom : DEFAULT_FRAME_ZOOM
@@ -477,7 +659,7 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
477
659
  cameraTransform = nextTransform;
478
660
  continue;
479
661
  }
480
- if (cue.kind === "flow" && cue.segments?.[0]) {
662
+ if (cue.kind === "flow" && cue.segments?.[0] && diagramType !== "sequence") {
481
663
  const seg = cue.segments[0];
482
664
  const from = nodeById.get(seg.from);
483
665
  const to = nodeById.get(seg.to);
@@ -486,26 +668,303 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
486
668
  for (const [id, rect] of rectById) {
487
669
  if (id !== seg.from && id !== seg.to) routeObstacles.push(rect);
488
670
  }
489
- const pairKey = [seg.from, seg.to].sort().join("|");
490
- const lane = laneByPair.get(pairKey) ?? 0;
491
- laneByPair.set(pairKey, lane + 1);
671
+ const lane = nextEdgeLane(laneByPair, seg.from, seg.to);
492
672
  const labelObstacles = [...allNodeRects, ...placedLabels];
493
- const runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane);
494
- if (runtime.labelRect) placedLabels.push(runtime.labelRect);
673
+ const edgeId = cue.edgeId ?? edges.find(
674
+ (edge) => !edge.structural && edge.from === seg.from && edge.to === seg.to && edge.kind === seg.op && edge.label === seg.label
675
+ )?.id;
676
+ let runtime = edgeId ? edgeRuntimes.get(edgeId) : void 0;
677
+ if (!runtime) {
678
+ runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane);
679
+ if (runtime.labelRect) placedLabels.push(runtime.labelRect);
680
+ if (edgeId) edgeRuntimes.set(edgeId, runtime);
681
+ }
495
682
  anims.push(...animateEdgeReveal(runtime, startMs, durMs));
496
683
  }
497
684
  }
498
685
  for (const a of anims) a.pause();
499
686
  return anims;
500
687
  }
688
+ function buildStructuralEdgeAnimations(edges, nodes, theme, scene, bounds, edgeRuntimes = /* @__PURE__ */ new Map(), sceneId = createEdgeSceneId(), diagramType = "architecture") {
689
+ const structural = edges.filter(
690
+ (e) => e.structural && diagramType !== "sequence" && !(diagramType === "tree" && !e.label)
691
+ );
692
+ if (structural.length === 0) return [];
693
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
694
+ const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
695
+ const allNodeRects = [...rectById.values()];
696
+ const placedLabels = [];
697
+ const laneByPair = /* @__PURE__ */ new Map();
698
+ const svg = ensureEdgeLayer(scene);
699
+ for (const edge of structural) {
700
+ const from = nodeById.get(edge.from);
701
+ const to = nodeById.get(edge.to);
702
+ if (!from || !to) continue;
703
+ const routeObstacles = [];
704
+ for (const [id, rect] of rectById) {
705
+ if (id !== edge.from && id !== edge.to) routeObstacles.push(rect);
706
+ }
707
+ const lane = nextEdgeLane(laneByPair, edge.from, edge.to);
708
+ const labelObstacles = [...allNodeRects, ...placedLabels];
709
+ const runtime = createEdgeRuntime(
710
+ svg,
711
+ from,
712
+ to,
713
+ edge.kind,
714
+ edge.label,
715
+ theme,
716
+ sceneId,
717
+ routeObstacles,
718
+ labelObstacles,
719
+ bounds,
720
+ lane
721
+ );
722
+ if (runtime.labelRect) placedLabels.push(runtime.labelRect);
723
+ setEdgeVisible(runtime, true);
724
+ edgeRuntimes.set(edge.id, runtime);
725
+ }
726
+ return [];
727
+ }
501
728
 
502
- // src/nodes.ts
503
- var STYLE_ID = "markdy-diagram-node-styles";
504
- function ensureNodeStyles(doc) {
729
+ // src/annotations.ts
730
+ var STYLE_ID = "markdy-annotation-styles";
731
+ function ensureAnnotationStyles(doc) {
505
732
  if (doc.getElementById(STYLE_ID)) return;
506
733
  const style = doc.createElement("style");
507
734
  style.id = STYLE_ID;
508
735
  style.textContent = `
736
+ .markdy-annotation-layer {
737
+ position: absolute;
738
+ inset: 0;
739
+ pointer-events: none;
740
+ z-index: 150;
741
+ }
742
+ .markdy-annotation {
743
+ position: absolute;
744
+ max-width: 220px;
745
+ font-family: var(--md-font-title, Georgia, serif);
746
+ font-size: 14px;
747
+ font-style: italic;
748
+ color: var(--md-text);
749
+ line-height: 1.35;
750
+ }
751
+ .markdy-annotation__leader {
752
+ position: absolute;
753
+ pointer-events: none;
754
+ }
755
+ `;
756
+ doc.head.appendChild(style);
757
+ }
758
+ function positionForAnnotation(position, bounds, index) {
759
+ const pad = 24;
760
+ const p = (position ?? "").toLowerCase();
761
+ if (p.includes("top") && p.includes("right")) return { x: bounds.width - pad - 200, y: pad + index * 48 };
762
+ if (p.includes("top") && p.includes("left")) return { x: pad, y: pad + index * 48 };
763
+ if (p.includes("bottom") && p.includes("right")) return { x: bounds.width - pad - 200, y: bounds.height - pad - 40 };
764
+ if (p.includes("bottom") && p.includes("left")) return { x: pad, y: bounds.height - pad - 40 };
765
+ return { x: bounds.width - pad - 200, y: pad + index * 48 };
766
+ }
767
+ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
768
+ if (annotations.length === 0) return;
769
+ const doc = layer.ownerDocument;
770
+ ensureAnnotationStyles(doc);
771
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
772
+ svg.classList.add("markdy-annotation__leader");
773
+ Object.assign(svg.style, { position: "absolute", inset: "0", width: "100%", height: "100%", overflow: "visible" });
774
+ layer.appendChild(svg);
775
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
776
+ annotations.slice(0, 2).forEach((ann, index) => {
777
+ const pos = positionForAnnotation(ann.position, bounds, index);
778
+ const textEl = doc.createElement("div");
779
+ textEl.className = "markdy-annotation";
780
+ textEl.textContent = ann.text;
781
+ textEl.style.left = `${pos.x}px`;
782
+ textEl.style.top = `${pos.y}px`;
783
+ layer.appendChild(textEl);
784
+ const target = ann.target ? nodeById.get(ann.target) : void 0;
785
+ if (!target) return;
786
+ const tx = target.x + target.width / 2;
787
+ const ty = target.y + target.height / 2;
788
+ const ax = pos.x + 8;
789
+ const ay = pos.y + 16;
790
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
791
+ path.setAttribute("d", `M ${ax} ${ay} Q ${(ax + tx) / 2} ${(ay + ty) / 2 - 20} ${tx} ${ty}`);
792
+ path.setAttribute("fill", "none");
793
+ path.setAttribute("stroke", theme.textMuted);
794
+ path.setAttribute("stroke-width", "1");
795
+ path.setAttribute("stroke-dasharray", "4 3");
796
+ path.setAttribute("opacity", "0.55");
797
+ svg.appendChild(path);
798
+ const dot = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
799
+ dot.setAttribute("cx", String(tx));
800
+ dot.setAttribute("cy", String(ty));
801
+ dot.setAttribute("r", "2");
802
+ dot.setAttribute("fill", theme.text);
803
+ svg.appendChild(dot);
804
+ });
805
+ }
806
+
807
+ // src/constellation.ts
808
+ var constellationId = 0;
809
+ function nextId() {
810
+ constellationId += 1;
811
+ return `md-constellation-${constellationId}`;
812
+ }
813
+ function appendCircle(doc, parent, cx, cy, radius, stroke, opacity, dash) {
814
+ const circle = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
815
+ circle.setAttribute("cx", String(cx));
816
+ circle.setAttribute("cy", String(cy));
817
+ circle.setAttribute("r", String(radius));
818
+ circle.setAttribute("fill", "none");
819
+ circle.setAttribute("stroke", stroke);
820
+ circle.setAttribute("stroke-width", "1");
821
+ circle.setAttribute("opacity", opacity);
822
+ if (dash) circle.setAttribute("stroke-dasharray", dash);
823
+ parent.appendChild(circle);
824
+ }
825
+ function mountConstellationLayer(layer, nodes, theme, bounds) {
826
+ if (nodes.length === 0) return;
827
+ const doc = layer.ownerDocument;
828
+ Object.assign(layer.style, {
829
+ position: "absolute",
830
+ inset: "0",
831
+ zIndex: "38",
832
+ pointerEvents: "none"
833
+ });
834
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
835
+ svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
836
+ Object.assign(svg.style, {
837
+ position: "absolute",
838
+ inset: "0",
839
+ width: "100%",
840
+ height: "100%",
841
+ overflow: "visible"
842
+ });
843
+ layer.appendChild(svg);
844
+ const focal = nodes.find((node) => node.focal) ?? nodes[0];
845
+ const centerX = focal.x + focal.width / 2;
846
+ const centerY = focal.y + focal.height / 2;
847
+ const radius = Math.max(120, Math.min(bounds.width, bounds.height) * 0.28);
848
+ const gradientId = `${nextId()}-halo`;
849
+ const defs = doc.createElementNS("http://www.w3.org/2000/svg", "defs");
850
+ const gradient = doc.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
851
+ gradient.setAttribute("id", gradientId);
852
+ const stopA = doc.createElementNS("http://www.w3.org/2000/svg", "stop");
853
+ stopA.setAttribute("offset", "0%");
854
+ stopA.setAttribute("stop-color", theme.accent);
855
+ stopA.setAttribute("stop-opacity", "0.24");
856
+ const stopB = doc.createElementNS("http://www.w3.org/2000/svg", "stop");
857
+ stopB.setAttribute("offset", "100%");
858
+ stopB.setAttribute("stop-color", theme.accent);
859
+ stopB.setAttribute("stop-opacity", "0");
860
+ gradient.append(stopA, stopB);
861
+ defs.appendChild(gradient);
862
+ svg.appendChild(defs);
863
+ const halo = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
864
+ halo.setAttribute("cx", String(centerX));
865
+ halo.setAttribute("cy", String(centerY));
866
+ halo.setAttribute("r", String(radius * 0.7));
867
+ halo.setAttribute("fill", `url(#${gradientId})`);
868
+ svg.appendChild(halo);
869
+ appendCircle(doc, svg, centerX, centerY, radius * 0.55, theme.rule ?? theme.border, "0.55", "2 8");
870
+ appendCircle(doc, svg, centerX, centerY, radius * 0.82, theme.rule ?? theme.border, "0.38", "1 11");
871
+ appendCircle(doc, svg, centerX, centerY, radius, theme.soft ?? theme.border, "0.22", "1 15");
872
+ for (const node of nodes) {
873
+ if (node.id === focal.id) continue;
874
+ const nodeX = node.x + node.width / 2;
875
+ const nodeY = node.y + node.height / 2;
876
+ const link = doc.createElementNS("http://www.w3.org/2000/svg", "line");
877
+ link.classList.add("markdy-constellation-link");
878
+ link.setAttribute("x1", String(centerX));
879
+ link.setAttribute("y1", String(centerY));
880
+ link.setAttribute("x2", String(nodeX));
881
+ link.setAttribute("y2", String(nodeY));
882
+ link.setAttribute("stroke", theme.soft ?? theme.accent);
883
+ link.setAttribute("stroke-width", "1");
884
+ link.setAttribute("stroke-dasharray", "2 9");
885
+ link.setAttribute("opacity", "0.22");
886
+ svg.appendChild(link);
887
+ }
888
+ for (let index = 0; index < 28; index += 1) {
889
+ const angle = index * 2.399963;
890
+ const distance = 70 + index * 47 % 190;
891
+ const star = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
892
+ star.classList.add("markdy-constellation-star");
893
+ star.setAttribute("cx", String(centerX + Math.cos(angle) * distance));
894
+ star.setAttribute("cy", String(centerY + Math.sin(angle) * distance * 0.72));
895
+ star.setAttribute("r", String(1 + index % 3 * 0.45));
896
+ star.setAttribute("fill", index % 4 === 0 ? theme.soft ?? theme.accent : theme.textMuted);
897
+ star.setAttribute("opacity", String(0.28 + index % 5 * 0.1));
898
+ star.style.animationDelay = `${-(index % 7) * 0.45}s`;
899
+ svg.appendChild(star);
900
+ }
901
+ }
902
+
903
+ // src/groups.ts
904
+ var STYLE_ID2 = "markdy-group-boundary-styles";
905
+ function ensureGroupStyles(doc) {
906
+ if (doc.getElementById(STYLE_ID2)) return;
907
+ const style = doc.createElement("style");
908
+ style.id = STYLE_ID2;
909
+ style.textContent = `
910
+ .markdy-group-boundary {
911
+ position: absolute;
912
+ box-sizing: border-box;
913
+ border: 1px dashed var(--md-group-border, color-mix(in srgb, var(--md-border) 70%, transparent));
914
+ border-radius: var(--md-radius-md, 8px);
915
+ background: color-mix(in srgb, var(--md-surface-raised) 40%, transparent);
916
+ pointer-events: none;
917
+ z-index: 40;
918
+ }
919
+ .markdy-group-boundary__label {
920
+ position: absolute;
921
+ left: 12px;
922
+ top: -10px;
923
+ padding: 2px 8px;
924
+ font-size: 10px;
925
+ font-weight: 600;
926
+ letter-spacing: 0.08em;
927
+ text-transform: uppercase;
928
+ color: var(--md-text-muted);
929
+ background: var(--md-canvas);
930
+ border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border) 60%, transparent));
931
+ border-radius: 4px;
932
+ font-family: var(--md-font-mono, ui-monospace, monospace);
933
+ }
934
+ `;
935
+ doc.head.appendChild(style);
936
+ }
937
+ function createGroupBoundaryEl(boundary, theme, doc = document) {
938
+ const el = doc.createElement("div");
939
+ el.className = "markdy-group-boundary";
940
+ el.dataset.group = boundary.id;
941
+ el.style.left = `${boundary.x}px`;
942
+ el.style.top = `${boundary.y}px`;
943
+ el.style.width = `${boundary.width}px`;
944
+ el.style.height = `${boundary.height}px`;
945
+ el.style.setProperty("--md-group-border", theme.hairline ?? theme.border);
946
+ if (boundary.label) {
947
+ const label = document.createElement("div");
948
+ label.className = "markdy-group-boundary__label";
949
+ label.textContent = boundary.label;
950
+ el.appendChild(label);
951
+ }
952
+ return el;
953
+ }
954
+ function mountGroupBoundaries(layer, boundaries, theme) {
955
+ ensureGroupStyles(layer.ownerDocument);
956
+ for (const boundary of boundaries) {
957
+ layer.appendChild(createGroupBoundaryEl(boundary, theme, layer.ownerDocument));
958
+ }
959
+ }
960
+
961
+ // src/nodes.ts
962
+ var STYLE_ID3 = "markdy-diagram-node-styles";
963
+ function ensureNodeStyles(doc) {
964
+ if (doc.getElementById(STYLE_ID3)) return;
965
+ const style = doc.createElement("style");
966
+ style.id = STYLE_ID3;
967
+ style.textContent = `
509
968
  .markdy-node {
510
969
  position: absolute;
511
970
  box-sizing: border-box;
@@ -522,7 +981,7 @@ function ensureNodeStyles(doc) {
522
981
  0 10px 22px -12px var(--md-shadow, rgba(2, 6, 23, 0.55)),
523
982
  inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent)),
524
983
  inset 0 1px 0 rgba(255, 255, 255, 0.05);
525
- font-family: Inter, ui-sans-serif, system-ui, sans-serif;
984
+ font-family: var(--md-font-node, Inter, ui-sans-serif, system-ui, sans-serif);
526
985
  overflow: hidden;
527
986
  opacity: 0;
528
987
  transform: translateY(8px);
@@ -616,6 +1075,13 @@ function ensureNodeStyles(doc) {
616
1075
  word-break: break-word;
617
1076
  text-wrap: balance;
618
1077
  }
1078
+ .markdy-node__value {
1079
+ flex: 0 0 auto;
1080
+ font-size: 18px;
1081
+ font-weight: 700;
1082
+ color: var(--md-ink, var(--md-text));
1083
+ font-variant-numeric: tabular-nums;
1084
+ }
619
1085
  .markdy-node[data-role="client"] { border-radius: 15px 15px 9px 9px; }
620
1086
  .markdy-node[data-role="data"] { border-radius: 12px 12px 20px 20px; }
621
1087
  .markdy-scene-title {
@@ -630,12 +1096,71 @@ function ensureNodeStyles(doc) {
630
1096
  color: var(--md-text);
631
1097
  opacity: 0;
632
1098
  transform: translateY(-6px);
633
- font-family: Inter, ui-sans-serif, system-ui, sans-serif;
1099
+ font-family: var(--md-font-title, Inter, ui-sans-serif, system-ui, sans-serif);
634
1100
  }
635
1101
  .markdy-scene-title[data-visible="1"] {
636
1102
  opacity: 1;
637
1103
  transform: translateY(0);
638
1104
  }
1105
+ .markdy-node[data-shape="diamond"] {
1106
+ border-radius: 4px;
1107
+ transform: rotate(0deg);
1108
+ clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
1109
+ }
1110
+ .markdy-node[data-shape="pill"] {
1111
+ border-radius: 999px;
1112
+ min-height: 56px;
1113
+ }
1114
+ .markdy-node[data-shape="circle"],
1115
+ .markdy-node[data-kind="dot"] {
1116
+ width: 64px;
1117
+ height: 64px;
1118
+ border-radius: 50%;
1119
+ }
1120
+ .markdy-node[data-kind="matrix"] {
1121
+ background-image:
1122
+ linear-gradient(var(--md-hairline) 1px, transparent 1px),
1123
+ linear-gradient(90deg, var(--md-hairline) 1px, transparent 1px);
1124
+ background-size: 12px 12px;
1125
+ }
1126
+ .markdy-node[data-kind="track"] {
1127
+ border-left: 4px solid var(--md-role-color, var(--md-accent));
1128
+ border-radius: var(--md-radius-md, 8px);
1129
+ }
1130
+ .markdy-node[data-kind="token_strip"] {
1131
+ border-radius: 999px;
1132
+ }
1133
+ .markdy-node[data-shape="rounded"] {
1134
+ border-radius: 16px;
1135
+ }
1136
+ .markdy-node[data-shape="terminal"] {
1137
+ border-radius: 6px;
1138
+ font-family: var(--md-font-mono, ui-monospace, monospace);
1139
+ box-shadow: none;
1140
+ background: var(--md-node-surface, var(--md-surface));
1141
+ }
1142
+ .markdy-node[data-focal="1"] {
1143
+ background: color-mix(in srgb, var(--md-accent-tint, var(--md-accent)) 100%, transparent);
1144
+ box-shadow:
1145
+ inset 0 0 0 1px color-mix(in srgb, var(--md-accent) 55%, transparent);
1146
+ }
1147
+ .markdy-scene-root[data-markdy-theme="nebula"] .markdy-node {
1148
+ border: 1px solid color-mix(in srgb, var(--md-role-color, var(--md-accent)) 34%, transparent);
1149
+ box-shadow:
1150
+ 0 0 24px -14px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 80%, transparent),
1151
+ inset 0 0 0 1px var(--md-hairline);
1152
+ }
1153
+ .markdy-scene-root[data-markdy-theme="nebula"] .markdy-node[data-focal="1"] {
1154
+ box-shadow:
1155
+ 0 0 34px -8px color-mix(in srgb, var(--md-accent) 78%, transparent),
1156
+ inset 0 0 0 1px var(--md-accent);
1157
+ }
1158
+ .markdy-scene-root[data-flat="1"] .markdy-node {
1159
+ box-shadow: inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent));
1160
+ }
1161
+ .markdy-scene-root[data-flat="1"] .markdy-node[data-visible="1"] {
1162
+ box-shadow: inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent));
1163
+ }
639
1164
  `;
640
1165
  doc.head.appendChild(style);
641
1166
  }
@@ -644,6 +1169,14 @@ var ICONS = {
644
1169
  ["rect", { x: "5", y: "5", width: "14", height: "14", rx: "3" }],
645
1170
  ["path", { d: "M9 9h6v6H9zM9 2.5v2.5M15 2.5v2.5M9 19v2.5M15 19v2.5M2.5 9h2.5M2.5 15h2.5M19 9h2.5M19 15h2.5" }]
646
1171
  ],
1172
+ laptop: [
1173
+ ["path", { d: "M3 19l18 0" }],
1174
+ ["path", { d: "M5 7a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1l0 -8" }]
1175
+ ],
1176
+ phone: [
1177
+ ["path", { d: "M6 5a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-14" }],
1178
+ ["path", { d: "M10.5 18h3" }]
1179
+ ],
647
1180
  user: [
648
1181
  ["circle", { cx: "12", cy: "8", r: "3.4" }],
649
1182
  ["path", { d: "M5.5 20a6.5 6.5 0 0 1 13 0" }]
@@ -783,6 +1316,7 @@ var ICONS = {
783
1316
  ["path", { d: "M12 16h.01" }]
784
1317
  ]
785
1318
  };
1319
+ var ICON_REGISTRY = Object.freeze(ICONS);
786
1320
  function iconKeyForNode(node) {
787
1321
  const override = typeof node.props?.icon === "string" ? node.props.icon.toLowerCase() : void 0;
788
1322
  if (override && ICONS[override]) return override;
@@ -802,6 +1336,7 @@ function iconKeyForNode(node) {
802
1336
  if (node.kind === "monitor" || node.kind === "metrics" || node.kind === "dashboard" || node.kind === "slo" || node.kind === "probe") return "metrics";
803
1337
  if (node.kind === "registry" || node.kind === "artifact") return "registry";
804
1338
  if (node.kind === "mobile") return "mobile";
1339
+ if (node.kind === "laptop" || node.kind === "desktop") return node.kind;
805
1340
  if (node.kind === "api" || node.kind === "service" || node.kind === "microservice" || node.kind === "backend" || node.kind === "server" || node.kind === "handler" || node.kind === "controller") return "server";
806
1341
  if (node.kind === "browser" || node.kind === "web" || node.kind === "frontend" || node.kind === "app") return "browser";
807
1342
  if (node.kind === "user" || node.kind === "client") return "user";
@@ -878,12 +1413,18 @@ function createNodeEl(node, theme, assets) {
878
1413
  el.style.top = `${node.y}px`;
879
1414
  el.style.setProperty("--md-node-w", `${node.width}px`);
880
1415
  el.style.setProperty("--md-node-h", `${node.height}px`);
1416
+ if (theme.flatCards) {
1417
+ el.style.setProperty("--md-shadow", "transparent");
1418
+ }
1419
+ if (theme.accentTint) el.style.setProperty("--md-accent-tint", theme.accentTint);
881
1420
  const roleColor = theme.roles[node.role] ?? theme.accent;
882
1421
  el.style.setProperty("--md-role-color", roleColor);
883
1422
  applyDeclaredNodeStyle(el, node.style);
884
1423
  const typeText = node.kind.replace(/_/g, " ");
885
1424
  el.dataset.kind = node.kind;
886
1425
  el.dataset.icon = iconKeyForNode(node);
1426
+ if (node.shape) el.dataset.shape = node.shape;
1427
+ if (node.focal) el.dataset.focal = "1";
887
1428
  el.title = `${node.label} (${typeText})`;
888
1429
  el.setAttribute("aria-label", el.title);
889
1430
  const body = document.createElement("div");
@@ -893,6 +1434,13 @@ function createNodeEl(node, theme, assets) {
893
1434
  label.className = "markdy-node__label";
894
1435
  label.textContent = node.label;
895
1436
  body.append(icon, label);
1437
+ const value = node.props?.value ?? node.props?.metric;
1438
+ if (value !== void 0 && value !== null) {
1439
+ const valueEl = document.createElement("div");
1440
+ valueEl.className = "markdy-node__value";
1441
+ valueEl.textContent = String(value);
1442
+ body.appendChild(valueEl);
1443
+ }
896
1444
  el.append(body);
897
1445
  return el;
898
1446
  }
@@ -903,6 +1451,225 @@ function createTitleEl(title) {
903
1451
  return el;
904
1452
  }
905
1453
 
1454
+ // src/sequence.ts
1455
+ var sequenceLayerCounter = 0;
1456
+ function markerId(prefix) {
1457
+ sequenceLayerCounter += 1;
1458
+ return `md-sequence-${prefix}-${sequenceLayerCounter}`;
1459
+ }
1460
+ function appendMarker(doc, defs, id, kind, color) {
1461
+ const marker = doc.createElementNS("http://www.w3.org/2000/svg", "marker");
1462
+ marker.setAttribute("id", id);
1463
+ marker.setAttribute("viewBox", "0 0 10 10");
1464
+ marker.setAttribute("refX", "8.5");
1465
+ marker.setAttribute("refY", "5");
1466
+ marker.setAttribute("markerWidth", "7");
1467
+ marker.setAttribute("markerHeight", "7");
1468
+ marker.setAttribute("orient", "auto");
1469
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1470
+ if (kind === "response") {
1471
+ path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4");
1472
+ path.setAttribute("fill", "none");
1473
+ path.setAttribute("stroke", color);
1474
+ path.setAttribute("stroke-width", "1.4");
1475
+ } else if (kind === "event") {
1476
+ path.setAttribute("d", "M 5 2 A 3 3 0 1 1 5 8 A 3 3 0 1 1 5 2");
1477
+ path.setAttribute("fill", color);
1478
+ } else {
1479
+ path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
1480
+ path.setAttribute("fill", color);
1481
+ }
1482
+ marker.appendChild(path);
1483
+ defs.appendChild(marker);
1484
+ }
1485
+ function createText(doc, x, y, text, theme) {
1486
+ const label = doc.createElementNS("http://www.w3.org/2000/svg", "text");
1487
+ label.setAttribute("x", String(x));
1488
+ label.setAttribute("y", String(y));
1489
+ label.setAttribute("text-anchor", "middle");
1490
+ label.setAttribute("dominant-baseline", "middle");
1491
+ label.setAttribute("font-size", "11");
1492
+ label.setAttribute("font-family", theme.fonts?.mono ?? "ui-monospace, SFMono-Regular, Menlo, monospace");
1493
+ label.setAttribute("fill", theme.text);
1494
+ label.textContent = text;
1495
+ return label;
1496
+ }
1497
+ function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds) {
1498
+ Object.assign(layer.style, {
1499
+ position: "absolute",
1500
+ inset: "0",
1501
+ zIndex: "52",
1502
+ pointerEvents: "none"
1503
+ });
1504
+ const doc = layer.ownerDocument;
1505
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1506
+ svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
1507
+ Object.assign(svg.style, {
1508
+ position: "absolute",
1509
+ inset: "0",
1510
+ width: "100%",
1511
+ height: "100%",
1512
+ overflow: "visible"
1513
+ });
1514
+ layer.appendChild(svg);
1515
+ const defs = doc.createElementNS("http://www.w3.org/2000/svg", "defs");
1516
+ const markers = /* @__PURE__ */ new Map();
1517
+ for (const kind of ["request", "response", "event"]) {
1518
+ const id = markerId(kind);
1519
+ markers.set(kind, id);
1520
+ appendMarker(doc, defs, id, kind, theme.edges[kind]);
1521
+ }
1522
+ svg.appendChild(defs);
1523
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
1524
+ const centerX = (id) => {
1525
+ const node = nodeById.get(id);
1526
+ return node ? node.x + node.width / 2 : 0;
1527
+ };
1528
+ for (const node of nodes) {
1529
+ const x = centerX(node.id);
1530
+ const lifeline = doc.createElementNS("http://www.w3.org/2000/svg", "line");
1531
+ lifeline.classList.add("markdy-sequence-lifeline");
1532
+ lifeline.setAttribute("x1", String(x));
1533
+ lifeline.setAttribute("x2", String(x));
1534
+ lifeline.setAttribute("y1", String(node.y + node.height + 12));
1535
+ lifeline.setAttribute("y2", String(bounds.height - 28));
1536
+ lifeline.setAttribute("stroke", theme.rule ?? theme.soft ?? theme.border);
1537
+ lifeline.setAttribute("stroke-width", "1");
1538
+ lifeline.setAttribute("stroke-dasharray", "4 5");
1539
+ lifeline.setAttribute("opacity", "0.75");
1540
+ svg.appendChild(lifeline);
1541
+ }
1542
+ for (const activation of activations) {
1543
+ const x = centerX(activation.participant);
1544
+ const bar = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
1545
+ bar.classList.add("markdy-sequence-activation");
1546
+ bar.setAttribute("x", String(x - 5));
1547
+ bar.setAttribute("y", String(activation.y));
1548
+ bar.setAttribute("width", "10");
1549
+ bar.setAttribute("height", String(activation.height));
1550
+ bar.setAttribute("rx", "3");
1551
+ bar.setAttribute("fill", theme.accent);
1552
+ bar.setAttribute("opacity", "0");
1553
+ svg.appendChild(bar);
1554
+ }
1555
+ const animations = [];
1556
+ for (const message of messages) {
1557
+ const fromX = centerX(message.from);
1558
+ const toX = centerX(message.to);
1559
+ const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
1560
+ group.classList.add("markdy-sequence-message");
1561
+ group.setAttribute("data-message", message.id);
1562
+ group.style.opacity = "0";
1563
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
1564
+ line.setAttribute("x1", String(fromX));
1565
+ line.setAttribute("x2", String(toX));
1566
+ line.setAttribute("y1", String(message.y));
1567
+ line.setAttribute("y2", String(message.y));
1568
+ line.setAttribute("stroke", theme.edges[message.kind]);
1569
+ line.setAttribute("stroke-width", "2");
1570
+ line.setAttribute("stroke-linecap", "round");
1571
+ if (message.kind === "response") line.setAttribute("stroke-dasharray", "6 4");
1572
+ if (message.kind === "event") line.setAttribute("stroke-dasharray", "2 6");
1573
+ if (message.kind !== "dependency") {
1574
+ const marker = markers.get(message.kind);
1575
+ if (marker) line.setAttribute("marker-end", `url(#${marker})`);
1576
+ }
1577
+ group.appendChild(line);
1578
+ if (message.label) {
1579
+ const midX = (fromX + toX) / 2;
1580
+ const plate = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
1581
+ const width = message.label.length * 6.6 + 16;
1582
+ plate.setAttribute("x", String(midX - width / 2));
1583
+ plate.setAttribute("y", String(message.y - 24));
1584
+ plate.setAttribute("width", String(width));
1585
+ plate.setAttribute("height", "18");
1586
+ plate.setAttribute("rx", "5");
1587
+ plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
1588
+ plate.setAttribute("stroke", theme.hairline ?? theme.border);
1589
+ plate.setAttribute("stroke-width", "1");
1590
+ group.appendChild(plate);
1591
+ group.appendChild(createText(doc, midX, message.y - 15, message.label, theme));
1592
+ }
1593
+ svg.appendChild(group);
1594
+ animations.push(
1595
+ group.animate(
1596
+ [{ opacity: 0 }, { opacity: 1 }],
1597
+ {
1598
+ duration: Math.max(160, message.duration * 1e3),
1599
+ delay: message.start * 1e3,
1600
+ fill: "forwards",
1601
+ easing: "ease-out"
1602
+ }
1603
+ )
1604
+ );
1605
+ }
1606
+ const activationEls = Array.from(svg.querySelectorAll(".markdy-sequence-activation"));
1607
+ activationEls.forEach((bar, index) => {
1608
+ const activation = activations[index];
1609
+ if (!activation) return;
1610
+ animations.push(
1611
+ bar.animate(
1612
+ [{ opacity: 0 }, { opacity: 0.9, offset: 0.2 }, { opacity: 0 }],
1613
+ {
1614
+ duration: Math.max(120, activation.duration * 1e3),
1615
+ delay: activation.start * 1e3,
1616
+ fill: "none",
1617
+ easing: "ease-out"
1618
+ }
1619
+ )
1620
+ );
1621
+ });
1622
+ for (const animation of animations) animation.pause();
1623
+ return animations;
1624
+ }
1625
+
1626
+ // src/tree.ts
1627
+ function mountTreeBuses(layer, buses, theme) {
1628
+ if (buses.length === 0) return;
1629
+ Object.assign(layer.style, {
1630
+ position: "absolute",
1631
+ inset: "0",
1632
+ zIndex: "42",
1633
+ pointerEvents: "none"
1634
+ });
1635
+ const doc = layer.ownerDocument;
1636
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1637
+ Object.assign(svg.style, {
1638
+ position: "absolute",
1639
+ inset: "0",
1640
+ width: "100%",
1641
+ height: "100%",
1642
+ overflow: "visible"
1643
+ });
1644
+ layer.appendChild(svg);
1645
+ const stroke = theme.rule ?? theme.hairline ?? theme.border;
1646
+ for (const bus of buses) {
1647
+ const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
1648
+ group.setAttribute("data-tree-bus", bus.id);
1649
+ const parentLeg = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1650
+ parentLeg.setAttribute("d", `M ${bus.parentX} ${bus.parentY} L ${bus.parentX} ${bus.branchY}`);
1651
+ const childXs = [...bus.childXs].sort((a, b) => a - b);
1652
+ const branch = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1653
+ const branchStart = childXs[0] ?? bus.parentX;
1654
+ const branchEnd = childXs[childXs.length - 1] ?? bus.parentX;
1655
+ branch.setAttribute("d", `M ${branchStart} ${bus.branchY} L ${branchEnd} ${bus.branchY}`);
1656
+ group.append(parentLeg, branch);
1657
+ for (const childX of bus.childXs) {
1658
+ const leg = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1659
+ leg.setAttribute("d", `M ${childX} ${bus.branchY} L ${childX} ${bus.childY}`);
1660
+ group.appendChild(leg);
1661
+ }
1662
+ for (const path of Array.from(group.querySelectorAll("path"))) {
1663
+ path.setAttribute("fill", "none");
1664
+ path.setAttribute("stroke", stroke);
1665
+ path.setAttribute("stroke-width", "1.5");
1666
+ path.setAttribute("stroke-linecap", "round");
1667
+ path.setAttribute("stroke-linejoin", "round");
1668
+ }
1669
+ svg.appendChild(group);
1670
+ }
1671
+ }
1672
+
906
1673
  // src/theme.ts
907
1674
  var SCENE_STYLE_ID = "markdy-scene-ambience-styles";
908
1675
  function ensureSceneStyles(doc) {
@@ -938,6 +1705,30 @@ function ensureSceneStyles(doc) {
938
1705
  opacity: 0.62;
939
1706
  }
940
1707
  .markdy-scene-content { z-index: 2; }
1708
+ .markdy-scene-root[data-markdy-theme="nebula"]::before {
1709
+ background:
1710
+ radial-gradient(circle at 18% 18%, color-mix(in srgb, var(--md-soft) 16%, transparent), transparent 32%),
1711
+ radial-gradient(circle at 84% 72%, color-mix(in srgb, var(--md-accent) 18%, transparent), transparent 36%),
1712
+ linear-gradient(var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
1713
+ linear-gradient(90deg, var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px;
1714
+ mask-image: none;
1715
+ opacity: 0.9;
1716
+ }
1717
+ .markdy-scene-root[data-markdy-theme="nebula"]::after {
1718
+ background:
1719
+ radial-gradient(ellipse at 50% 42%, color-mix(in srgb, var(--md-accent) 12%, transparent), transparent 56%),
1720
+ linear-gradient(180deg, transparent 0%, var(--md-vignette) 100%);
1721
+ opacity: 0.8;
1722
+ }
1723
+ @keyframes markdy-star-twinkle {
1724
+ from { opacity: 0.24; transform: scale(0.85); }
1725
+ to { opacity: 0.9; transform: scale(1.15); }
1726
+ }
1727
+ .markdy-constellation-star {
1728
+ transform-box: fill-box;
1729
+ transform-origin: center;
1730
+ animation: markdy-star-twinkle 5s ease-in-out infinite alternate;
1731
+ }
941
1732
  .markdy-camera-layer {
942
1733
  position: absolute;
943
1734
  inset: 0;
@@ -990,15 +1781,32 @@ function applyThemeToScene(scene, theme) {
990
1781
  scene.style.setProperty("--md-border", theme.border);
991
1782
  scene.style.setProperty("--md-text", theme.text);
992
1783
  scene.style.setProperty("--md-text-muted", theme.textMuted);
1784
+ scene.style.setProperty("--md-paper", theme.paper ?? theme.canvas);
1785
+ scene.style.setProperty("--md-ink", theme.ink ?? theme.text);
1786
+ scene.style.setProperty("--md-muted", theme.muted ?? theme.textMuted);
1787
+ scene.style.setProperty("--md-rule", theme.rule ?? theme.border);
993
1788
  scene.style.setProperty("--md-grid-minor", theme.gridMinor);
994
1789
  scene.style.setProperty("--md-grid-major", theme.gridMajor);
995
1790
  scene.style.setProperty("--md-vignette", theme.vignette);
996
1791
  scene.style.setProperty("--md-accent", theme.accent);
1792
+ if (theme.link) scene.style.setProperty("--md-link", theme.link);
1793
+ if (theme.soft) scene.style.setProperty("--md-soft", theme.soft);
1794
+ if (theme.accentTint) scene.style.setProperty("--md-accent-tint", theme.accentTint);
997
1795
  scene.style.setProperty("--md-node-surface", theme.nodeSurface ?? theme.surface);
998
1796
  scene.style.setProperty("--md-node-surface-raised", theme.nodeSurfaceRaised ?? theme.surfaceRaised);
999
1797
  scene.style.setProperty("--md-hairline", theme.hairline ?? `color-mix(in srgb, ${theme.border} 50%, transparent)`);
1000
1798
  scene.style.setProperty("--md-shadow", theme.shadow ?? "rgba(2, 6, 23, 0.55)");
1799
+ if (theme.fonts?.title) scene.style.setProperty("--md-font-title", theme.fonts.title);
1800
+ if (theme.fonts?.nodeName) scene.style.setProperty("--md-font-node", theme.fonts.nodeName);
1801
+ if (theme.fonts?.mono) scene.style.setProperty("--md-font-mono", theme.fonts.mono);
1802
+ if (theme.radiusMd) scene.style.setProperty("--md-radius-md", `${theme.radiusMd}px`);
1803
+ if (theme.spacing) {
1804
+ for (const [key, value] of Object.entries(theme.spacing)) {
1805
+ scene.style.setProperty(`--md-space-${key}`, `${value}px`);
1806
+ }
1807
+ }
1001
1808
  scene.dataset.markdyTheme = theme.name;
1809
+ if (theme.flatCards) scene.dataset.flat = "1";
1002
1810
  }
1003
1811
 
1004
1812
  // src/diagram.ts
@@ -1138,9 +1946,50 @@ function createDiagram(opts) {
1138
1946
  const cameraLayer = document.createElement("div");
1139
1947
  cameraLayer.className = "markdy-camera-layer";
1140
1948
  sceneContent.appendChild(cameraLayer);
1949
+ const structuralEdgeHost = document.createElement("div");
1950
+ structuralEdgeHost.className = "markdy-structural-edge-host";
1951
+ Object.assign(structuralEdgeHost.style, {
1952
+ position: "absolute",
1953
+ inset: "0",
1954
+ zIndex: "45",
1955
+ pointerEvents: "none"
1956
+ });
1957
+ cameraLayer.appendChild(structuralEdgeHost);
1958
+ const treeLayer = document.createElement("div");
1959
+ treeLayer.className = "markdy-tree-layer";
1960
+ cameraLayer.appendChild(treeLayer);
1961
+ mountTreeBuses(treeLayer, plan.treeBuses, plan.theme);
1962
+ const constellationLayer = document.createElement("div");
1963
+ constellationLayer.className = "markdy-constellation-layer";
1964
+ cameraLayer.appendChild(constellationLayer);
1965
+ if (plan.diagramType === "constellation") {
1966
+ mountConstellationLayer(
1967
+ constellationLayer,
1968
+ plan.nodes,
1969
+ plan.theme,
1970
+ { width: plan.meta.width, height: plan.meta.height }
1971
+ );
1972
+ }
1973
+ const groupLayer = document.createElement("div");
1974
+ groupLayer.className = "markdy-group-layer";
1975
+ Object.assign(groupLayer.style, {
1976
+ position: "absolute",
1977
+ inset: "0",
1978
+ zIndex: "48",
1979
+ pointerEvents: "none"
1980
+ });
1981
+ cameraLayer.appendChild(groupLayer);
1982
+ mountGroupBoundaries(groupLayer, plan.groupBoundaries, plan.theme);
1983
+ const sequenceLayer = document.createElement("div");
1984
+ sequenceLayer.className = "markdy-sequence-layer";
1985
+ cameraLayer.appendChild(sequenceLayer);
1141
1986
  const nodeLayer = document.createElement("div");
1142
1987
  nodeLayer.className = "markdy-scene-node-layer";
1988
+ Object.assign(nodeLayer.style, { position: "absolute", inset: "0", zIndex: "60" });
1143
1989
  cameraLayer.appendChild(nodeLayer);
1990
+ const annotationLayer = document.createElement("div");
1991
+ annotationLayer.className = "markdy-annotation-layer";
1992
+ cameraLayer.appendChild(annotationLayer);
1144
1993
  const captionLayer = createBeatCaptionLayer(document, plan.beats);
1145
1994
  sceneContent.appendChild(captionLayer);
1146
1995
  const nodeEls = /* @__PURE__ */ new Map();
@@ -1156,13 +2005,38 @@ function createDiagram(opts) {
1156
2005
  scaleScene();
1157
2006
  const resizeObserver = new ResizeObserver(scaleScene);
1158
2007
  resizeObserver.observe(viewport);
2008
+ const edgeRuntimes = /* @__PURE__ */ new Map();
2009
+ const edgeSceneId = createEdgeSceneId();
2010
+ const sequenceAnims = plan.diagramType === "sequence" ? mountSequenceLayer(
2011
+ sequenceLayer,
2012
+ plan.nodes,
2013
+ plan.sequenceMessages,
2014
+ plan.sequenceActivations,
2015
+ plan.theme,
2016
+ { width: plan.meta.width, height: plan.meta.height }
2017
+ ) : [];
1159
2018
  const allAnims = [
2019
+ ...sequenceAnims,
2020
+ ...buildStructuralEdgeAnimations(
2021
+ plan.edges,
2022
+ plan.nodes,
2023
+ plan.theme,
2024
+ structuralEdgeHost,
2025
+ { width: plan.meta.width, height: plan.meta.height },
2026
+ edgeRuntimes,
2027
+ edgeSceneId,
2028
+ plan.diagramType
2029
+ ),
1160
2030
  ...buildCueAnimations(plan.cues, nodeEls, plan.nodes, plan.theme, cameraLayer, titleEl, {
1161
2031
  width: plan.meta.width,
1162
2032
  height: plan.meta.height
1163
- }),
2033
+ }, plan.edges, edgeRuntimes, edgeSceneId, plan.diagramType),
1164
2034
  ...buildBeatCaptionAnimations(plan.beats, captionLayer)
1165
2035
  ];
2036
+ mountAnnotations(annotationLayer, plan.annotations, plan.nodes, plan.theme, {
2037
+ width: plan.meta.width,
2038
+ height: plan.meta.height
2039
+ });
1166
2040
  for (const anim of allAnims) {
1167
2041
  anim.pause();
1168
2042
  anim.currentTime = 0;
@@ -1260,5 +2134,6 @@ function createDiagram(opts) {
1260
2134
  return diagram;
1261
2135
  }
1262
2136
  export {
2137
+ ICON_REGISTRY,
1263
2138
  createDiagram
1264
2139
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "description": "Browser renderer for diagram-native animated MarkdyScript architecture diagrams, built on the Web Animations API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,14 +44,14 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@markdy/core": "0.8.10"
47
+ "@markdy/core": "0.8.12"
48
48
  },
49
49
  "devDependencies": {
50
50
  "jsdom": "^29.1.1",
51
51
  "tsup": "^8.5.1",
52
52
  "typescript": "^5.9.3",
53
53
  "vitest": "^4.1.7",
54
- "@markdy/stdlib-systems": "0.8.10"
54
+ "@markdy/stdlib-systems": "0.8.12"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsup",