@markdy/renderer-dom 0.7.26 → 0.7.27

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 (2) hide show
  1. package/dist/index.js +1190 -82
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -309,6 +309,730 @@ function readRotation(el) {
309
309
  }
310
310
 
311
311
  // src/actors.ts
312
+ var ARCHITECTURE_NODE_TYPES = /* @__PURE__ */ new Set([
313
+ "service",
314
+ "api",
315
+ "microservice",
316
+ "client",
317
+ "user",
318
+ "db",
319
+ "database",
320
+ "queue",
321
+ "cache",
322
+ "cloud",
323
+ "region",
324
+ "container",
325
+ "cluster"
326
+ ]);
327
+ var SHOWCASE_SURFACE_TYPES = /* @__PURE__ */ new Set(["parking_map", "ascii_map", "game_scene", "byte_viz"]);
328
+ var ARCHITECTURE_STYLE_ID = "markdy-architecture-node-styles";
329
+ var SHOWCASE_STYLE_ID = "markdy-showcase-surface-styles";
330
+ function ensureShowcaseStyles(doc) {
331
+ if (doc.getElementById(SHOWCASE_STYLE_ID)) return;
332
+ const style = doc.createElement("style");
333
+ style.id = SHOWCASE_STYLE_ID;
334
+ style.textContent = `
335
+ .markdy-showcase {
336
+ --surface-a: rgba(15, 23, 42, 0.9);
337
+ --accent: #38bdf8;
338
+ --accent-2: #22c55e;
339
+ position: relative;
340
+ box-sizing: border-box;
341
+ width: 390px;
342
+ height: 250px;
343
+ overflow: hidden;
344
+ border: 1px solid rgba(148, 163, 184, 0.34);
345
+ border-radius: 24px;
346
+ color: #e5f3ff;
347
+ background:
348
+ radial-gradient(circle at 12% 0%, color-mix(in srgb, var(--accent) 32%, transparent), transparent 34%),
349
+ radial-gradient(circle at 90% 15%, color-mix(in srgb, var(--accent-2) 26%, transparent), transparent 34%),
350
+ linear-gradient(145deg, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0.025) 32%, var(--surface-a));
351
+ box-shadow:
352
+ 0 26px 70px rgba(2, 6, 23, 0.42),
353
+ 0 0 0 1px rgba(255, 255, 255, 0.06) inset,
354
+ 0 1px 0 rgba(255, 255, 255, 0.18) inset,
355
+ 0 0 44px color-mix(in srgb, var(--accent) 24%, transparent);
356
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
357
+ isolation: isolate;
358
+ contain: layout paint style;
359
+ backdrop-filter: blur(16px) saturate(1.2);
360
+ -webkit-backdrop-filter: blur(16px) saturate(1.2);
361
+ }
362
+ .markdy-showcase::before {
363
+ content: "";
364
+ position: absolute;
365
+ inset: 0;
366
+ z-index: -1;
367
+ background:
368
+ linear-gradient(90deg, transparent 0 48%, rgba(255, 255, 255, 0.09) 50%, transparent 52%) 0 0 / 34px 34px,
369
+ linear-gradient(0deg, transparent 0 48%, rgba(255, 255, 255, 0.055) 50%, transparent 52%) 0 0 / 34px 34px;
370
+ mask-image: linear-gradient(to bottom, rgba(0,0,0,0.85), transparent 82%);
371
+ opacity: 0.48;
372
+ animation: markdyGridDrift 7s linear infinite;
373
+ }
374
+ .markdy-showcase::after {
375
+ content: "";
376
+ position: absolute;
377
+ inset: -40% -20%;
378
+ z-index: -1;
379
+ background: conic-gradient(from 90deg, transparent, color-mix(in srgb, var(--accent) 20%, transparent), transparent 32%);
380
+ opacity: 0.6;
381
+ animation: markdyAurora 9s linear infinite;
382
+ }
383
+ .markdy-showcase--byte_viz { width: 420px; }
384
+ .markdy-showcase__top {
385
+ display: flex;
386
+ align-items: center;
387
+ justify-content: space-between;
388
+ gap: 12px;
389
+ padding: 14px 16px 8px;
390
+ }
391
+ .markdy-showcase__title {
392
+ min-width: 0;
393
+ overflow: hidden;
394
+ text-overflow: ellipsis;
395
+ white-space: nowrap;
396
+ font-size: 15px;
397
+ font-weight: 820;
398
+ letter-spacing: 0.01em;
399
+ color: #f8fafc;
400
+ }
401
+ .markdy-showcase__pill {
402
+ flex: 0 0 auto;
403
+ border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
404
+ border-radius: 999px;
405
+ padding: 4px 8px;
406
+ color: #bae6fd;
407
+ background: rgba(8, 47, 73, 0.54);
408
+ font-size: 10px;
409
+ font-weight: 800;
410
+ letter-spacing: 0.08em;
411
+ text-transform: uppercase;
412
+ box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 22%, transparent);
413
+ }
414
+ .markdy-showcase__body { position: absolute; inset: 48px 14px 14px; }
415
+ .markdy-showcase--parking_map { --accent: #38bdf8; --accent-2: #22c55e; }
416
+ .parking-map__road {
417
+ position: absolute;
418
+ left: 14px;
419
+ right: 14px;
420
+ top: 78px;
421
+ height: 50px;
422
+ border-radius: 999px;
423
+ background:
424
+ repeating-linear-gradient(90deg, rgba(226, 232, 240, 0.78) 0 18px, transparent 18px 34px) center / 100% 3px no-repeat,
425
+ linear-gradient(90deg, rgba(15, 23, 42, 0.96), rgba(30, 41, 59, 0.88));
426
+ box-shadow: 0 10px 28px rgba(2, 6, 23, 0.28) inset;
427
+ }
428
+ .parking-map__scan {
429
+ position: absolute;
430
+ left: 28px;
431
+ top: 42px;
432
+ width: 66px;
433
+ height: 124px;
434
+ border-radius: 18px;
435
+ border: 1px solid rgba(56, 189, 248, 0.55);
436
+ background: linear-gradient(180deg, rgba(56, 189, 248, 0.22), transparent);
437
+ box-shadow: 0 0 24px rgba(56, 189, 248, 0.28);
438
+ animation: markdyScanPulse 1.8s ease-in-out infinite;
439
+ }
440
+ .parking-map__car {
441
+ position: absolute;
442
+ left: 48px;
443
+ top: 86px;
444
+ width: 46px;
445
+ height: 22px;
446
+ border-radius: 11px 15px 15px 11px;
447
+ background: linear-gradient(90deg, #22d3ee, #818cf8);
448
+ box-shadow: 0 0 18px rgba(56, 189, 248, 0.55), 0 8px 18px rgba(2, 6, 23, 0.35);
449
+ animation: markdyCarGlide 3.8s cubic-bezier(.4,0,.2,1) infinite;
450
+ }
451
+ .parking-map__car::before,
452
+ .parking-map__car::after {
453
+ content: "";
454
+ position: absolute;
455
+ bottom: -4px;
456
+ width: 8px;
457
+ height: 8px;
458
+ border-radius: 999px;
459
+ background: #020617;
460
+ border: 2px solid #cbd5e1;
461
+ }
462
+ .parking-map__car::before { left: 7px; }
463
+ .parking-map__car::after { right: 7px; }
464
+ .parking-map__slots {
465
+ position: absolute;
466
+ right: 12px;
467
+ top: 24px;
468
+ display: grid;
469
+ grid-template-columns: repeat(4, 44px);
470
+ gap: 8px;
471
+ }
472
+ .parking-map__slot {
473
+ height: 34px;
474
+ border-radius: 10px;
475
+ border: 1px solid rgba(148, 163, 184, 0.34);
476
+ background: rgba(15, 23, 42, 0.7);
477
+ box-shadow: 0 0 0 1px rgba(255,255,255,0.035) inset;
478
+ }
479
+ .parking-map__slot:nth-child(3),
480
+ .parking-map__slot:nth-child(6),
481
+ .parking-map__slot:nth-child(8) {
482
+ border-color: rgba(34, 197, 94, 0.78);
483
+ background: rgba(20, 83, 45, 0.5);
484
+ animation: markdySlotGlow 1.7s ease-in-out infinite;
485
+ }
486
+ .parking-map__route {
487
+ position: absolute;
488
+ left: 91px;
489
+ right: 95px;
490
+ top: 109px;
491
+ height: 3px;
492
+ border-radius: 999px;
493
+ background: linear-gradient(90deg, transparent, #22c55e, #38bdf8, transparent);
494
+ filter: drop-shadow(0 0 8px #22c55e);
495
+ animation: markdyRouteFlow 1.5s linear infinite;
496
+ }
497
+ .parking-map__stats {
498
+ position: absolute;
499
+ left: 12px;
500
+ right: 12px;
501
+ bottom: 5px;
502
+ display: grid;
503
+ grid-template-columns: repeat(3, 1fr);
504
+ gap: 8px;
505
+ }
506
+ .parking-map__stat,
507
+ .ascii-map__badge,
508
+ .game-scene__hud,
509
+ .byte-viz__badge {
510
+ border: 1px solid rgba(148, 163, 184, 0.24);
511
+ border-radius: 12px;
512
+ padding: 8px;
513
+ background: rgba(2, 6, 23, 0.42);
514
+ color: #cbd5e1;
515
+ font-size: 10px;
516
+ font-weight: 760;
517
+ }
518
+ .parking-map__stat strong,
519
+ .byte-viz__badge strong {
520
+ display: block;
521
+ margin-top: 2px;
522
+ color: #f8fafc;
523
+ font-size: 16px;
524
+ }
525
+ .markdy-showcase--ascii_map { --accent: #22c55e; --accent-2: #38bdf8; }
526
+ .ascii-map__terminal {
527
+ position: absolute;
528
+ inset: 0;
529
+ border-radius: 18px;
530
+ border: 1px solid rgba(34, 197, 94, 0.34);
531
+ background: linear-gradient(180deg, rgba(2, 6, 23, 0.72), rgba(2, 6, 23, 0.36));
532
+ box-shadow: 0 0 28px rgba(34, 197, 94, 0.16) inset;
533
+ }
534
+ .ascii-map__chrome {
535
+ height: 28px;
536
+ padding-left: 12px;
537
+ display: flex;
538
+ align-items: center;
539
+ gap: 6px;
540
+ border-bottom: 1px solid rgba(148, 163, 184, 0.16);
541
+ }
542
+ .ascii-map__chrome span {
543
+ width: 8px;
544
+ height: 8px;
545
+ border-radius: 999px;
546
+ background: #ef4444;
547
+ box-shadow: 14px 0 #f59e0b, 28px 0 #22c55e;
548
+ }
549
+ .ascii-map__code {
550
+ position: absolute;
551
+ left: 16px;
552
+ top: 46px;
553
+ font-family: "SFMono-Regular", Consolas, monospace;
554
+ font-size: 19px;
555
+ line-height: 1.55;
556
+ color: #bbf7d0;
557
+ text-shadow: 0 0 12px rgba(34, 197, 94, 0.45);
558
+ }
559
+ .ascii-map__code b {
560
+ color: #67e8f9;
561
+ animation: markdyBlink 1.2s steps(2, end) infinite;
562
+ }
563
+ .ascii-map__minimap {
564
+ position: absolute;
565
+ right: 18px;
566
+ top: 48px;
567
+ width: 102px;
568
+ height: 122px;
569
+ display: grid;
570
+ grid-template-columns: repeat(3, 1fr);
571
+ gap: 7px;
572
+ }
573
+ .ascii-map__mini-slot {
574
+ border-radius: 7px;
575
+ border: 1px solid rgba(148, 163, 184, 0.28);
576
+ background: rgba(15, 23, 42, 0.72);
577
+ }
578
+ .ascii-map__mini-slot:nth-child(2),
579
+ .ascii-map__mini-slot:nth-child(6) {
580
+ border-color: rgba(34, 197, 94, 0.85);
581
+ background: rgba(20, 83, 45, 0.56);
582
+ }
583
+ .ascii-map__transform {
584
+ position: absolute;
585
+ left: 16px;
586
+ right: 16px;
587
+ bottom: 10px;
588
+ display: grid;
589
+ grid-template-columns: 1fr 1fr 1fr;
590
+ gap: 8px;
591
+ }
592
+ .markdy-showcase--game_scene { --accent: #f59e0b; --accent-2: #22c55e; }
593
+ .game-scene__world {
594
+ position: absolute;
595
+ inset: 0;
596
+ border-radius: 18px;
597
+ overflow: hidden;
598
+ background:
599
+ radial-gradient(circle at 80% 18%, rgba(245, 158, 11, 0.22), transparent 26%),
600
+ linear-gradient(160deg, #172554, #020617 72%);
601
+ }
602
+ .game-scene__world::before {
603
+ content: "";
604
+ position: absolute;
605
+ inset: 0;
606
+ background: repeating-linear-gradient(115deg, transparent 0 22px, rgba(255,255,255,0.06) 22px 24px);
607
+ animation: markdyRoadMove 4s linear infinite;
608
+ }
609
+ .game-scene__road {
610
+ position: absolute;
611
+ left: -35px;
612
+ right: -35px;
613
+ top: 94px;
614
+ height: 76px;
615
+ transform: rotate(-8deg);
616
+ background:
617
+ repeating-linear-gradient(90deg, #e2e8f0 0 24px, transparent 24px 46px) center / 100% 4px no-repeat,
618
+ linear-gradient(180deg, #334155, #0f172a);
619
+ box-shadow: 0 18px 32px rgba(2, 6, 23, 0.42);
620
+ }
621
+ .game-scene__car {
622
+ position: absolute;
623
+ left: 88px;
624
+ top: 126px;
625
+ width: 58px;
626
+ height: 30px;
627
+ border-radius: 12px 18px 18px 12px;
628
+ background: linear-gradient(90deg, #fb7185, #f59e0b);
629
+ transform: rotate(-8deg);
630
+ box-shadow: 0 0 24px rgba(245, 158, 11, 0.52);
631
+ animation: markdyCarPark 3.4s cubic-bezier(.2,1,.3,1) infinite;
632
+ }
633
+ .game-scene__spot {
634
+ position: absolute;
635
+ right: 54px;
636
+ top: 85px;
637
+ width: 90px;
638
+ height: 68px;
639
+ border: 3px solid rgba(34, 197, 94, 0.78);
640
+ border-left-style: dashed;
641
+ border-radius: 10px;
642
+ filter: drop-shadow(0 0 12px rgba(34, 197, 94, 0.48));
643
+ }
644
+ .game-scene__cones {
645
+ position: absolute;
646
+ right: 80px;
647
+ bottom: 34px;
648
+ display: flex;
649
+ gap: 14px;
650
+ }
651
+ .game-scene__cone {
652
+ width: 0;
653
+ height: 0;
654
+ border-left: 10px solid transparent;
655
+ border-right: 10px solid transparent;
656
+ border-bottom: 26px solid #f97316;
657
+ filter: drop-shadow(0 5px 8px rgba(2, 6, 23, 0.35));
658
+ }
659
+ .game-scene__hud {
660
+ position: absolute;
661
+ left: 14px;
662
+ top: 14px;
663
+ min-width: 120px;
664
+ border-color: rgba(245, 158, 11, 0.38);
665
+ }
666
+ .game-scene__hud strong { color: #fed7aa; font-size: 18px; }
667
+ .game-scene__perfect {
668
+ position: absolute;
669
+ left: 132px;
670
+ top: 54px;
671
+ color: #fef3c7;
672
+ font-size: 21px;
673
+ font-weight: 900;
674
+ letter-spacing: 0.05em;
675
+ text-shadow: 0 0 18px rgba(245, 158, 11, 0.7);
676
+ animation: markdyPerfectPop 1.6s ease-in-out infinite;
677
+ }
678
+ .markdy-showcase--byte_viz { --accent: #a78bfa; --accent-2: #38bdf8; }
679
+ .byte-viz__pipeline {
680
+ position: absolute;
681
+ inset: 4px;
682
+ display: grid;
683
+ grid-template-columns: 98px 1fr 122px;
684
+ gap: 12px;
685
+ align-items: stretch;
686
+ }
687
+ .byte-viz__glyph {
688
+ display: grid;
689
+ place-items: center;
690
+ border-radius: 22px;
691
+ border: 1px solid rgba(167, 139, 250, 0.45);
692
+ background: radial-gradient(circle at 30% 20%, rgba(167, 139, 250, 0.42), rgba(15, 23, 42, 0.78));
693
+ color: #fff;
694
+ font-size: 72px;
695
+ font-weight: 920;
696
+ box-shadow: 0 0 34px rgba(167, 139, 250, 0.32) inset;
697
+ }
698
+ .byte-viz__middle { display: grid; gap: 10px; }
699
+ .byte-viz__row {
700
+ display: flex;
701
+ align-items: center;
702
+ gap: 7px;
703
+ }
704
+ .byte-viz__chip {
705
+ flex: 1 1 0;
706
+ border: 1px solid rgba(148, 163, 184, 0.25);
707
+ border-radius: 12px;
708
+ padding: 8px 10px;
709
+ background: rgba(2, 6, 23, 0.42);
710
+ color: #ddd6fe;
711
+ font-family: "SFMono-Regular", Consolas, monospace;
712
+ font-size: 12px;
713
+ font-weight: 800;
714
+ }
715
+ .byte-viz__arrow {
716
+ color: #67e8f9;
717
+ filter: drop-shadow(0 0 8px #38bdf8);
718
+ animation: markdyArrowPulse 1.1s ease-in-out infinite;
719
+ }
720
+ .byte-viz__bits {
721
+ display: grid;
722
+ grid-template-columns: repeat(8, 1fr);
723
+ gap: 5px;
724
+ }
725
+ .byte-viz__bit {
726
+ display: grid;
727
+ place-items: center;
728
+ height: 31px;
729
+ border-radius: 8px;
730
+ background: rgba(15, 23, 42, 0.82);
731
+ border: 1px solid rgba(56, 189, 248, 0.28);
732
+ color: #bae6fd;
733
+ font-family: "SFMono-Regular", Consolas, monospace;
734
+ font-size: 12px;
735
+ font-weight: 900;
736
+ animation: markdyBitFlip 1.9s ease-in-out infinite;
737
+ }
738
+ .byte-viz__bit:nth-child(2n) { animation-delay: 0.12s; }
739
+ .byte-viz__bit:nth-child(3n) { animation-delay: 0.24s; }
740
+ .byte-viz__side { display: grid; gap: 9px; }
741
+ @keyframes markdyGridDrift { to { background-position: 34px 34px, 34px 34px; } }
742
+ @keyframes markdyAurora { to { transform: rotate(1turn); } }
743
+ @keyframes markdyScanPulse { 50% { transform: translateX(18px); opacity: 0.72; } }
744
+ @keyframes markdyCarGlide { 0%, 12% { transform: translateX(0); } 68%, 100% { transform: translateX(198px); } }
745
+ @keyframes markdySlotGlow { 50% { box-shadow: 0 0 20px rgba(34, 197, 94, 0.38); } }
746
+ @keyframes markdyRouteFlow { to { filter: hue-rotate(70deg) drop-shadow(0 0 9px #38bdf8); } }
747
+ @keyframes markdyBlink { 50% { opacity: 0.25; } }
748
+ @keyframes markdyRoadMove { to { background-position: 44px 0; } }
749
+ @keyframes markdyCarPark { 0%, 12% { transform: translateX(0) rotate(-8deg); } 70%, 100% { transform: translateX(164px) translateY(-31px) rotate(-1deg); } }
750
+ @keyframes markdyPerfectPop { 0%, 100% { transform: scale(0.92); opacity: 0.72; } 50% { transform: scale(1.05); opacity: 1; } }
751
+ @keyframes markdyArrowPulse { 50% { transform: translateX(3px); opacity: 0.72; } }
752
+ @keyframes markdyBitFlip { 50% { transform: translateY(-2px); border-color: rgba(167, 139, 250, 0.75); } }
753
+ `;
754
+ doc.head.appendChild(style);
755
+ }
756
+ function createShowcaseSurfaceEl(type, def) {
757
+ ensureShowcaseStyles(document);
758
+ const label = def.args[0] || type.replace("_", " ");
759
+ const root = document.createElement("div");
760
+ root.className = `markdy-showcase markdy-showcase--${type}`;
761
+ root.setAttribute("role", "img");
762
+ root.setAttribute("aria-label", label);
763
+ if (type === "parking_map") {
764
+ root.innerHTML = `
765
+ <div class="markdy-showcase__top"><div class="markdy-showcase__title"></div><div class="markdy-showcase__pill">live ops</div></div>
766
+ <div class="markdy-showcase__body">
767
+ <div class="parking-map__scan"></div><div class="parking-map__road"></div><div class="parking-map__route"></div><div class="parking-map__car"></div>
768
+ <div class="parking-map__slots"><span class="parking-map__slot"></span><span class="parking-map__slot"></span><span class="parking-map__slot"></span><span class="parking-map__slot"></span><span class="parking-map__slot"></span><span class="parking-map__slot"></span><span class="parking-map__slot"></span><span class="parking-map__slot"></span></div>
769
+ <div class="parking-map__stats"><div class="parking-map__stat">occupancy<strong>87%</strong></div><div class="parking-map__stat">OCR<strong>18ms</strong></div><div class="parking-map__stat">route<strong>A-17</strong></div></div>
770
+ </div>`;
771
+ } else if (type === "ascii_map") {
772
+ root.innerHTML = `
773
+ <div class="markdy-showcase__top"><div class="markdy-showcase__title"></div><div class="markdy-showcase__pill">parser</div></div>
774
+ <div class="markdy-showcase__body">
775
+ <div class="ascii-map__terminal"><div class="ascii-map__chrome"><span></span></div><div class="ascii-map__code">[P1][P2][<b> </b>][EV]<br />[IN]===LANE===[OUT]<br />0101 0000 0100 0001</div><div class="ascii-map__minimap"><i class="ascii-map__mini-slot"></i><i class="ascii-map__mini-slot"></i><i class="ascii-map__mini-slot"></i><i class="ascii-map__mini-slot"></i><i class="ascii-map__mini-slot"></i><i class="ascii-map__mini-slot"></i></div><div class="ascii-map__transform"><div class="ascii-map__badge">tokens</div><div class="ascii-map__badge">graph</div><div class="ascii-map__badge">slots</div></div></div>
776
+ </div>`;
777
+ } else if (type === "game_scene") {
778
+ root.innerHTML = `
779
+ <div class="markdy-showcase__top"><div class="markdy-showcase__title"></div><div class="markdy-showcase__pill">60 fps</div></div>
780
+ <div class="markdy-showcase__body">
781
+ <div class="game-scene__world"><div class="game-scene__hud">combo<br /><strong>x4.2</strong></div><div class="game-scene__perfect">PERFECT PARK</div><div class="game-scene__road"></div><div class="game-scene__spot"></div><div class="game-scene__car"></div><div class="game-scene__cones"><i class="game-scene__cone"></i><i class="game-scene__cone"></i><i class="game-scene__cone"></i></div></div>
782
+ </div>`;
783
+ } else {
784
+ root.innerHTML = `
785
+ <div class="markdy-showcase__top"><div class="markdy-showcase__title"></div><div class="markdy-showcase__pill">UTF-8</div></div>
786
+ <div class="markdy-showcase__body">
787
+ <div class="byte-viz__pipeline"><div class="byte-viz__glyph">A</div><div class="byte-viz__middle"><div class="byte-viz__row"><div class="byte-viz__chip">char</div><span class="byte-viz__arrow">-></span><div class="byte-viz__chip">U+0041</div></div><div class="byte-viz__row"><div class="byte-viz__chip">0x41</div><span class="byte-viz__arrow">-></span><div class="byte-viz__chip">01000001</div></div><div class="byte-viz__bits"><span class="byte-viz__bit">0</span><span class="byte-viz__bit">1</span><span class="byte-viz__bit">0</span><span class="byte-viz__bit">0</span><span class="byte-viz__bit">0</span><span class="byte-viz__bit">0</span><span class="byte-viz__bit">0</span><span class="byte-viz__bit">1</span></div></div><div class="byte-viz__side"><div class="byte-viz__badge">bytes<strong>1</strong></div><div class="byte-viz__badge">planes<strong>BMP</strong></div><div class="byte-viz__badge">glyph<strong>paint</strong></div></div></div>
788
+ </div>`;
789
+ }
790
+ const title = root.querySelector(".markdy-showcase__title");
791
+ if (title) title.textContent = label;
792
+ return root;
793
+ }
794
+ function ensureArchitectureStyles(doc) {
795
+ if (doc.getElementById(ARCHITECTURE_STYLE_ID)) return;
796
+ const style = doc.createElement("style");
797
+ style.id = ARCHITECTURE_STYLE_ID;
798
+ style.textContent = `
799
+ .markdy-arch-node {
800
+ --markdy-node-accent: #38bdf8;
801
+ --markdy-node-accent-2: #22c55e;
802
+ --markdy-node-surface: rgba(15, 23, 42, 0.82);
803
+ --markdy-node-border: rgba(148, 163, 184, 0.34);
804
+ --markdy-node-glow: rgba(56, 189, 248, 0.24);
805
+ position: relative;
806
+ isolation: isolate;
807
+ width: 184px;
808
+ min-height: 88px;
809
+ box-sizing: border-box;
810
+ display: grid;
811
+ grid-template-columns: 34px minmax(0, 1fr);
812
+ align-items: center;
813
+ gap: 10px;
814
+ padding: 13px 14px;
815
+ border: 1px solid var(--markdy-node-border);
816
+ border-radius: 14px;
817
+ color: #e5eefb;
818
+ background:
819
+ linear-gradient(145deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.02) 34%, rgba(15, 23, 42, 0.88) 100%),
820
+ radial-gradient(circle at 18% 0%, color-mix(in srgb, var(--markdy-node-accent) 34%, transparent), transparent 42%),
821
+ var(--markdy-node-surface);
822
+ box-shadow:
823
+ 0 16px 34px rgba(2, 6, 23, 0.28),
824
+ 0 0 0 1px rgba(255, 255, 255, 0.04) inset,
825
+ 0 1px 0 rgba(255, 255, 255, 0.12) inset,
826
+ 0 0 28px var(--markdy-node-glow);
827
+ overflow: hidden;
828
+ contain: layout paint style;
829
+ backdrop-filter: blur(14px) saturate(1.18);
830
+ -webkit-backdrop-filter: blur(14px) saturate(1.18);
831
+ }
832
+ .markdy-arch-node::before {
833
+ content: "";
834
+ position: absolute;
835
+ inset: 0;
836
+ z-index: -1;
837
+ background:
838
+ linear-gradient(90deg, transparent 0 24%, rgba(255, 255, 255, 0.06) 50%, transparent 76%) -180px 0 / 180px 100% no-repeat,
839
+ repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.055) 0 1px, transparent 1px 10px);
840
+ opacity: 0.6;
841
+ }
842
+ .markdy-arch-node::after {
843
+ content: "";
844
+ position: absolute;
845
+ left: 14px;
846
+ right: 14px;
847
+ bottom: 9px;
848
+ height: 2px;
849
+ border-radius: 999px;
850
+ background: linear-gradient(90deg, transparent, var(--markdy-node-accent), var(--markdy-node-accent-2), transparent);
851
+ opacity: 0.82;
852
+ filter: drop-shadow(0 0 6px var(--markdy-node-accent));
853
+ }
854
+ .markdy-arch-node__icon {
855
+ position: relative;
856
+ width: 32px;
857
+ height: 32px;
858
+ border-radius: 11px;
859
+ background:
860
+ radial-gradient(circle at 35% 25%, rgba(255, 255, 255, 0.28), transparent 42%),
861
+ linear-gradient(145deg, var(--markdy-node-accent), color-mix(in srgb, var(--markdy-node-accent) 38%, #020617));
862
+ box-shadow:
863
+ 0 0 0 1px rgba(255, 255, 255, 0.16) inset,
864
+ 0 10px 20px color-mix(in srgb, var(--markdy-node-accent) 25%, transparent);
865
+ }
866
+ .markdy-arch-node__icon::before,
867
+ .markdy-arch-node__icon::after {
868
+ content: "";
869
+ position: absolute;
870
+ box-sizing: border-box;
871
+ border-color: rgba(255, 255, 255, 0.9);
872
+ }
873
+ .markdy-arch-node__label {
874
+ min-width: 0;
875
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
876
+ font-size: 15px;
877
+ font-weight: 720;
878
+ line-height: 1.08;
879
+ color: #f8fafc;
880
+ text-shadow: 0 1px 12px rgba(15, 23, 42, 0.8);
881
+ white-space: nowrap;
882
+ overflow: hidden;
883
+ text-overflow: ellipsis;
884
+ }
885
+ .markdy-arch-node__type {
886
+ display: block;
887
+ margin-top: 5px;
888
+ font-size: 9px;
889
+ font-weight: 740;
890
+ letter-spacing: 0.08em;
891
+ text-transform: uppercase;
892
+ color: color-mix(in srgb, var(--markdy-node-accent) 70%, #e2e8f0);
893
+ opacity: 0.86;
894
+ }
895
+ .markdy-arch-node[data-markdy-system-type="service"],
896
+ .markdy-arch-node[data-markdy-system-type="api"],
897
+ .markdy-arch-node[data-markdy-system-type="microservice"] {
898
+ --markdy-node-accent: #38bdf8;
899
+ --markdy-node-accent-2: #818cf8;
900
+ }
901
+ .markdy-arch-node[data-markdy-system-type="client"],
902
+ .markdy-arch-node[data-markdy-system-type="user"] {
903
+ --markdy-node-accent: #f59e0b;
904
+ --markdy-node-accent-2: #fb7185;
905
+ }
906
+ .markdy-arch-node[data-markdy-system-type="database"],
907
+ .markdy-arch-node[data-markdy-system-type="db"] {
908
+ --markdy-node-accent: #22c55e;
909
+ --markdy-node-accent-2: #14b8a6;
910
+ border-radius: 18px 18px 24px 24px;
911
+ }
912
+ .markdy-arch-node[data-markdy-system-type="cache"] {
913
+ --markdy-node-accent: #a3e635;
914
+ --markdy-node-accent-2: #22c55e;
915
+ border-style: dashed;
916
+ }
917
+ .markdy-arch-node[data-markdy-system-type="queue"] {
918
+ --markdy-node-accent: #a78bfa;
919
+ --markdy-node-accent-2: #38bdf8;
920
+ }
921
+ .markdy-arch-node[data-markdy-system-type="cloud"],
922
+ .markdy-arch-node[data-markdy-system-type="region"] {
923
+ --markdy-node-accent: #60a5fa;
924
+ --markdy-node-accent-2: #67e8f9;
925
+ border-radius: 999px;
926
+ }
927
+ .markdy-arch-node[data-markdy-system-type="container"],
928
+ .markdy-arch-node[data-markdy-system-type="cluster"] {
929
+ --markdy-node-accent: #c084fc;
930
+ --markdy-node-accent-2: #f472b6;
931
+ border-radius: 10px;
932
+ }
933
+ .markdy-arch-node[data-markdy-system-type="service"] .markdy-arch-node__icon::before,
934
+ .markdy-arch-node[data-markdy-system-type="api"] .markdy-arch-node__icon::before,
935
+ .markdy-arch-node[data-markdy-system-type="microservice"] .markdy-arch-node__icon::before {
936
+ inset: 9px 7px;
937
+ border-top: 3px solid rgba(255, 255, 255, 0.92);
938
+ border-bottom: 3px solid rgba(255, 255, 255, 0.92);
939
+ }
940
+ .markdy-arch-node[data-markdy-system-type="service"] .markdy-arch-node__icon::after,
941
+ .markdy-arch-node[data-markdy-system-type="api"] .markdy-arch-node__icon::after,
942
+ .markdy-arch-node[data-markdy-system-type="microservice"] .markdy-arch-node__icon::after {
943
+ inset: 14px 7px auto;
944
+ border-top: 3px solid rgba(255, 255, 255, 0.92);
945
+ }
946
+ .markdy-arch-node[data-markdy-system-type="client"] .markdy-arch-node__icon::before,
947
+ .markdy-arch-node[data-markdy-system-type="user"] .markdy-arch-node__icon::before {
948
+ left: 8px;
949
+ right: 8px;
950
+ top: 8px;
951
+ height: 13px;
952
+ border: 2px solid rgba(255, 255, 255, 0.92);
953
+ border-radius: 3px;
954
+ }
955
+ .markdy-arch-node[data-markdy-system-type="client"] .markdy-arch-node__icon::after,
956
+ .markdy-arch-node[data-markdy-system-type="user"] .markdy-arch-node__icon::after {
957
+ left: 12px;
958
+ right: 12px;
959
+ bottom: 7px;
960
+ border-top: 2px solid rgba(255, 255, 255, 0.92);
961
+ }
962
+ .markdy-arch-node[data-markdy-system-type="database"] .markdy-arch-node__icon::before,
963
+ .markdy-arch-node[data-markdy-system-type="db"] .markdy-arch-node__icon::before,
964
+ .markdy-arch-node[data-markdy-system-type="cache"] .markdy-arch-node__icon::before {
965
+ inset: 7px 7px 9px;
966
+ border: 2px solid rgba(255, 255, 255, 0.92);
967
+ border-radius: 50% / 16%;
968
+ }
969
+ .markdy-arch-node[data-markdy-system-type="database"] .markdy-arch-node__icon::after,
970
+ .markdy-arch-node[data-markdy-system-type="db"] .markdy-arch-node__icon::after,
971
+ .markdy-arch-node[data-markdy-system-type="cache"] .markdy-arch-node__icon::after {
972
+ left: 8px;
973
+ right: 8px;
974
+ top: 12px;
975
+ border-top: 2px solid rgba(255, 255, 255, 0.72);
976
+ }
977
+ .markdy-arch-node[data-markdy-system-type="queue"] .markdy-arch-node__icon::before {
978
+ inset: 9px 8px;
979
+ border: 2px solid rgba(255, 255, 255, 0.92);
980
+ border-radius: 999px;
981
+ }
982
+ .markdy-arch-node[data-markdy-system-type="queue"] .markdy-arch-node__icon::after {
983
+ left: 13px;
984
+ top: 9px;
985
+ width: 8px;
986
+ height: 14px;
987
+ border-left: 2px solid rgba(255, 255, 255, 0.82);
988
+ border-right: 2px solid rgba(255, 255, 255, 0.82);
989
+ }
990
+ .markdy-arch-node[data-markdy-system-type="cloud"] .markdy-arch-node__icon::before,
991
+ .markdy-arch-node[data-markdy-system-type="region"] .markdy-arch-node__icon::before {
992
+ left: 7px;
993
+ right: 7px;
994
+ bottom: 9px;
995
+ height: 12px;
996
+ border: 2px solid rgba(255, 255, 255, 0.92);
997
+ border-radius: 999px;
998
+ }
999
+ .markdy-arch-node[data-markdy-system-type="cloud"] .markdy-arch-node__icon::after,
1000
+ .markdy-arch-node[data-markdy-system-type="region"] .markdy-arch-node__icon::after {
1001
+ left: 10px;
1002
+ top: 7px;
1003
+ width: 13px;
1004
+ height: 13px;
1005
+ border-top: 2px solid rgba(255, 255, 255, 0.92);
1006
+ border-left: 2px solid rgba(255, 255, 255, 0.92);
1007
+ border-radius: 999px 0 0 0;
1008
+ }
1009
+ .markdy-arch-node[data-markdy-system-type="container"] .markdy-arch-node__icon::before,
1010
+ .markdy-arch-node[data-markdy-system-type="cluster"] .markdy-arch-node__icon::before {
1011
+ inset: 8px;
1012
+ border: 2px solid rgba(255, 255, 255, 0.92);
1013
+ border-radius: 4px;
1014
+ }
1015
+ .markdy-arch-node[data-markdy-system-type="container"] .markdy-arch-node__icon::after,
1016
+ .markdy-arch-node[data-markdy-system-type="cluster"] .markdy-arch-node__icon::after {
1017
+ left: 10px;
1018
+ right: 10px;
1019
+ top: 14px;
1020
+ border-top: 2px solid rgba(255, 255, 255, 0.72);
1021
+ }
1022
+ `;
1023
+ doc.head.appendChild(style);
1024
+ }
1025
+ function isArchitectureNodeType(type) {
1026
+ return ARCHITECTURE_NODE_TYPES.has(type);
1027
+ }
1028
+ function isShowcaseSurfaceType(type) {
1029
+ return SHOWCASE_SURFACE_TYPES.has(type);
1030
+ }
1031
+ function architectureTypeLabel(type) {
1032
+ if (type === "db") return "database";
1033
+ if (type === "api") return "service";
1034
+ return type;
1035
+ }
312
1036
  function createActorEl(name, def, assetDefs, assetOverrides) {
313
1037
  let el;
314
1038
  switch (def.type) {
@@ -377,62 +1101,44 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
377
1101
  el = createFigureEl(def);
378
1102
  break;
379
1103
  }
1104
+ case "parking_map":
1105
+ case "ascii_map":
1106
+ case "game_scene":
1107
+ case "byte_viz": {
1108
+ el = createShowcaseSurfaceEl(def.type, def);
1109
+ break;
1110
+ }
380
1111
  case "service":
1112
+ case "api":
1113
+ case "microservice":
381
1114
  case "client":
1115
+ case "user":
382
1116
  case "db":
383
- case "queue": {
1117
+ case "database":
1118
+ case "queue":
1119
+ case "cache":
1120
+ case "cloud":
1121
+ case "region":
1122
+ case "container":
1123
+ case "cluster": {
1124
+ ensureArchitectureStyles(document);
384
1125
  const card = document.createElement("div");
1126
+ card.className = "markdy-arch-node";
1127
+ card.dataset.markdySystemType = def.type;
1128
+ card.setAttribute("role", "img");
1129
+ card.setAttribute("aria-label", `${architectureTypeLabel(def.type)} ${def.args[0] ?? name}`);
1130
+ const icon = document.createElement("span");
1131
+ icon.className = "markdy-arch-node__icon";
1132
+ icon.setAttribute("aria-hidden", "true");
385
1133
  const label = document.createElement("div");
1134
+ label.className = "markdy-arch-node__label";
386
1135
  label.textContent = def.args[0] ?? "";
387
- Object.assign(label.style, {
388
- fontFamily: "system-ui, -apple-system, sans-serif",
389
- fontSize: `${def.size ?? 16}px`,
390
- fontWeight: "600",
391
- color: "#e2e8f0",
392
- textAlign: "center",
393
- lineHeight: "1.2",
394
- pointerEvents: "none",
395
- whiteSpace: "nowrap",
396
- overflow: "hidden",
397
- textOverflow: "ellipsis",
398
- maxWidth: "100%"
399
- });
400
- Object.assign(card.style, {
401
- width: "180px",
402
- height: "84px",
403
- boxSizing: "border-box",
404
- border: "1px solid #475569",
405
- background: "#0f172a",
406
- display: "grid",
407
- placeItems: "center",
408
- padding: "10px 12px"
409
- });
410
- if (def.type === "service") {
411
- card.style.borderRadius = "12px";
412
- card.style.background = "linear-gradient(180deg, #1e293b 0%, #0f172a 100%)";
413
- } else if (def.type === "client") {
414
- const bar = document.createElement("div");
415
- Object.assign(bar.style, {
416
- position: "absolute",
417
- top: "8px",
418
- left: "8px",
419
- right: "8px",
420
- height: "8px",
421
- borderRadius: "6px",
422
- background: "#334155"
423
- });
424
- card.style.position = "relative";
425
- card.style.borderRadius = "10px";
426
- card.style.paddingTop = "20px";
427
- card.appendChild(bar);
428
- } else if (def.type === "db") {
429
- card.style.borderRadius = "50% / 14%";
430
- card.style.background = "linear-gradient(180deg, #334155 0%, #0f172a 70%)";
431
- card.style.boxShadow = "inset 0 10px 0 rgba(226, 232, 240, 0.12)";
432
- } else if (def.type === "queue") {
433
- card.style.borderRadius = "8px";
434
- card.style.boxShadow = "-6px -6px 0 rgba(30, 41, 59, 0.9), -12px -12px 0 rgba(15, 23, 42, 0.9)";
435
- }
1136
+ label.style.fontSize = `${def.size ?? 15}px`;
1137
+ const typeLabel = document.createElement("span");
1138
+ typeLabel.className = "markdy-arch-node__type";
1139
+ typeLabel.textContent = architectureTypeLabel(def.type);
1140
+ label.appendChild(typeLabel);
1141
+ card.appendChild(icon);
436
1142
  card.appendChild(label);
437
1143
  el = card;
438
1144
  break;
@@ -456,6 +1162,8 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
456
1162
  el.style.opacity = String(def.opacity ?? 1);
457
1163
  if (def.z !== void 0) el.style.zIndex = String(def.z);
458
1164
  else if (def.type === "caption") el.style.zIndex = "100";
1165
+ else if (isShowcaseSurfaceType(def.type)) el.style.zIndex = "25";
1166
+ else if (isArchitectureNodeType(def.type)) el.style.zIndex = "10";
459
1167
  return el;
460
1168
  }
461
1169
 
@@ -776,27 +1484,185 @@ var bounce = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
776
1484
  anims.push(el.animate(keyframes, { ...baseOpts, easing: "ease-out" }));
777
1485
  };
778
1486
 
1487
+ // src/actions/effects.ts
1488
+ function numeric(value, fallback) {
1489
+ return typeof value === "number" ? value : fallback;
1490
+ }
1491
+ function stringParam(value, fallback) {
1492
+ return typeof value === "string" && value.length > 0 ? value : fallback;
1493
+ }
1494
+ var spring = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
1495
+ const to = ev.params.to;
1496
+ const toX = to?.[0] ?? state.x;
1497
+ const toY = to?.[1] ?? state.y;
1498
+ const stiffness = numeric(ev.params.stiffness, 0.18);
1499
+ const overshootX = toX + (toX - state.x) * stiffness;
1500
+ const overshootY = toY + (toY - state.y) * stiffness;
1501
+ anims.push(
1502
+ el.animate(
1503
+ [
1504
+ { transform: tx2(state), offset: 0 },
1505
+ { transform: tx2({ ...state, x: overshootX, y: overshootY }), offset: 0.72 },
1506
+ { transform: tx2({ ...state, x: toX, y: toY }), offset: 1 }
1507
+ ],
1508
+ { ...baseOpts, easing: "cubic-bezier(.2,1.35,.35,1)" }
1509
+ )
1510
+ );
1511
+ state.x = toX;
1512
+ state.y = toY;
1513
+ };
1514
+ var followPath = ({ ev, el, baseOpts, anims }) => {
1515
+ const path = stringParam(ev.params.path, "M 0 0 L 120 0");
1516
+ const rotate2 = ev.params.rotate === false ? "0deg" : "auto";
1517
+ anims.push(
1518
+ el.animate(
1519
+ [
1520
+ { offsetPath: `path('${path}')`, offsetDistance: "0%", offsetRotate: rotate2 },
1521
+ { offsetPath: `path('${path}')`, offsetDistance: "100%", offsetRotate: rotate2 }
1522
+ ],
1523
+ baseOpts
1524
+ )
1525
+ );
1526
+ };
1527
+ var pulse = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
1528
+ const amount = numeric(ev.params.amount, 1.08);
1529
+ anims.push(
1530
+ el.animate(
1531
+ [
1532
+ { transform: tx2(state), offset: 0 },
1533
+ { transform: tx2({ ...state, scale: state.scale * amount }), offset: 0.48 },
1534
+ { transform: tx2(state), offset: 1 }
1535
+ ],
1536
+ { ...baseOpts, easing: "ease-in-out" }
1537
+ )
1538
+ );
1539
+ };
1540
+ var glow = ({ ev, el, baseOpts, anims }) => {
1541
+ const color = stringParam(ev.params.color, "#38bdf8");
1542
+ const strength = numeric(ev.params.strength, 24);
1543
+ anims.push(
1544
+ el.animate(
1545
+ [
1546
+ { filter: "drop-shadow(0 0 0 transparent)", boxShadow: "0 0 0 rgba(0,0,0,0)" },
1547
+ { filter: `drop-shadow(0 0 ${strength}px ${color})`, boxShadow: `0 0 ${strength}px ${color}` },
1548
+ { filter: "drop-shadow(0 0 0 transparent)", boxShadow: "0 0 0 rgba(0,0,0,0)" }
1549
+ ],
1550
+ { ...baseOpts, easing: "ease-in-out" }
1551
+ )
1552
+ );
1553
+ };
1554
+ var ripple = ({ ev, el, delayMs, durMs, anims }) => {
1555
+ const color = stringParam(ev.params.color, "#38bdf8");
1556
+ const size = numeric(ev.params.size, 140);
1557
+ const ring = document.createElement("span");
1558
+ ring.setAttribute("data-markdy-ripple", "1");
1559
+ Object.assign(ring.style, {
1560
+ position: "absolute",
1561
+ left: "50%",
1562
+ top: "50%",
1563
+ width: `${size}px`,
1564
+ height: `${size}px`,
1565
+ marginLeft: `${-size / 2}px`,
1566
+ marginTop: `${-size / 2}px`,
1567
+ border: `2px solid ${color}`,
1568
+ borderRadius: "999px",
1569
+ pointerEvents: "none",
1570
+ opacity: "0"
1571
+ });
1572
+ el.appendChild(ring);
1573
+ anims.push(
1574
+ ring.animate(
1575
+ [
1576
+ { transform: "scale(0.2)", opacity: 0.6 },
1577
+ { transform: "scale(1)", opacity: 0 }
1578
+ ],
1579
+ { delay: delayMs, duration: durMs, fill: "forwards", easing: "ease-out" }
1580
+ )
1581
+ );
1582
+ };
1583
+ var blur = ({ ev, el, baseOpts, anims }) => {
1584
+ const from = numeric(ev.params.from, 0);
1585
+ const to = numeric(ev.params.to, 8);
1586
+ anims.push(el.animate([{ filter: `blur(${from}px)` }, { filter: `blur(${to}px)` }], baseOpts));
1587
+ };
1588
+ var lineReveal = ({ ev, el, baseOpts, anims }) => {
1589
+ const from = stringParam(ev.params.from, "left");
1590
+ const start = from === "right" ? "inset(0 0 0 100%)" : "inset(0 100% 0 0)";
1591
+ anims.push(el.animate([{ clipPath: start }, { clipPath: "inset(0 0 0 0)" }], baseOpts));
1592
+ };
1593
+ var mask = ({ ev, el, baseOpts, anims }) => {
1594
+ const from = numeric(ev.params.from, 0);
1595
+ const to = numeric(ev.params.to, 120);
1596
+ anims.push(
1597
+ el.animate(
1598
+ [
1599
+ { clipPath: `circle(${from}% at 50% 50%)` },
1600
+ { clipPath: `circle(${to}% at 50% 50%)` }
1601
+ ],
1602
+ baseOpts
1603
+ )
1604
+ );
1605
+ };
1606
+ var parallax = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
1607
+ const depth = numeric(ev.params.depth, 0.35);
1608
+ const by = ev.params.by;
1609
+ const dx = (by?.[0] ?? 24) * depth;
1610
+ const dy = (by?.[1] ?? 0) * depth;
1611
+ anims.push(
1612
+ el.animate(
1613
+ [
1614
+ { transform: tx2(state) },
1615
+ { transform: tx2({ ...state, x: state.x + dx, y: state.y + dy }) }
1616
+ ],
1617
+ baseOpts
1618
+ )
1619
+ );
1620
+ };
1621
+
779
1622
  // src/geometry/rect.ts
780
1623
  var ACTOR_SIZES = {
781
- service: { width: 180, height: 84 },
782
- client: { width: 180, height: 84 },
783
- db: { width: 180, height: 84 },
784
- queue: { width: 180, height: 84 },
1624
+ service: { width: 184, height: 88 },
1625
+ api: { width: 184, height: 88 },
1626
+ microservice: { width: 184, height: 88 },
1627
+ client: { width: 184, height: 88 },
1628
+ user: { width: 184, height: 88 },
1629
+ db: { width: 184, height: 88 },
1630
+ database: { width: 184, height: 88 },
1631
+ queue: { width: 184, height: 88 },
1632
+ cache: { width: 184, height: 88 },
1633
+ cloud: { width: 184, height: 88 },
1634
+ region: { width: 184, height: 88 },
1635
+ container: { width: 184, height: 88 },
1636
+ cluster: { width: 184, height: 88 },
785
1637
  box: { width: 100, height: 100 },
786
1638
  caption: { width: 260, height: 56 },
787
- figure: { width: 120, height: 170 }
1639
+ figure: { width: 120, height: 170 },
1640
+ parking_map: { width: 390, height: 250 },
1641
+ ascii_map: { width: 390, height: 250 },
1642
+ game_scene: { width: 390, height: 250 },
1643
+ byte_viz: { width: 420, height: 250 }
788
1644
  };
789
1645
  var DEFAULT_ACTOR_SIZE = { width: 140, height: 42 };
790
1646
  function actorSizeByType(type) {
791
1647
  return ACTOR_SIZES[type] ?? DEFAULT_ACTOR_SIZE;
792
1648
  }
793
1649
  function actorCenter(state, actorType) {
794
- const { width, height } = actorSizeByType(actorType);
795
- return { x: state.x + width / 2, y: state.y + height / 2 };
1650
+ const rect = actorRect(state, actorType);
1651
+ return { x: rect.x1 + (rect.x2 - rect.x1) / 2, y: rect.y1 + (rect.y2 - rect.y1) / 2 };
796
1652
  }
797
1653
  function actorRect(state, actorType) {
798
1654
  const { width, height } = actorSizeByType(actorType);
799
- return { x1: state.x, y1: state.y, x2: state.x + width, y2: state.y + height };
1655
+ const scale2 = Math.max(1e-3, state.scale);
1656
+ const scaledWidth = width * scale2;
1657
+ const scaledHeight = height * scale2;
1658
+ const dx = (scaledWidth - width) / 2;
1659
+ const dy = (scaledHeight - height) / 2;
1660
+ return {
1661
+ x1: state.x - dx,
1662
+ y1: state.y - dy,
1663
+ x2: state.x + width + dx,
1664
+ y2: state.y + height + dy
1665
+ };
800
1666
  }
801
1667
  function inflateRect(rect, pad) {
802
1668
  return {
@@ -849,19 +1715,58 @@ function polylineLength(points) {
849
1715
  }
850
1716
  return Math.max(1, total);
851
1717
  }
852
- function pointAtDistance(points, dist) {
853
- let remain = Math.max(0, dist);
1718
+ function segmentLength(a, b) {
1719
+ return Math.hypot(b.x - a.x, b.y - a.y);
1720
+ }
1721
+ function labelPointForPath(points, lane) {
1722
+ if (points.length < 2) return points[0] ?? { x: 0, y: 0 };
1723
+ let bestIndex = 0;
1724
+ let bestLength = -1;
854
1725
  for (let i = 0; i < points.length - 1; i++) {
855
- const a = points[i];
856
- const b = points[i + 1];
857
- const seg = Math.hypot(b.x - a.x, b.y - a.y);
858
- if (remain <= seg || i === points.length - 2) {
859
- const t = seg <= 0 ? 0 : Math.min(1, remain / seg);
860
- return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
1726
+ const len = segmentLength(points[i], points[i + 1]);
1727
+ if (len > bestLength) {
1728
+ bestIndex = i;
1729
+ bestLength = len;
861
1730
  }
862
- remain -= seg;
863
1731
  }
864
- return points[0];
1732
+ const a = points[bestIndex];
1733
+ const b = points[bestIndex + 1];
1734
+ const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
1735
+ const offset = lane * 8;
1736
+ if (Math.abs(a.x - b.x) >= Math.abs(a.y - b.y)) {
1737
+ return { x: mid.x, y: mid.y - 10 - offset };
1738
+ }
1739
+ return { x: mid.x + 10 + offset, y: mid.y };
1740
+ }
1741
+ function clamp(n, min, max) {
1742
+ if (max < min) return n;
1743
+ return Math.min(max, Math.max(min, n));
1744
+ }
1745
+ function clampPointToScene(point, ast) {
1746
+ const pad = 14;
1747
+ return {
1748
+ x: round1(clamp(point.x, pad, ast.meta.width - pad)),
1749
+ y: round1(clamp(point.y, pad, ast.meta.height - pad))
1750
+ };
1751
+ }
1752
+ function routeLength(points) {
1753
+ let total = 0;
1754
+ for (let i = 0; i < points.length - 1; i++) total += segmentLength(points[i], points[i + 1]);
1755
+ return total;
1756
+ }
1757
+ function routeBends(points) {
1758
+ let bends = 0;
1759
+ for (let i = 1; i < points.length - 1; i++) {
1760
+ const prev = points[i - 1];
1761
+ const cur = points[i];
1762
+ const next = points[i + 1];
1763
+ const dx1 = Math.sign(cur.x - prev.x);
1764
+ const dy1 = Math.sign(cur.y - prev.y);
1765
+ const dx2 = Math.sign(next.x - cur.x);
1766
+ const dy2 = Math.sign(next.y - cur.y);
1767
+ if (dx1 !== dx2 || dy1 !== dy2) bends++;
1768
+ }
1769
+ return bends;
865
1770
  }
866
1771
  function routeFlowPath(sourceName, targetName, sourceState, targetState, states, ast, lane) {
867
1772
  const sourceType = ast.actors[sourceName]?.type ?? "box";
@@ -898,17 +1803,25 @@ function routeFlowPath(sourceName, targetName, sourceState, targetState, states,
898
1803
  [source, { x: source.x, y: topLane }, { x: target.x, y: topLane }, target],
899
1804
  [source, { x: source.x, y: bottomLane }, { x: target.x, y: bottomLane }, target]
900
1805
  );
1806
+ const minObstacleX = obstacles.length ? Math.min(...obstacles.map((o) => o.x1)) : Math.min(source.x, target.x);
1807
+ const maxObstacleX = obstacles.length ? Math.max(...obstacles.map((o) => o.x2)) : Math.max(source.x, target.x);
1808
+ const leftLane = Math.max(16, minObstacleX - 24 - lane * 12);
1809
+ const rightLane = Math.min(ast.meta.width - 16, maxObstacleX + 24 + lane * 12);
1810
+ candidates.push(
1811
+ [source, { x: leftLane, y: source.y }, { x: leftLane, y: target.y }, target],
1812
+ [source, { x: rightLane, y: source.y }, { x: rightLane, y: target.y }, target]
1813
+ );
901
1814
  let best = candidates[0];
902
- let bestHits = Number.POSITIVE_INFINITY;
1815
+ let bestScore = Number.POSITIVE_INFINITY;
903
1816
  for (const candidate of candidates) {
904
1817
  const hits = countPathIntersections(candidate, obstacles);
905
- if (hits < bestHits) {
1818
+ const score = hits * 1e5 + routeBends(candidate) * 800 + routeLength(candidate);
1819
+ if (score < bestScore) {
906
1820
  best = candidate;
907
- bestHits = hits;
908
- if (hits === 0) break;
1821
+ bestScore = score;
909
1822
  }
910
1823
  }
911
- return best;
1824
+ return best.map((point) => clampPointToScene(point, ast));
912
1825
  }
913
1826
 
914
1827
  // src/actions/flow.ts
@@ -938,6 +1851,41 @@ function ensureEdgeLayer(scene) {
938
1851
  scene.appendChild(svg);
939
1852
  return svg;
940
1853
  }
1854
+ function ensureEdgeDefs(svg) {
1855
+ if (svg.querySelector("defs[data-markdy-flow-defs='1']")) return;
1856
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
1857
+ defs.setAttribute("data-markdy-flow-defs", "1");
1858
+ const glow2 = document.createElementNS("http://www.w3.org/2000/svg", "filter");
1859
+ glow2.setAttribute("id", "markdy-flow-glow");
1860
+ glow2.setAttribute("x", "-40%");
1861
+ glow2.setAttribute("y", "-40%");
1862
+ glow2.setAttribute("width", "180%");
1863
+ glow2.setAttribute("height", "180%");
1864
+ const blur2 = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
1865
+ blur2.setAttribute("stdDeviation", "3");
1866
+ blur2.setAttribute("result", "coloredBlur");
1867
+ const merge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
1868
+ const glowNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
1869
+ glowNode.setAttribute("in", "coloredBlur");
1870
+ const sourceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
1871
+ sourceNode.setAttribute("in", "SourceGraphic");
1872
+ merge.append(glowNode, sourceNode);
1873
+ glow2.append(blur2, merge);
1874
+ const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
1875
+ arrow.setAttribute("id", "markdy-flow-arrow");
1876
+ arrow.setAttribute("viewBox", "0 0 10 10");
1877
+ arrow.setAttribute("refX", "8");
1878
+ arrow.setAttribute("refY", "5");
1879
+ arrow.setAttribute("markerWidth", "6");
1880
+ arrow.setAttribute("markerHeight", "6");
1881
+ arrow.setAttribute("orient", "auto-start-reverse");
1882
+ const arrowPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
1883
+ arrowPath.setAttribute("d", "M 1 1 L 9 5 L 1 9 z");
1884
+ arrowPath.setAttribute("fill", "context-stroke");
1885
+ arrow.appendChild(arrowPath);
1886
+ defs.append(glow2, arrow);
1887
+ svg.prepend(defs);
1888
+ }
941
1889
  function isDashed(ctx) {
942
1890
  const style = String(ctx.ev.params.style ?? "");
943
1891
  return style === "dashed" || style === "fire_and_forget" || ctx.ev.action === "response";
@@ -951,38 +1899,51 @@ function buildEdge(ctx, targetName) {
951
1899
  const length = polylineLength(points);
952
1900
  const pathD = toPathD(points);
953
1901
  const stroke = STROKE_BY_ACTION[ev.action] ?? DEFAULT_STROKE;
1902
+ const svg = ensureEdgeLayer(scene);
1903
+ ensureEdgeDefs(svg);
954
1904
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
955
1905
  group.setAttribute("data-markdy-flow-edge", "1");
956
1906
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
957
1907
  path.setAttribute("d", pathD);
958
1908
  path.setAttribute("fill", "none");
959
1909
  path.setAttribute("stroke", stroke);
960
- path.setAttribute("stroke-width", "2.5");
1910
+ path.setAttribute("stroke-width", "3");
1911
+ path.setAttribute("stroke-linecap", "round");
1912
+ path.setAttribute("stroke-linejoin", "round");
1913
+ path.setAttribute("marker-end", "url(#markdy-flow-arrow)");
1914
+ path.setAttribute("filter", "url(#markdy-flow-glow)");
961
1915
  path.setAttribute("data-markdy-flow-action", ev.action);
962
1916
  path.style.strokeDasharray = isDashed(ctx) ? "8 6" : `${length}`;
963
1917
  path.style.strokeDashoffset = `${length}`;
964
1918
  group.appendChild(path);
965
1919
  const marker = document.createElementNS("http://www.w3.org/2000/svg", "circle");
966
- marker.setAttribute("r", "3");
1920
+ marker.setAttribute("r", "4.5");
967
1921
  marker.setAttribute("fill", stroke);
1922
+ marker.setAttribute("filter", "url(#markdy-flow-glow)");
968
1923
  marker.style.offsetPath = `path('${pathD}')`;
969
1924
  marker.style.offsetDistance = "0%";
970
1925
  marker.style.opacity = "0";
971
1926
  group.appendChild(marker);
972
1927
  const labelText = String(ev.params.label ?? "");
973
1928
  if (labelText) {
974
- const midPoint = pointAtDistance(points, length * 0.5);
1929
+ const midPoint = labelPointForPath(points, lane);
975
1930
  const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
976
1931
  label.setAttribute("x", `${round1(midPoint.x)}`);
977
1932
  label.setAttribute("y", `${round1(midPoint.y - 8)}`);
978
1933
  label.setAttribute("text-anchor", "middle");
979
1934
  label.setAttribute("font-size", "12");
1935
+ label.setAttribute("font-weight", "700");
1936
+ label.setAttribute("paint-order", "stroke");
1937
+ label.setAttribute("stroke", "rgba(2, 6, 23, 0.88)");
1938
+ label.setAttribute("stroke-width", "5");
1939
+ label.setAttribute("stroke-linejoin", "round");
980
1940
  label.setAttribute("fill", "#cbd5e1");
1941
+ label.setAttribute("filter", "url(#markdy-flow-glow)");
981
1942
  label.setAttribute("data-full-label", labelText);
982
1943
  label.textContent = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
983
1944
  group.appendChild(label);
984
1945
  }
985
- ensureEdgeLayer(scene).appendChild(group);
1946
+ svg.appendChild(group);
986
1947
  anims.push(
987
1948
  path.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], baseOpts),
988
1949
  marker.animate(
@@ -1164,9 +2125,18 @@ var ACTION_HANDLERS = Object.freeze({
1164
2125
  exit,
1165
2126
  fade_in: fadeIn,
1166
2127
  fade_out: fadeOut,
2128
+ spring,
2129
+ follow_path: followPath,
1167
2130
  scale,
1168
2131
  rotate,
1169
2132
  shake,
2133
+ pulse,
2134
+ glow,
2135
+ ripple,
2136
+ blur,
2137
+ line_reveal: lineReveal,
2138
+ mask,
2139
+ parallax,
1170
2140
  jump,
1171
2141
  bounce,
1172
2142
  say,
@@ -1196,7 +2166,7 @@ var DEFAULT_EASE_BY_ACTION = {
1196
2166
  exit: "in",
1197
2167
  fade_out: "in"
1198
2168
  };
1199
- function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
2169
+ function buildAnimations(ast, actorEls, actorLayer, cameraLayer, assetOverrides, faceSwaps) {
1200
2170
  const anims = [];
1201
2171
  const states = /* @__PURE__ */ new Map();
1202
2172
  for (const [name, def] of Object.entries(ast.actors)) {
@@ -1222,7 +2192,7 @@ function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
1222
2192
  easing: toEasing(ev.params.ease ?? DEFAULT_EASE_BY_ACTION[ev.action])
1223
2193
  };
1224
2194
  if (ev.actor === "camera") {
1225
- buildCameraAction(ev, scene, ast, baseOpts, anims, cameraState);
2195
+ buildCameraAction(ev, cameraLayer, ast, baseOpts, anims, cameraState);
1226
2196
  continue;
1227
2197
  }
1228
2198
  const el = actorEls.get(ev.actor);
@@ -1239,7 +2209,7 @@ function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
1239
2209
  ast,
1240
2210
  states,
1241
2211
  actorEls,
1242
- scene,
2212
+ scene: actorLayer,
1243
2213
  assetOverrides,
1244
2214
  faceSwaps,
1245
2215
  anims,
@@ -1251,6 +2221,134 @@ function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
1251
2221
  }
1252
2222
 
1253
2223
  // src/player.ts
2224
+ var SCENE_STYLE_ID = "markdy-scene-ambience-styles";
2225
+ var SAFE_FRAME_PADDING = 28;
2226
+ function ensureSceneStyles(doc) {
2227
+ if (doc.getElementById(SCENE_STYLE_ID)) return;
2228
+ const style = doc.createElement("style");
2229
+ style.id = SCENE_STYLE_ID;
2230
+ style.textContent = `
2231
+ .markdy-scene-root {
2232
+ --markdy-scene-grid: rgba(148, 163, 184, 0.13);
2233
+ --markdy-scene-grid-strong: rgba(148, 163, 184, 0.2);
2234
+ --markdy-scene-vignette: rgba(2, 6, 23, 0.72);
2235
+ --markdy-scene-accent: rgba(56, 189, 248, 0.16);
2236
+ isolation: isolate;
2237
+ }
2238
+ .markdy-scene-root::before,
2239
+ .markdy-scene-root::after {
2240
+ content: "";
2241
+ position: absolute;
2242
+ inset: 0;
2243
+ pointer-events: none;
2244
+ }
2245
+ .markdy-scene-root::before {
2246
+ z-index: 0;
2247
+ background:
2248
+ linear-gradient(var(--markdy-scene-grid) 1px, transparent 1px) 0 0 / 32px 32px,
2249
+ linear-gradient(90deg, var(--markdy-scene-grid) 1px, transparent 1px) 0 0 / 32px 32px,
2250
+ linear-gradient(var(--markdy-scene-grid-strong) 1px, transparent 1px) 0 0 / 160px 160px,
2251
+ linear-gradient(90deg, var(--markdy-scene-grid-strong) 1px, transparent 1px) 0 0 / 160px 160px;
2252
+ mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
2253
+ opacity: 0.82;
2254
+ }
2255
+ .markdy-scene-root::after {
2256
+ z-index: 1;
2257
+ background:
2258
+ radial-gradient(ellipse at 50% 0%, var(--markdy-scene-accent), transparent 42%),
2259
+ linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.08) 58%, var(--markdy-scene-vignette) 100%),
2260
+ linear-gradient(90deg, rgba(255, 255, 255, 0.035), transparent 18%, transparent 82%, rgba(255, 255, 255, 0.035));
2261
+ mix-blend-mode: screen;
2262
+ opacity: 0.8;
2263
+ }
2264
+ .markdy-scene-root[data-markdy-scene-tone="light"] {
2265
+ --markdy-scene-grid: rgba(15, 23, 42, 0.09);
2266
+ --markdy-scene-grid-strong: rgba(15, 23, 42, 0.14);
2267
+ --markdy-scene-vignette: rgba(241, 245, 249, 0.74);
2268
+ --markdy-scene-accent: rgba(14, 165, 233, 0.12);
2269
+ }
2270
+ .markdy-scene-content {
2271
+ z-index: 2;
2272
+ }
2273
+ .markdy-scene-actor-layer {
2274
+ position: absolute;
2275
+ inset: 0;
2276
+ overflow: visible;
2277
+ transform-origin: 0 0;
2278
+ will-change: transform;
2279
+ }
2280
+ `;
2281
+ doc.head.appendChild(style);
2282
+ }
2283
+ function unionRect(a, b) {
2284
+ if (!a) return b;
2285
+ return {
2286
+ x1: Math.min(a.x1, b.x1),
2287
+ y1: Math.min(a.y1, b.y1),
2288
+ x2: Math.max(a.x2, b.x2),
2289
+ y2: Math.max(a.y2, b.y2)
2290
+ };
2291
+ }
2292
+ function numericPair(value) {
2293
+ if (!Array.isArray(value) || value.length < 2) return null;
2294
+ const [x, y] = value;
2295
+ return typeof x === "number" && typeof y === "number" ? [x, y] : null;
2296
+ }
2297
+ function computeContentBounds(ast) {
2298
+ let bounds = null;
2299
+ const maxScaleByActor = /* @__PURE__ */ new Map();
2300
+ const positionsByActor = /* @__PURE__ */ new Map();
2301
+ for (const [name, def] of Object.entries(ast.actors)) {
2302
+ maxScaleByActor.set(name, Math.max(1e-3, def.scale ?? 1));
2303
+ positionsByActor.set(name, [[def.x, def.y]]);
2304
+ }
2305
+ for (const ev of ast.events) {
2306
+ if (ev.actor === "camera") continue;
2307
+ if (ev.action === "scale" && typeof ev.params.to === "number") {
2308
+ maxScaleByActor.set(ev.actor, Math.max(maxScaleByActor.get(ev.actor) ?? 1, ev.params.to));
2309
+ }
2310
+ if (ev.action === "move" || ev.action === "spring") {
2311
+ const to = numericPair(ev.params.to);
2312
+ if (to) positionsByActor.get(ev.actor)?.push(to);
2313
+ }
2314
+ }
2315
+ for (const [name, def] of Object.entries(ast.actors)) {
2316
+ const positions = positionsByActor.get(name) ?? [[def.x, def.y]];
2317
+ const scale2 = maxScaleByActor.get(name) ?? def.scale ?? 1;
2318
+ for (const [x, y] of positions) {
2319
+ bounds = unionRect(bounds, actorRect({ ...stateFrom(def), x, y, scale: scale2 }, def.type));
2320
+ }
2321
+ }
2322
+ return bounds;
2323
+ }
2324
+ function computeSafeFrame(ast) {
2325
+ const bounds = computeContentBounds(ast);
2326
+ if (!bounds) return { x: 0, y: 0, scale: 1 };
2327
+ const boundsWidth = Math.max(1, bounds.x2 - bounds.x1);
2328
+ const boundsHeight = Math.max(1, bounds.y2 - bounds.y1);
2329
+ const availableWidth = Math.max(1, ast.meta.width - SAFE_FRAME_PADDING * 2);
2330
+ const availableHeight = Math.max(1, ast.meta.height - SAFE_FRAME_PADDING * 2);
2331
+ const scale2 = Math.min(1, availableWidth / boundsWidth, availableHeight / boundsHeight);
2332
+ const scaledWidth = boundsWidth * scale2;
2333
+ const scaledHeight = boundsHeight * scale2;
2334
+ function axisOffset(min, max, sceneSize, scaledSize) {
2335
+ const paddedMin = SAFE_FRAME_PADDING;
2336
+ const paddedMax = sceneSize - SAFE_FRAME_PADDING;
2337
+ const scaledMin = min * scale2;
2338
+ const scaledMax = max * scale2;
2339
+ if (scaledSize > paddedMax - paddedMin) return (sceneSize - scaledSize) / 2 - scaledMin;
2340
+ if (scaledMin < paddedMin) return paddedMin - scaledMin;
2341
+ if (scaledMax > paddedMax) return paddedMax - scaledMax;
2342
+ return 0;
2343
+ }
2344
+ const x = axisOffset(bounds.x1, bounds.x2, ast.meta.width, scaledWidth);
2345
+ const y = axisOffset(bounds.y1, bounds.y2, ast.meta.height, scaledHeight);
2346
+ return {
2347
+ x: Math.round(x * 1e3) / 1e3,
2348
+ y: Math.round(y * 1e3) / 1e3,
2349
+ scale: Math.round(scale2 * 1e3) / 1e3
2350
+ };
2351
+ }
1254
2352
  function bgToTextColor(bg) {
1255
2353
  let hex = bg.trim().replace(/^#/, "");
1256
2354
  if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
@@ -1350,6 +2448,9 @@ function createPlayer(opts) {
1350
2448
  }
1351
2449
  }
1352
2450
  const scene = document.createElement("div");
2451
+ ensureSceneStyles(document);
2452
+ scene.className = "markdy-scene-root";
2453
+ scene.dataset.markdySceneTone = bgToTextColor(ast.meta.bg) === "#1a1a1a" ? "light" : "dark";
1353
2454
  Object.assign(scene.style, {
1354
2455
  position: "absolute",
1355
2456
  top: "0",
@@ -1364,6 +2465,7 @@ function createPlayer(opts) {
1364
2465
  });
1365
2466
  viewport.appendChild(scene);
1366
2467
  const sceneContent = document.createElement("div");
2468
+ sceneContent.className = "markdy-scene-content";
1367
2469
  Object.assign(sceneContent.style, {
1368
2470
  position: "absolute",
1369
2471
  top: "0",
@@ -1374,6 +2476,12 @@ function createPlayer(opts) {
1374
2476
  willChange: "transform"
1375
2477
  });
1376
2478
  scene.appendChild(sceneContent);
2479
+ const actorLayer = document.createElement("div");
2480
+ actorLayer.className = "markdy-scene-actor-layer";
2481
+ const safeFrame = computeSafeFrame(ast);
2482
+ actorLayer.dataset.markdySafeFrame = `${safeFrame.x},${safeFrame.y},${safeFrame.scale}`;
2483
+ actorLayer.style.transform = `translate(${safeFrame.x}px, ${safeFrame.y}px) scale(${safeFrame.scale})`;
2484
+ sceneContent.appendChild(actorLayer);
1377
2485
  function scaleScene() {
1378
2486
  const s = viewport.clientWidth / ast.meta.width;
1379
2487
  scene.style.transform = `scale(${s})`;
@@ -1384,11 +2492,11 @@ function createPlayer(opts) {
1384
2492
  const actorEls = /* @__PURE__ */ new Map();
1385
2493
  for (const [name, def] of Object.entries(ast.actors)) {
1386
2494
  const el = createActorEl(name, def, ast.assets, assetOverrides);
1387
- sceneContent.appendChild(el);
2495
+ actorLayer.appendChild(el);
1388
2496
  actorEls.set(name, el);
1389
2497
  }
1390
2498
  const faceSwaps = [];
1391
- const allAnims = buildAnimations(ast, actorEls, sceneContent, assetOverrides, faceSwaps);
2499
+ const allAnims = buildAnimations(ast, actorEls, actorLayer, sceneContent, assetOverrides, faceSwaps);
1392
2500
  faceSwaps.sort((a, b) => a.timeMs - b.timeMs);
1393
2501
  for (const anim of allAnims) {
1394
2502
  anim.pause();