@markdy/renderer-dom 0.8.2 → 0.8.3

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
@@ -42,7 +42,7 @@ import { createPlayer } from "@markdy/renderer-dom";
42
42
  const player = createPlayer({
43
43
  container: document.getElementById("scene")!,
44
44
  code: `
45
- scene "Request" theme=midnight
45
+ scene "Request" theme=paper
46
46
  browser Web
47
47
  service API
48
48
  beat main:
package/dist/index.d.ts CHANGED
@@ -10,6 +10,12 @@ interface PlayerOptions {
10
10
  autoplay?: boolean;
11
11
  loop?: boolean;
12
12
  copyright?: boolean;
13
+ /**
14
+ * Maps node `image=`/`logo=` values to resolved URLs. Lets a host (Astro,
15
+ * MDX) remap DSL asset references to CDN/data URLs. Values not present here
16
+ * are used verbatim as the image `src`.
17
+ */
18
+ assets?: Record<string, string>;
13
19
  /** @deprecated Prefer sceneBoundaryProgress. */
14
20
  progressBar?: boolean;
15
21
  /** Show rainbow progress around scene boundary. Defaults to true. */
package/dist/index.js CHANGED
@@ -233,13 +233,13 @@ function ensureDefs(svg, theme, id) {
233
233
  const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
234
234
  arrow.setAttribute("id", `${id}-arrow-${kind}`);
235
235
  arrow.setAttribute("viewBox", "0 0 10 10");
236
- arrow.setAttribute("refX", "8");
236
+ arrow.setAttribute("refX", "8.5");
237
237
  arrow.setAttribute("refY", "5");
238
- arrow.setAttribute("markerWidth", "6");
239
- arrow.setAttribute("markerHeight", "6");
238
+ arrow.setAttribute("markerWidth", "7");
239
+ arrow.setAttribute("markerHeight", "7");
240
240
  arrow.setAttribute("orient", "auto-start-reverse");
241
241
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
242
- path.setAttribute("d", "M 1 1 L 9 5 L 1 9 z");
242
+ path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
243
243
  path.setAttribute("fill", color);
244
244
  arrow.appendChild(path);
245
245
  defs.appendChild(arrow);
@@ -260,19 +260,21 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
260
260
  path.setAttribute("d", d);
261
261
  path.setAttribute("fill", "none");
262
262
  path.setAttribute("stroke", color);
263
- path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2.5");
263
+ path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2");
264
264
  path.setAttribute("stroke-linejoin", "round");
265
265
  path.setAttribute("stroke-linecap", "round");
266
266
  if (style.dash) path.setAttribute("stroke-dasharray", style.dash);
267
267
  if (style.marker !== "none") {
268
268
  path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
269
269
  }
270
- path.style.filter = `drop-shadow(0 0 4px ${color}55)`;
270
+ if (kind !== "dependency") {
271
+ path.style.filter = `drop-shadow(0 0 3px ${color}33)`;
272
+ }
271
273
  const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
272
- dot.setAttribute("r", "4");
274
+ dot.setAttribute("r", "3.5");
273
275
  dot.setAttribute("fill", color);
274
276
  dot.style.opacity = "0";
275
- dot.style.filter = `drop-shadow(0 0 6px ${color})`;
277
+ dot.style.filter = `drop-shadow(0 0 4px ${color}aa)`;
276
278
  group.append(path, dot);
277
279
  let labelEl;
278
280
  let labelRect;
@@ -280,6 +282,20 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
280
282
  const textWidth = label.length * 6.6 + 10;
281
283
  const placement = placeFlowLabel(points, textWidth, labelObstacles, bounds);
282
284
  labelRect = placement.rect;
285
+ const plate = document.createElementNS("http://www.w3.org/2000/svg", "rect");
286
+ const padX = 7;
287
+ const halfW = textWidth / 2;
288
+ plate.setAttribute("x", String(placement.x - halfW - padX));
289
+ plate.setAttribute("y", String(placement.y - 9));
290
+ plate.setAttribute("width", String(textWidth + padX * 2));
291
+ plate.setAttribute("height", "18");
292
+ plate.setAttribute("rx", "6");
293
+ plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
294
+ plate.setAttribute("fill-opacity", "0.9");
295
+ plate.setAttribute("stroke", theme.hairline ?? `color-mix(in srgb, ${theme.border} 60%, transparent)`);
296
+ plate.setAttribute("stroke-width", "1");
297
+ plate.style.opacity = "0";
298
+ group.appendChild(plate);
283
299
  labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
284
300
  labelEl.setAttribute("x", String(placement.x));
285
301
  labelEl.setAttribute("y", String(placement.y));
@@ -287,14 +303,11 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
287
303
  labelEl.setAttribute("dominant-baseline", "middle");
288
304
  labelEl.setAttribute("font-size", "11");
289
305
  labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, monospace");
290
- labelEl.setAttribute("fill", theme.textMuted);
291
- labelEl.setAttribute("stroke", theme.canvas);
292
- labelEl.setAttribute("stroke-width", "3");
293
- labelEl.setAttribute("paint-order", "stroke");
294
- labelEl.setAttribute("stroke-linejoin", "round");
306
+ labelEl.setAttribute("fill", theme.text);
295
307
  labelEl.textContent = label;
296
308
  labelEl.style.opacity = "0";
297
309
  group.appendChild(labelEl);
310
+ labelEl.__plate = plate;
298
311
  }
299
312
  svg.appendChild(group);
300
313
  return { group, path, label: labelEl, dot, pathLen: len, points, labelRect };
@@ -332,6 +345,10 @@ function animateEdgeReveal(runtime, startMs, durMs) {
332
345
  );
333
346
  if (label) {
334
347
  anims.push(label.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
348
+ const plate = label.__plate;
349
+ if (plate) {
350
+ anims.push(plate.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
351
+ }
335
352
  }
336
353
  if (points.length >= 2) {
337
354
  anims.push(
@@ -448,42 +465,46 @@ function ensureNodeStyles(doc) {
448
465
  box-sizing: border-box;
449
466
  width: var(--md-node-w, 184px);
450
467
  height: var(--md-node-h, 88px);
451
- border-radius: 14px;
452
- border: 1px solid var(--md-border);
453
- background: linear-gradient(145deg, var(--md-surface-raised), var(--md-surface));
468
+ border-radius: 12px;
469
+ background:
470
+ linear-gradient(180deg,
471
+ var(--md-node-surface-raised, color-mix(in srgb, var(--md-surface-raised) 88%, #ffffff 12%)),
472
+ var(--md-node-surface, var(--md-surface)));
454
473
  color: var(--md-text);
455
- box-shadow: 0 12px 32px rgba(2, 6, 23, 0.28), 0 0 0 1px rgba(255,255,255,0.04) inset;
474
+ box-shadow:
475
+ 0 1px 1px color-mix(in srgb, var(--md-shadow, rgba(2, 6, 23, 0.5)) 50%, transparent),
476
+ 0 10px 22px -12px var(--md-shadow, rgba(2, 6, 23, 0.55)),
477
+ inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent)),
478
+ inset 0 1px 0 rgba(255, 255, 255, 0.05);
456
479
  font-family: Inter, ui-sans-serif, system-ui, sans-serif;
457
480
  overflow: hidden;
458
481
  opacity: 0;
459
482
  transform: translateY(8px);
460
- transition: box-shadow 0.3s ease;
483
+ transition: box-shadow 0.25s ease, transform 0.25s ease;
461
484
  }
462
485
  .markdy-node[data-visible="1"] {
463
486
  opacity: 1;
464
487
  transform: translateY(0);
465
488
  }
466
489
  .markdy-node[data-focused="1"] {
467
- box-shadow: 0 0 0 2px var(--md-accent), 0 16px 40px rgba(2, 6, 23, 0.35);
490
+ box-shadow:
491
+ 0 2px 4px rgba(2, 6, 23, 0.32),
492
+ 0 16px 34px -14px rgba(2, 6, 23, 0.6),
493
+ inset 0 0 0 1px color-mix(in srgb, var(--md-accent) 65%, transparent),
494
+ 0 0 0 3px color-mix(in srgb, var(--md-accent) 20%, transparent);
468
495
  }
469
496
  .markdy-node[data-glow="1"] {
470
- box-shadow: 0 0 24px var(--md-glow-color, var(--md-accent)), 0 0 0 1px var(--md-glow-color, var(--md-accent)) inset;
471
- }
472
- .markdy-node__rail {
473
- position: absolute;
474
- left: 0;
475
- top: 0;
476
- bottom: 0;
477
- width: 4px;
478
- background: var(--md-role-color, var(--md-accent));
479
- border-radius: 16px 0 0 16px;
480
- }
481
- .markdy-node__type {
482
- display: none;
497
+ box-shadow:
498
+ 0 2px 4px rgba(2, 6, 23, 0.32),
499
+ 0 0 0 1px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 55%, transparent),
500
+ 0 0 20px -2px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 42%, transparent),
501
+ inset 0 0 18px -8px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 45%, transparent);
483
502
  }
503
+ .markdy-node__rail { display: none; }
504
+ .markdy-node__type { display: none; }
484
505
  .markdy-node__body {
485
506
  height: 100%;
486
- padding: 0 14px 0 18px;
507
+ padding: 0 13px;
487
508
  display: flex;
488
509
  align-items: center;
489
510
  gap: 10px;
@@ -493,43 +514,69 @@ function ensureNodeStyles(doc) {
493
514
  flex: 0 0 auto;
494
515
  width: 30px;
495
516
  height: 30px;
496
- border-radius: 10px;
517
+ border-radius: 8px;
497
518
  display: flex;
498
519
  align-items: center;
499
520
  justify-content: center;
500
521
  color: var(--md-role-color, var(--md-accent));
501
- background: color-mix(in srgb, var(--md-role-color, var(--md-accent)) 16%, transparent);
502
- box-shadow: 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 28%, transparent) inset;
522
+ background:
523
+ linear-gradient(180deg,
524
+ color-mix(in srgb, var(--md-role-color, var(--md-accent)) 24%, transparent),
525
+ color-mix(in srgb, var(--md-role-color, var(--md-accent)) 11%, transparent));
526
+ box-shadow:
527
+ inset 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 34%, transparent),
528
+ inset 0 1px 0 rgba(255, 255, 255, 0.12);
503
529
  }
504
530
  .markdy-node__icon svg {
505
- width: 18px;
506
- height: 18px;
531
+ width: 17px;
532
+ height: 17px;
507
533
  display: block;
508
534
  stroke: currentColor;
509
535
  }
536
+ .markdy-node__icon[data-media="image"] {
537
+ background: color-mix(in srgb, var(--md-surface-raised) 68%, #ffffff 32%);
538
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-border) 60%, transparent);
539
+ padding: 3px;
540
+ overflow: hidden;
541
+ }
542
+ .markdy-node__icon img {
543
+ width: 100%;
544
+ height: 100%;
545
+ object-fit: contain;
546
+ border-radius: 6px;
547
+ display: block;
548
+ }
549
+ .markdy-node__icon[data-fit="cover"] img {
550
+ object-fit: cover;
551
+ }
510
552
  .markdy-node[data-icon="decision"] .markdy-node__icon,
511
553
  .markdy-node[data-icon="flow"] .markdy-node__icon {
512
554
  border-radius: 999px;
513
555
  }
514
556
  .markdy-node__label {
557
+ flex: 1 1 auto;
515
558
  min-width: 0;
516
559
  padding: 0;
517
- font-size: 15px;
518
- font-weight: 650;
519
- line-height: 1.25;
520
- white-space: nowrap;
560
+ font-size: 14px;
561
+ font-weight: 600;
562
+ letter-spacing: -0.01em;
563
+ line-height: 1.18;
564
+ display: -webkit-box;
565
+ -webkit-box-orient: vertical;
566
+ -webkit-line-clamp: 3;
567
+ line-clamp: 3;
521
568
  overflow: hidden;
522
- text-overflow: ellipsis;
569
+ overflow-wrap: anywhere;
570
+ word-break: break-word;
571
+ text-wrap: balance;
523
572
  }
524
- .markdy-node[data-role="client"] { border-radius: 20px 20px 8px 8px; }
525
- .markdy-node[data-role="data"] { border-radius: 16px 16px 28px 28px; }
526
- .markdy-node[data-role="flow"] { transform: rotate(0deg); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
527
- .markdy-node[data-role="network"] { clip-path: polygon(8% 0%, 92% 0%, 100% 50%, 92% 100%, 8% 100%, 0% 50%); }
573
+ .markdy-node[data-role="client"] { border-radius: 15px 15px 9px 9px; }
574
+ .markdy-node[data-role="data"] { border-radius: 12px 12px 20px 20px; }
528
575
  .markdy-scene-title {
529
576
  position: absolute;
530
- left: 64px;
531
- top: 28px;
532
- right: 64px;
577
+ left: 44px;
578
+ top: 26px;
579
+ right: 44px;
533
580
  font-size: 32px;
534
581
  font-weight: 700;
535
582
  line-height: 1.2;
@@ -636,6 +683,49 @@ var ICONS = {
636
683
  ["circle", { cx: "18", cy: "18", r: "2.5" }],
637
684
  ["path", { d: "M8.4 11 15.6 7M8.4 13 15.6 17" }]
638
685
  ],
686
+ server: [
687
+ ["rect", { x: "4", y: "4.5", width: "16", height: "6.5", rx: "2" }],
688
+ ["rect", { x: "4", y: "13", width: "16", height: "6.5", rx: "2" }],
689
+ ["path", { d: "M7.5 7.75h.01M7.5 16.25h.01" }],
690
+ ["path", { d: "M11 7.75h6M11 16.25h6" }]
691
+ ],
692
+ scheduler: [
693
+ ["circle", { cx: "12", cy: "12", r: "8" }],
694
+ ["path", { d: "M12 7.5V12l3.2 2" }]
695
+ ],
696
+ key: [
697
+ ["circle", { cx: "8.5", cy: "8.5", r: "3.6" }],
698
+ ["path", { d: "M11.1 11.1 19 19" }],
699
+ ["path", { d: "M16.4 16.4l1.8 1.8M18.2 13.6l1.8 1.8" }]
700
+ ],
701
+ pod: [
702
+ ["path", { d: "M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Z" }],
703
+ ["path", { d: "M8.75 10.5h6.5v5h-6.5z" }]
704
+ ],
705
+ function: [
706
+ ["rect", { x: "4", y: "4", width: "16", height: "16", rx: "4.5" }],
707
+ ["path", { d: "M13.2 7.5 9.5 12.4h3.4L11 16.5" }]
708
+ ],
709
+ lock: [
710
+ ["rect", { x: "5", y: "11", width: "14", height: "9", rx: "2.2" }],
711
+ ["path", { d: "M8 11V8a4 4 0 0 1 8 0v3" }],
712
+ ["path", { d: "M12 14.8v2.4" }]
713
+ ],
714
+ metrics: [
715
+ ["path", { d: "M4 20h16" }],
716
+ ["rect", { x: "5", y: "11", width: "2.6", height: "6", rx: "0.6" }],
717
+ ["rect", { x: "10.7", y: "6.5", width: "2.6", height: "10.5", rx: "0.6" }],
718
+ ["rect", { x: "16.4", y: "13", width: "2.6", height: "4", rx: "0.6" }]
719
+ ],
720
+ mobile: [
721
+ ["rect", { x: "7", y: "3", width: "10", height: "18", rx: "2.6" }],
722
+ ["path", { d: "M10.5 18h3" }]
723
+ ],
724
+ registry: [
725
+ ["rect", { x: "3.5", y: "13", width: "7", height: "6", rx: "1" }],
726
+ ["rect", { x: "13.5", y: "13", width: "7", height: "6", rx: "1" }],
727
+ ["rect", { x: "8.5", y: "5", width: "7", height: "6", rx: "1" }]
728
+ ],
639
729
  flow: [
640
730
  ["path", { d: "M5 12h14" }],
641
731
  ["path", { d: "m13 6 6 6-6 6" }]
@@ -647,38 +737,78 @@ var ICONS = {
647
737
  ]
648
738
  };
649
739
  function iconKeyForNode(node) {
740
+ const override = typeof node.props?.icon === "string" ? node.props.icon.toLowerCase() : void 0;
741
+ if (override && ICONS[override]) return override;
650
742
  if (node.kind === "api_gateway" || node.kind === "gateway" || node.kind === "load_balancer" || node.kind === "ingress") return "gateway";
651
743
  if (node.kind === "db" || node.kind === "database" || node.kind === "sql" || node.kind === "nosql" || node.kind === "warehouse") return "database";
652
744
  if (node.kind === "bucket" || node.kind === "object_store" || node.kind === "blob" || node.kind === "volume" || node.kind === "disk") return "storage";
653
745
  if (node.kind === "cdn" || node.kind === "dns" || node.kind === "internet") return "cdn";
654
746
  if (node.kind === "queue" || node.kind === "topic" || node.kind === "stream" || node.kind === "event_bus" || node.kind === "broker") return "queue";
655
- if (node.kind === "worker" || node.kind === "job" || node.kind === "scheduler" || node.kind === "cron" || node.kind === "batch") return "worker";
747
+ if (node.kind === "scheduler" || node.kind === "cron") return "scheduler";
748
+ if (node.kind === "worker" || node.kind === "job" || node.kind === "batch") return "worker";
749
+ if (node.kind === "function" || node.kind === "lambda") return "function";
750
+ if (node.kind === "pod" || node.kind === "container" || node.kind === "sidecar") return "pod";
751
+ if (node.kind === "secret" || node.kind === "key" || node.kind === "certificate" || node.kind === "vault") return "key";
752
+ if (node.kind === "auth" || node.kind === "oauth" || node.kind === "oidc" || node.kind === "jwt" || node.kind === "lock") return "lock";
753
+ if (node.kind === "firewall" || node.kind === "waf" || node.kind === "vpn" || node.kind === "bastion") return "security";
754
+ if (node.kind === "proxy" || node.kind === "reverse_proxy" || node.kind === "router" || node.kind === "nat" || node.kind === "switch") return "gateway";
755
+ if (node.kind === "monitor" || node.kind === "metrics" || node.kind === "dashboard" || node.kind === "slo" || node.kind === "probe") return "metrics";
756
+ if (node.kind === "registry" || node.kind === "artifact") return "registry";
757
+ if (node.kind === "mobile") return "mobile";
758
+ if (node.kind === "api" || node.kind === "service" || node.kind === "microservice" || node.kind === "backend" || node.kind === "server" || node.kind === "handler" || node.kind === "controller") return "server";
656
759
  if (node.kind === "browser" || node.kind === "web" || node.kind === "frontend" || node.kind === "app") return "browser";
657
760
  if (node.kind === "user" || node.kind === "client") return "user";
658
761
  if (node.kind === "decision" || node.kind === "condition") return "decision";
659
762
  if (node.kind === "cache") return "cache";
660
763
  return ICONS[node.kind] ? node.kind : node.role;
661
764
  }
662
- function createIconEl(doc, node) {
663
- const wrap = doc.createElement("div");
664
- wrap.className = "markdy-node__icon";
665
- wrap.setAttribute("aria-hidden", "true");
765
+ function appendGlyph(doc, wrap, spec) {
666
766
  const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
667
767
  svg.setAttribute("viewBox", "0 0 24 24");
668
768
  svg.setAttribute("fill", "none");
669
- svg.setAttribute("stroke-width", "2");
769
+ svg.setAttribute("stroke-width", "1.75");
670
770
  svg.setAttribute("stroke-linecap", "round");
671
771
  svg.setAttribute("stroke-linejoin", "round");
672
- const spec = ICONS[iconKeyForNode(node)] ?? ICONS.service;
673
772
  for (const [tag, attrs] of spec) {
674
773
  const child = doc.createElementNS("http://www.w3.org/2000/svg", tag);
675
774
  for (const [name, value] of Object.entries(attrs)) child.setAttribute(name, value);
676
775
  svg.appendChild(child);
677
776
  }
678
777
  wrap.appendChild(svg);
778
+ }
779
+ function resolveImageSrc(node, assets) {
780
+ const raw = node.props?.image ?? node.props?.logo;
781
+ if (typeof raw !== "string" || raw.length === 0) return void 0;
782
+ return assets?.[raw] ?? raw;
783
+ }
784
+ function createNodeMediaEl(doc, node, assets) {
785
+ const wrap = doc.createElement("div");
786
+ wrap.className = "markdy-node__icon";
787
+ wrap.setAttribute("aria-hidden", "true");
788
+ const imgSrc = resolveImageSrc(node, assets);
789
+ if (imgSrc) {
790
+ wrap.dataset.media = "image";
791
+ const fit = typeof node.props?.imageFit === "string" ? node.props.imageFit.toLowerCase() : "contain";
792
+ wrap.dataset.fit = fit === "cover" ? "cover" : "contain";
793
+ const img = doc.createElement("img");
794
+ img.src = imgSrc;
795
+ img.alt = "";
796
+ img.decoding = "async";
797
+ img.loading = "lazy";
798
+ img.referrerPolicy = "no-referrer";
799
+ img.addEventListener("error", () => {
800
+ wrap.removeAttribute("data-media");
801
+ wrap.removeAttribute("data-fit");
802
+ wrap.textContent = "";
803
+ appendGlyph(doc, wrap, ICONS[iconKeyForNode(node)] ?? ICONS.service);
804
+ });
805
+ wrap.appendChild(img);
806
+ return wrap;
807
+ }
808
+ appendGlyph(doc, wrap, ICONS[iconKeyForNode(node)] ?? ICONS.service);
679
809
  return wrap;
680
810
  }
681
- function createNodeEl(node, theme) {
811
+ function createNodeEl(node, theme, assets) {
682
812
  const el = document.createElement("div");
683
813
  el.className = "markdy-node markdy-scene-actor";
684
814
  el.dataset.actor = node.id;
@@ -694,19 +824,14 @@ function createNodeEl(node, theme) {
694
824
  el.dataset.icon = iconKeyForNode(node);
695
825
  el.title = `${node.label} (${typeText})`;
696
826
  el.setAttribute("aria-label", el.title);
697
- const rail = document.createElement("div");
698
- rail.className = "markdy-node__rail";
699
- const type = document.createElement("div");
700
- type.className = "markdy-node__type";
701
- type.textContent = typeText;
702
827
  const body = document.createElement("div");
703
828
  body.className = "markdy-node__body";
704
- const icon = createIconEl(document, node);
829
+ const icon = createNodeMediaEl(document, node, assets);
705
830
  const label = document.createElement("div");
706
831
  label.className = "markdy-node__label";
707
832
  label.textContent = node.label;
708
833
  body.append(icon, label);
709
- el.append(rail, type, body);
834
+ el.append(body);
710
835
  return el;
711
836
  }
712
837
  function createTitleEl(title) {
@@ -740,15 +865,15 @@ function ensureSceneStyles(doc) {
740
865
  linear-gradient(90deg, var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
741
866
  linear-gradient(var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px,
742
867
  linear-gradient(90deg, var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px;
743
- mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
744
- opacity: 0.82;
868
+ mask-image: radial-gradient(ellipse at 50% 42%, #000 55%, transparent 100%);
869
+ opacity: 0.5;
745
870
  }
746
871
  .markdy-scene-root::after {
747
872
  z-index: 1;
748
873
  background:
749
- radial-gradient(ellipse at 50% 0%, color-mix(in srgb, var(--md-accent) 16%, transparent), transparent 42%),
750
- linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.08) 58%, var(--md-vignette) 100%);
751
- opacity: 0.8;
874
+ radial-gradient(ellipse at 50% -8%, color-mix(in srgb, var(--md-accent) 10%, transparent), transparent 46%),
875
+ linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.05) 62%, var(--md-vignette) 100%);
876
+ opacity: 0.62;
752
877
  }
753
878
  .markdy-scene-content { z-index: 2; }
754
879
  .markdy-scene-actor-layer {
@@ -772,6 +897,10 @@ function applyThemeToScene(scene, theme) {
772
897
  scene.style.setProperty("--md-grid-major", theme.gridMajor);
773
898
  scene.style.setProperty("--md-vignette", theme.vignette);
774
899
  scene.style.setProperty("--md-accent", theme.accent);
900
+ scene.style.setProperty("--md-node-surface", theme.nodeSurface ?? theme.surface);
901
+ scene.style.setProperty("--md-node-surface-raised", theme.nodeSurfaceRaised ?? theme.surfaceRaised);
902
+ scene.style.setProperty("--md-hairline", theme.hairline ?? `color-mix(in srgb, ${theme.border} 50%, transparent)`);
903
+ scene.style.setProperty("--md-shadow", theme.shadow ?? "rgba(2, 6, 23, 0.55)");
775
904
  scene.dataset.markdyTheme = theme.name;
776
905
  }
777
906
 
@@ -783,6 +912,7 @@ function createPlayer(opts) {
783
912
  autoplay = true,
784
913
  loop = true,
785
914
  copyright = true,
915
+ assets,
786
916
  progressBar,
787
917
  sceneBoundaryProgress,
788
918
  playbackRate: initialPlaybackRate = 1,
@@ -877,7 +1007,7 @@ function createPlayer(opts) {
877
1007
  actorLayer.appendChild(titleEl);
878
1008
  const nodeEls = /* @__PURE__ */ new Map();
879
1009
  for (const node of plan.nodes) {
880
- const el = createNodeEl(node, plan.theme);
1010
+ const el = createNodeEl(node, plan.theme, assets);
881
1011
  actorLayer.appendChild(el);
882
1012
  nodeEls.set(node.id, el);
883
1013
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Browser renderer for animated MarkdyScript architecture diagrams, built on the Web Animations API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,19 +43,20 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@markdy/core": "0.8.2"
46
+ "@markdy/core": "0.8.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "jsdom": "^29.1.1",
50
50
  "tsup": "^8.5.1",
51
51
  "typescript": "^5.9.3",
52
52
  "vitest": "^4.1.7",
53
- "@markdy/stdlib-systems": "0.8.2"
53
+ "@markdy/stdlib-systems": "0.8.3"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsup",
57
57
  "typecheck": "tsc --noEmit",
58
58
  "lint": "tsc --noEmit",
59
- "test": "vitest run"
59
+ "test": "vitest run",
60
+ "harness": "pnpm --filter @markdy/core run build && pnpm run build && node harness/serve.mjs"
60
61
  }
61
62
  }