@markdy/renderer-dom 0.7.27 → 0.7.29

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 +522 -432
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -37,6 +37,9 @@ function toEasing(val) {
37
37
  return "linear";
38
38
  }
39
39
 
40
+ // src/actors.ts
41
+ import { TECHNICAL_NODE_KINDS, VISUAL_PRIMITIVE_TYPES } from "@markdy/core";
42
+
40
43
  // src/figure.ts
41
44
  function createFigureEl(def) {
42
45
  const skinColor = def.args[0] || "#ffdbac";
@@ -309,30 +312,20 @@ function readRotation(el) {
309
312
  }
310
313
 
311
314
  // 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"
315
+ var ARCHITECTURE_NODE_TYPES = new Set(Object.keys(TECHNICAL_NODE_KINDS));
316
+ var LEGACY_VISUAL_PRIMITIVE_TYPES = ["parking_map", "ascii_map", "game_scene", "byte_viz"];
317
+ var VISUAL_PRIMITIVE_TYPE_SET = /* @__PURE__ */ new Set([
318
+ ...VISUAL_PRIMITIVE_TYPES,
319
+ ...LEGACY_VISUAL_PRIMITIVE_TYPES
326
320
  ]);
327
- var SHOWCASE_SURFACE_TYPES = /* @__PURE__ */ new Set(["parking_map", "ascii_map", "game_scene", "byte_viz"]);
328
321
  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;
322
+ var VISUAL_PRIMITIVE_STYLE_ID = "markdy-visual-primitive-styles";
323
+ function ensureVisualPrimitiveStyles(doc) {
324
+ if (doc.getElementById(VISUAL_PRIMITIVE_STYLE_ID)) return;
332
325
  const style = doc.createElement("style");
333
- style.id = SHOWCASE_STYLE_ID;
326
+ style.id = VISUAL_PRIMITIVE_STYLE_ID;
334
327
  style.textContent = `
335
- .markdy-showcase {
328
+ .markdy-visual {
336
329
  --surface-a: rgba(15, 23, 42, 0.9);
337
330
  --accent: #38bdf8;
338
331
  --accent-2: #22c55e;
@@ -354,12 +347,17 @@ function ensureShowcaseStyles(doc) {
354
347
  0 1px 0 rgba(255, 255, 255, 0.18) inset,
355
348
  0 0 44px color-mix(in srgb, var(--accent) 24%, transparent);
356
349
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
350
+ line-height: 1.2;
357
351
  isolation: isolate;
358
352
  contain: layout paint style;
359
353
  backdrop-filter: blur(16px) saturate(1.2);
360
354
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
361
355
  }
362
- .markdy-showcase::before {
356
+ .markdy-visual,
357
+ .markdy-visual * {
358
+ box-sizing: border-box;
359
+ }
360
+ .markdy-visual::before {
363
361
  content: "";
364
362
  position: absolute;
365
363
  inset: 0;
@@ -371,7 +369,7 @@ function ensureShowcaseStyles(doc) {
371
369
  opacity: 0.48;
372
370
  animation: markdyGridDrift 7s linear infinite;
373
371
  }
374
- .markdy-showcase::after {
372
+ .markdy-visual::after {
375
373
  content: "";
376
374
  position: absolute;
377
375
  inset: -40% -20%;
@@ -380,15 +378,18 @@ function ensureShowcaseStyles(doc) {
380
378
  opacity: 0.6;
381
379
  animation: markdyAurora 9s linear infinite;
382
380
  }
383
- .markdy-showcase--byte_viz { width: 420px; }
384
- .markdy-showcase__top {
381
+ .markdy-visual[data-tone="green"] { --accent: #22c55e; --accent-2: #38bdf8; }
382
+ .markdy-visual[data-tone="amber"] { --accent: #f59e0b; --accent-2: #22c55e; }
383
+ .markdy-visual[data-tone="purple"] { --accent: #a78bfa; --accent-2: #38bdf8; }
384
+ .markdy-visual[data-tone="rose"] { --accent: #fb7185; --accent-2: #f59e0b; }
385
+ .markdy-visual__top {
385
386
  display: flex;
386
387
  align-items: center;
387
388
  justify-content: space-between;
388
389
  gap: 12px;
389
390
  padding: 14px 16px 8px;
390
391
  }
391
- .markdy-showcase__title {
392
+ .markdy-visual__title {
392
393
  min-width: 0;
393
394
  overflow: hidden;
394
395
  text-overflow: ellipsis;
@@ -398,7 +399,7 @@ function ensureShowcaseStyles(doc) {
398
399
  letter-spacing: 0.01em;
399
400
  color: #f8fafc;
400
401
  }
401
- .markdy-showcase__pill {
402
+ .markdy-visual__pill {
402
403
  flex: 0 0 auto;
403
404
  border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
404
405
  border-radius: 999px;
@@ -411,127 +412,95 @@ function ensureShowcaseStyles(doc) {
411
412
  text-transform: uppercase;
412
413
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 22%, transparent);
413
414
  }
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;
415
+ .markdy-visual__body { position: absolute; inset: 48px 14px 14px; }
416
+ .markdy-visual--metric {
417
+ width: 112px;
418
+ height: 44px;
419
+ border-radius: 14px;
420
+ }
421
+ .markdy-visual--grid {
422
+ width: 190px;
423
+ height: 92px;
424
+ border-radius: 18px;
425
+ }
426
+ .markdy-visual--lane {
427
+ width: 300px;
428
+ height: 44px;
422
429
  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
430
  }
428
- .parking-map__scan {
431
+ .markdy-visual--marker {
432
+ width: 50px;
433
+ height: 24px;
434
+ border-radius: 999px;
435
+ }
436
+ .markdy-visual--token_strip {
437
+ width: 300px;
438
+ height: 54px;
439
+ border-radius: 16px;
440
+ }
441
+ .markdy-visual--glyph_card {
442
+ width: 98px;
443
+ height: 120px;
444
+ border-radius: 22px;
445
+ }
446
+ .visual-panel__scan {
429
447
  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);
448
+ left: 18px;
449
+ top: 18px;
450
+ width: 92px;
451
+ height: 122px;
452
+ border-radius: 24px;
453
+ border: 1px solid color-mix(in srgb, var(--accent) 54%, transparent);
454
+ background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 22%, transparent), transparent);
455
+ box-shadow: 0 0 24px color-mix(in srgb, var(--accent) 28%, transparent);
438
456
  animation: markdyScanPulse 1.8s ease-in-out infinite;
439
457
  }
440
- .parking-map__car {
458
+ .visual-panel__rails {
441
459
  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: "";
460
+ left: 18px;
461
+ right: 18px;
462
+ bottom: 42px;
463
+ height: 48px;
464
+ border-radius: 999px;
465
+ background:
466
+ repeating-linear-gradient(90deg, rgba(226, 232, 240, 0.74) 0 18px, transparent 18px 34px) center / 100% 3px no-repeat,
467
+ linear-gradient(90deg, rgba(15, 23, 42, 0.96), rgba(30, 41, 59, 0.88));
468
+ box-shadow: 0 10px 28px rgba(2, 6, 23, 0.28) inset;
469
+ }
470
+ .visual-panel__pulse {
454
471
  position: absolute;
455
- bottom: -4px;
456
- width: 8px;
457
- height: 8px;
472
+ left: 110px;
473
+ right: 78px;
474
+ bottom: 64px;
475
+ height: 3px;
458
476
  border-radius: 999px;
459
- background: #020617;
460
- border: 2px solid #cbd5e1;
477
+ background: linear-gradient(90deg, transparent, var(--accent-2), var(--accent), transparent);
478
+ filter: drop-shadow(0 0 8px var(--accent-2));
479
+ animation: markdyRouteFlow 1.5s linear infinite;
461
480
  }
462
- .parking-map__car::before { left: 7px; }
463
- .parking-map__car::after { right: 7px; }
464
- .parking-map__slots {
481
+ .visual-panel__cells {
465
482
  position: absolute;
466
- right: 12px;
467
- top: 24px;
483
+ right: 18px;
484
+ top: 22px;
468
485
  display: grid;
469
- grid-template-columns: repeat(4, 44px);
470
- gap: 8px;
486
+ grid-template-columns: repeat(4, 42px);
487
+ gap: 7px;
471
488
  }
472
- .parking-map__slot {
473
- height: 34px;
489
+ .visual-panel__cell {
490
+ height: 30px;
474
491
  border-radius: 10px;
475
492
  border: 1px solid rgba(148, 163, 184, 0.34);
476
493
  background: rgba(15, 23, 42, 0.7);
477
494
  box-shadow: 0 0 0 1px rgba(255,255,255,0.035) inset;
478
495
  }
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);
496
+ .visual-panel__cell:nth-child(3),
497
+ .visual-panel__cell:nth-child(6),
498
+ .visual-panel__cell:nth-child(8) {
499
+ border-color: color-mix(in srgb, var(--accent-2) 78%, transparent);
500
+ background: color-mix(in srgb, var(--accent-2) 35%, rgba(15, 23, 42, 0.7));
484
501
  animation: markdySlotGlow 1.7s ease-in-out infinite;
485
502
  }
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 {
503
+ .visual-terminal__chrome {
535
504
  height: 28px;
536
505
  padding-left: 12px;
537
506
  display: flex;
@@ -539,256 +508,276 @@ function ensureShowcaseStyles(doc) {
539
508
  gap: 6px;
540
509
  border-bottom: 1px solid rgba(148, 163, 184, 0.16);
541
510
  }
542
- .ascii-map__chrome span {
511
+ .visual-terminal__chrome span {
543
512
  width: 8px;
544
513
  height: 8px;
545
514
  border-radius: 999px;
546
515
  background: #ef4444;
547
516
  box-shadow: 14px 0 #f59e0b, 28px 0 #22c55e;
548
517
  }
549
- .ascii-map__code {
518
+ .visual-terminal__lines {
550
519
  position: absolute;
551
520
  left: 16px;
521
+ right: 16px;
552
522
  top: 46px;
553
523
  font-family: "SFMono-Regular", Consolas, monospace;
554
- font-size: 19px;
524
+ font-size: 18px;
555
525
  line-height: 1.55;
556
- color: #bbf7d0;
557
- text-shadow: 0 0 12px rgba(34, 197, 94, 0.45);
526
+ color: color-mix(in srgb, var(--accent-2) 62%, #f8fafc);
527
+ text-shadow: 0 0 12px color-mix(in srgb, var(--accent-2) 38%, transparent);
558
528
  }
559
- .ascii-map__code b {
560
- color: #67e8f9;
561
- animation: markdyBlink 1.2s steps(2, end) infinite;
529
+ .visual-terminal__line {
530
+ overflow: hidden;
531
+ text-overflow: ellipsis;
532
+ white-space: nowrap;
562
533
  }
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;
534
+ .visual-metric {
535
+ height: 100%;
536
+ padding: 7px 9px;
537
+ display: flex;
538
+ align-items: center;
539
+ justify-content: center;
540
+ gap: 6px;
541
+ min-width: 0;
542
+ overflow: hidden;
543
+ white-space: nowrap;
572
544
  }
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);
545
+ .visual-metric strong {
546
+ flex: 0 0 auto;
547
+ color: #f8fafc;
548
+ font-size: 15px;
549
+ line-height: 1;
577
550
  }
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);
551
+ .visual-metric span {
552
+ min-width: 0;
553
+ overflow: hidden;
554
+ color: #94a3b8;
555
+ font-size: 8px;
556
+ font-weight: 840;
557
+ letter-spacing: 0.08em;
558
+ line-height: 1;
559
+ text-overflow: ellipsis;
560
+ text-transform: uppercase;
582
561
  }
583
- .ascii-map__transform {
562
+ .visual-grid {
584
563
  position: absolute;
585
- left: 16px;
586
- right: 16px;
587
- bottom: 10px;
564
+ inset: 10px;
588
565
  display: grid;
589
- grid-template-columns: 1fr 1fr 1fr;
590
- gap: 8px;
566
+ gap: 7px;
591
567
  }
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%);
568
+ .visual-grid__cell {
569
+ border-radius: 8px;
570
+ border: 1px solid rgba(148, 163, 184, 0.3);
571
+ background: rgba(15, 23, 42, 0.74);
601
572
  }
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;
573
+ .visual-grid__cell.is-active {
574
+ border-color: color-mix(in srgb, var(--accent-2) 78%, transparent);
575
+ background: color-mix(in srgb, var(--accent-2) 35%, rgba(15, 23, 42, 0.74));
576
+ box-shadow: 0 0 18px color-mix(in srgb, var(--accent-2) 26%, transparent);
608
577
  }
609
- .game-scene__road {
578
+ .visual-lane {
610
579
  position: absolute;
611
- left: -35px;
612
- right: -35px;
613
- top: 94px;
614
- height: 76px;
615
- transform: rotate(-8deg);
580
+ inset: 0;
581
+ border-radius: inherit;
616
582
  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));
583
+ repeating-linear-gradient(90deg, rgba(226, 232, 240, 0.76) 0 18px, transparent 18px 34px) center / 100% 3px no-repeat,
584
+ linear-gradient(90deg, rgba(15, 23, 42, 0.96), rgba(30, 41, 59, 0.88));
585
+ box-shadow: 0 10px 28px rgba(2, 6, 23, 0.28) inset, 0 0 18px color-mix(in srgb, var(--accent) 22%, transparent);
643
586
  }
644
- .game-scene__cones {
587
+ .visual-lane::after {
588
+ content: "";
645
589
  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));
590
+ left: 18%;
591
+ right: 18%;
592
+ top: 50%;
593
+ height: 3px;
594
+ border-radius: 999px;
595
+ transform: translateY(-50%);
596
+ background: linear-gradient(90deg, transparent, var(--accent-2), var(--accent), transparent);
597
+ filter: drop-shadow(0 0 8px var(--accent-2));
598
+ animation: markdyRouteFlow 1.5s linear infinite;
658
599
  }
659
- .game-scene__hud {
600
+ .visual-marker {
660
601
  position: absolute;
661
- left: 14px;
662
- top: 14px;
663
- min-width: 120px;
664
- border-color: rgba(245, 158, 11, 0.38);
602
+ inset: 0;
603
+ border-radius: inherit;
604
+ background: linear-gradient(90deg, var(--accent), #818cf8);
605
+ box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 55%, transparent), 0 8px 18px rgba(2, 6, 23, 0.35);
665
606
  }
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 {
607
+ .visual-marker::before,
608
+ .visual-marker::after {
609
+ content: "";
680
610
  position: absolute;
681
- inset: 4px;
682
- display: grid;
683
- grid-template-columns: 98px 1fr 122px;
684
- gap: 12px;
685
- align-items: stretch;
611
+ bottom: -3px;
612
+ width: 8px;
613
+ height: 8px;
614
+ border-radius: 999px;
615
+ background: #020617;
616
+ border: 2px solid #cbd5e1;
686
617
  }
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 {
618
+ .visual-marker::before { left: 9px; }
619
+ .visual-marker::after { right: 9px; }
620
+ .visual-tokens {
621
+ height: 100%;
622
+ padding: 8px;
700
623
  display: flex;
701
624
  align-items: center;
702
625
  gap: 7px;
703
626
  }
704
- .byte-viz__chip {
627
+ .visual-token {
705
628
  flex: 1 1 0;
629
+ min-width: 0;
706
630
  border: 1px solid rgba(148, 163, 184, 0.25);
707
631
  border-radius: 12px;
708
632
  padding: 8px 10px;
709
633
  background: rgba(2, 6, 23, 0.42);
710
- color: #ddd6fe;
634
+ color: color-mix(in srgb, var(--accent) 45%, #f8fafc);
711
635
  font-family: "SFMono-Regular", Consolas, monospace;
712
636
  font-size: 12px;
713
637
  font-weight: 800;
638
+ overflow: hidden;
639
+ text-overflow: ellipsis;
640
+ white-space: nowrap;
714
641
  }
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 {
642
+ .visual-glyph {
643
+ height: 100%;
726
644
  display: grid;
727
645
  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;
646
+ padding: 10px;
647
+ text-align: center;
648
+ }
649
+ .visual-glyph strong {
650
+ color: #f8fafc;
651
+ font-size: 56px;
652
+ line-height: 1;
653
+ }
654
+ .visual-glyph span {
655
+ margin-top: 6px;
656
+ color: #cbd5e1;
657
+ font-size: 10px;
658
+ font-weight: 800;
659
+ letter-spacing: 0.08em;
660
+ text-transform: uppercase;
737
661
  }
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
662
  @keyframes markdyGridDrift { to { background-position: 34px 34px, 34px 34px; } }
742
663
  @keyframes markdyAurora { to { transform: rotate(1turn); } }
743
664
  @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); } }
665
+ @keyframes markdySlotGlow { 50% { box-shadow: 0 0 20px color-mix(in srgb, var(--accent-2) 32%, transparent); } }
666
+ @keyframes markdyRouteFlow { to { filter: hue-rotate(70deg) drop-shadow(0 0 9px var(--accent)); } }
753
667
  `;
754
668
  doc.head.appendChild(style);
755
669
  }
756
- function createShowcaseSurfaceEl(type, def) {
757
- ensureShowcaseStyles(document);
758
- const label = def.args[0] || type.replace("_", " ");
670
+ function escapeHtml(value) {
671
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
672
+ }
673
+ function toneArg(def, index, fallback = "cyan") {
674
+ const tone = def.args[index]?.trim().toLowerCase() || fallback;
675
+ return /^(cyan|green|amber|purple|rose)$/.test(tone) ? tone : fallback;
676
+ }
677
+ function parseGridSpec(spec) {
678
+ const match = /^(\d+)x(\d+)$/i.exec(spec?.trim() ?? "");
679
+ const cols = match ? Number(match[1]) : 4;
680
+ const rows = match ? Number(match[2]) : 2;
681
+ return {
682
+ cols: Math.min(Math.max(cols, 1), 8),
683
+ rows: Math.min(Math.max(rows, 1), 4)
684
+ };
685
+ }
686
+ function activeCellSet(value) {
687
+ return new Set(
688
+ (value ?? "").split(/[|\s]+/).map((part) => Number(part.trim())).filter((part) => Number.isInteger(part) && part > 0)
689
+ );
690
+ }
691
+ function canonicalVisualType(type) {
692
+ switch (type) {
693
+ case "surface":
694
+ return "panel";
695
+ case "stat":
696
+ return "metric";
697
+ case "matrix":
698
+ return "grid";
699
+ case "track":
700
+ return "lane";
701
+ case "dot":
702
+ return "marker";
703
+ case "chips":
704
+ return "token_strip";
705
+ case "glyph":
706
+ return "glyph_card";
707
+ case "parking_map":
708
+ case "game_scene":
709
+ case "byte_viz":
710
+ return "panel";
711
+ case "ascii_map":
712
+ return "terminal";
713
+ default:
714
+ return type;
715
+ }
716
+ }
717
+ function legacyVisualArgs(type, args) {
718
+ const label = args[0] || type.replace("_", " ");
719
+ switch (type) {
720
+ case "parking_map":
721
+ return [label, "live ops", "cyan"];
722
+ case "ascii_map":
723
+ return [label, "[P1][P2][ ][EV]", "[IN]===LANE===[OUT]", "0101 0000 0100 0001", "green"];
724
+ case "game_scene":
725
+ return [label, "60 fps", "amber"];
726
+ case "byte_viz":
727
+ return [label, "UTF-8", "purple"];
728
+ default:
729
+ return args;
730
+ }
731
+ }
732
+ function createVisualPrimitiveEl(type, def) {
733
+ ensureVisualPrimitiveStyles(document);
734
+ const visualType = canonicalVisualType(type);
735
+ const args = legacyVisualArgs(type, def.args);
736
+ const label = args[0] || type.replace("_", " ");
759
737
  const root = document.createElement("div");
760
- root.className = `markdy-showcase markdy-showcase--${type}`;
738
+ root.className = `markdy-visual markdy-visual--${visualType}`;
739
+ root.dataset.visualType = visualType;
761
740
  root.setAttribute("role", "img");
762
741
  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") {
742
+ if (visualType === "panel") {
743
+ const tag = escapeHtml(args[1] || "live");
744
+ root.dataset.tone = toneArg({ ...def, args }, 2);
772
745
  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>
746
+ <div class="markdy-visual__top"><div class="markdy-visual__title">${escapeHtml(label)}</div><div class="markdy-visual__pill">${tag}</div></div>
747
+ <div class="markdy-visual__body">
748
+ <div class="visual-panel__scan"></div>
749
+ <div class="visual-panel__cells">${Array.from({ length: 8 }, () => '<span class="visual-panel__cell"></span>').join("")}</div>
750
+ <div class="visual-panel__rails"></div>
751
+ <div class="visual-panel__pulse"></div>
776
752
  </div>`;
777
- } else if (type === "game_scene") {
753
+ } else if (visualType === "terminal") {
754
+ root.dataset.tone = toneArg({ ...def, args }, 4, "green");
755
+ const lines = [args[1], args[2], args[3]].filter((line) => typeof line === "string" && line.length > 0).map((line) => `<div class="visual-terminal__line">${escapeHtml(line)}</div>`).join("");
778
756
  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>`;
757
+ <div class="markdy-visual__top"><div class="markdy-visual__title">${escapeHtml(label)}</div><div class="markdy-visual__pill">terminal</div></div>
758
+ <div class="markdy-visual__body"><div class="visual-terminal__chrome"><span></span></div><div class="visual-terminal__lines">${lines}</div></div>`;
759
+ } else if (visualType === "metric") {
760
+ root.dataset.tone = toneArg({ ...def, args }, 2);
761
+ root.innerHTML = `<div class="visual-metric"><strong>${escapeHtml(args[1] || "\u2014")}</strong><span>${escapeHtml(label)}</span></div>`;
762
+ } else if (visualType === "grid") {
763
+ root.dataset.tone = toneArg({ ...def, args }, 3, "green");
764
+ const { cols, rows } = parseGridSpec(args[1]);
765
+ const active = activeCellSet(args[2]);
766
+ root.innerHTML = `<div class="visual-grid" style="grid-template-columns: repeat(${cols}, 1fr);">${Array.from({ length: cols * rows }, (_, i) => `<span class="visual-grid__cell${active.has(i + 1) ? " is-active" : ""}"></span>`).join("")}</div>`;
767
+ } else if (visualType === "lane") {
768
+ root.dataset.tone = toneArg({ ...def, args }, 1);
769
+ root.innerHTML = `<div class="visual-lane"></div>`;
770
+ } else if (visualType === "marker") {
771
+ root.dataset.tone = toneArg({ ...def, args }, 1);
772
+ root.innerHTML = `<div class="visual-marker"></div>`;
773
+ } else if (visualType === "token_strip") {
774
+ root.dataset.tone = toneArg({ ...def, args }, 1, "purple");
775
+ const tokens = (args[0] || "token").split("|").map((token) => token.trim()).filter(Boolean);
776
+ root.innerHTML = `<div class="visual-tokens">${tokens.map((token) => `<span class="visual-token">${escapeHtml(token)}</span>`).join("")}</div>`;
783
777
  } 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>`;
778
+ root.dataset.tone = toneArg({ ...def, args }, 2, "purple");
779
+ root.innerHTML = `<div class="visual-glyph"><div><strong>${escapeHtml(args[0] || "A")}</strong><span>${escapeHtml(args[1] || "glyph")}</span></div></div>`;
789
780
  }
790
- const title = root.querySelector(".markdy-showcase__title");
791
- if (title) title.textContent = label;
792
781
  return root;
793
782
  }
794
783
  function ensureArchitectureStyles(doc) {
@@ -892,59 +881,75 @@ function ensureArchitectureStyles(doc) {
892
881
  color: color-mix(in srgb, var(--markdy-node-accent) 70%, #e2e8f0);
893
882
  opacity: 0.86;
894
883
  }
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"] {
884
+ .markdy-arch-node[data-markdy-system-kind="compute"],
885
+ .markdy-arch-node[data-markdy-system-kind="code"] {
898
886
  --markdy-node-accent: #38bdf8;
899
887
  --markdy-node-accent-2: #818cf8;
900
888
  }
901
- .markdy-arch-node[data-markdy-system-type="client"],
902
- .markdy-arch-node[data-markdy-system-type="user"] {
889
+ .markdy-arch-node[data-markdy-system-kind="client"] {
903
890
  --markdy-node-accent: #f59e0b;
904
891
  --markdy-node-accent-2: #fb7185;
905
892
  }
906
- .markdy-arch-node[data-markdy-system-type="database"],
907
- .markdy-arch-node[data-markdy-system-type="db"] {
893
+ .markdy-arch-node[data-markdy-system-kind="data"] {
908
894
  --markdy-node-accent: #22c55e;
909
895
  --markdy-node-accent-2: #14b8a6;
910
- border-radius: 18px 18px 24px 24px;
911
896
  }
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"] {
897
+ .markdy-arch-node[data-markdy-system-kind="messaging"] {
918
898
  --markdy-node-accent: #a78bfa;
919
899
  --markdy-node-accent-2: #38bdf8;
920
900
  }
921
- .markdy-arch-node[data-markdy-system-type="cloud"],
922
- .markdy-arch-node[data-markdy-system-type="region"] {
901
+ .markdy-arch-node[data-markdy-system-kind="network"] {
923
902
  --markdy-node-accent: #60a5fa;
924
903
  --markdy-node-accent-2: #67e8f9;
925
- border-radius: 999px;
926
904
  }
927
- .markdy-arch-node[data-markdy-system-type="container"],
928
- .markdy-arch-node[data-markdy-system-type="cluster"] {
905
+ .markdy-arch-node[data-markdy-system-kind="platform"] {
929
906
  --markdy-node-accent: #c084fc;
930
907
  --markdy-node-accent-2: #f472b6;
908
+ }
909
+ .markdy-arch-node[data-markdy-system-kind="security"] {
910
+ --markdy-node-accent: #fb7185;
911
+ --markdy-node-accent-2: #f59e0b;
912
+ }
913
+ .markdy-arch-node[data-markdy-system-kind="delivery"] {
914
+ --markdy-node-accent: #facc15;
915
+ --markdy-node-accent-2: #22c55e;
916
+ }
917
+ .markdy-arch-node[data-markdy-system-kind="observability"] {
918
+ --markdy-node-accent: #2dd4bf;
919
+ --markdy-node-accent-2: #38bdf8;
920
+ }
921
+ .markdy-arch-node[data-markdy-system-kind="flow"] {
922
+ --markdy-node-accent: #e879f9;
923
+ --markdy-node-accent-2: #a78bfa;
924
+ }
925
+ .markdy-arch-node[data-markdy-system-kind="distributed"] {
926
+ --markdy-node-accent: #94a3b8;
927
+ --markdy-node-accent-2: #38bdf8;
928
+ }
929
+ .markdy-arch-node[data-markdy-system-kind="data"] {
930
+ border-radius: 18px 18px 24px 24px;
931
+ }
932
+ .markdy-arch-node[data-markdy-system-kind="network"] {
933
+ border-radius: 999px;
934
+ }
935
+ .markdy-arch-node[data-markdy-system-kind="platform"] {
931
936
  border-radius: 10px;
932
937
  }
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 {
938
+ .markdy-arch-node[data-markdy-system-kind="security"] {
939
+ border-style: solid;
940
+ }
941
+ .markdy-arch-node[data-markdy-system-kind="compute"] .markdy-arch-node__icon::before,
942
+ .markdy-arch-node[data-markdy-system-kind="code"] .markdy-arch-node__icon::before {
936
943
  inset: 9px 7px;
937
944
  border-top: 3px solid rgba(255, 255, 255, 0.92);
938
945
  border-bottom: 3px solid rgba(255, 255, 255, 0.92);
939
946
  }
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 {
947
+ .markdy-arch-node[data-markdy-system-kind="compute"] .markdy-arch-node__icon::after,
948
+ .markdy-arch-node[data-markdy-system-kind="code"] .markdy-arch-node__icon::after {
943
949
  inset: 14px 7px auto;
944
950
  border-top: 3px solid rgba(255, 255, 255, 0.92);
945
951
  }
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 {
952
+ .markdy-arch-node[data-markdy-system-kind="client"] .markdy-arch-node__icon::before {
948
953
  left: 8px;
949
954
  right: 8px;
950
955
  top: 8px;
@@ -952,34 +957,29 @@ function ensureArchitectureStyles(doc) {
952
957
  border: 2px solid rgba(255, 255, 255, 0.92);
953
958
  border-radius: 3px;
954
959
  }
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 {
960
+ .markdy-arch-node[data-markdy-system-kind="client"] .markdy-arch-node__icon::after {
957
961
  left: 12px;
958
962
  right: 12px;
959
963
  bottom: 7px;
960
964
  border-top: 2px solid rgba(255, 255, 255, 0.92);
961
965
  }
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 {
966
+ .markdy-arch-node[data-markdy-system-kind="data"] .markdy-arch-node__icon::before {
965
967
  inset: 7px 7px 9px;
966
968
  border: 2px solid rgba(255, 255, 255, 0.92);
967
969
  border-radius: 50% / 16%;
968
970
  }
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 {
971
+ .markdy-arch-node[data-markdy-system-kind="data"] .markdy-arch-node__icon::after {
972
972
  left: 8px;
973
973
  right: 8px;
974
974
  top: 12px;
975
975
  border-top: 2px solid rgba(255, 255, 255, 0.72);
976
976
  }
977
- .markdy-arch-node[data-markdy-system-type="queue"] .markdy-arch-node__icon::before {
977
+ .markdy-arch-node[data-markdy-system-kind="messaging"] .markdy-arch-node__icon::before {
978
978
  inset: 9px 8px;
979
979
  border: 2px solid rgba(255, 255, 255, 0.92);
980
980
  border-radius: 999px;
981
981
  }
982
- .markdy-arch-node[data-markdy-system-type="queue"] .markdy-arch-node__icon::after {
982
+ .markdy-arch-node[data-markdy-system-kind="messaging"] .markdy-arch-node__icon::after {
983
983
  left: 13px;
984
984
  top: 9px;
985
985
  width: 8px;
@@ -987,8 +987,7 @@ function ensureArchitectureStyles(doc) {
987
987
  border-left: 2px solid rgba(255, 255, 255, 0.82);
988
988
  border-right: 2px solid rgba(255, 255, 255, 0.82);
989
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 {
990
+ .markdy-arch-node[data-markdy-system-kind="network"] .markdy-arch-node__icon::before {
992
991
  left: 7px;
993
992
  right: 7px;
994
993
  bottom: 9px;
@@ -996,8 +995,7 @@ function ensureArchitectureStyles(doc) {
996
995
  border: 2px solid rgba(255, 255, 255, 0.92);
997
996
  border-radius: 999px;
998
997
  }
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 {
998
+ .markdy-arch-node[data-markdy-system-kind="network"] .markdy-arch-node__icon::after {
1001
999
  left: 10px;
1002
1000
  top: 7px;
1003
1001
  width: 13px;
@@ -1006,33 +1004,74 @@ function ensureArchitectureStyles(doc) {
1006
1004
  border-left: 2px solid rgba(255, 255, 255, 0.92);
1007
1005
  border-radius: 999px 0 0 0;
1008
1006
  }
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 {
1007
+ .markdy-arch-node[data-markdy-system-kind="platform"] .markdy-arch-node__icon::before {
1011
1008
  inset: 8px;
1012
1009
  border: 2px solid rgba(255, 255, 255, 0.92);
1013
1010
  border-radius: 4px;
1014
1011
  }
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 {
1012
+ .markdy-arch-node[data-markdy-system-kind="platform"] .markdy-arch-node__icon::after {
1017
1013
  left: 10px;
1018
1014
  right: 10px;
1019
1015
  top: 14px;
1020
1016
  border-top: 2px solid rgba(255, 255, 255, 0.72);
1021
1017
  }
1018
+ .markdy-arch-node[data-markdy-system-kind="security"] .markdy-arch-node__icon::before {
1019
+ left: 9px;
1020
+ right: 9px;
1021
+ top: 7px;
1022
+ bottom: 7px;
1023
+ border: 2px solid rgba(255, 255, 255, 0.92);
1024
+ border-radius: 10px 10px 14px 14px;
1025
+ }
1026
+ .markdy-arch-node[data-markdy-system-kind="security"] .markdy-arch-node__icon::after {
1027
+ left: 14px;
1028
+ right: 14px;
1029
+ top: 14px;
1030
+ border-top: 2px solid rgba(255, 255, 255, 0.82);
1031
+ }
1032
+ .markdy-arch-node[data-markdy-system-kind="delivery"] .markdy-arch-node__icon::before,
1033
+ .markdy-arch-node[data-markdy-system-kind="observability"] .markdy-arch-node__icon::before,
1034
+ .markdy-arch-node[data-markdy-system-kind="distributed"] .markdy-arch-node__icon::before {
1035
+ inset: 8px;
1036
+ border: 2px solid rgba(255, 255, 255, 0.92);
1037
+ border-radius: 999px;
1038
+ }
1039
+ .markdy-arch-node[data-markdy-system-kind="delivery"] .markdy-arch-node__icon::after,
1040
+ .markdy-arch-node[data-markdy-system-kind="observability"] .markdy-arch-node__icon::after,
1041
+ .markdy-arch-node[data-markdy-system-kind="distributed"] .markdy-arch-node__icon::after {
1042
+ left: 15px;
1043
+ top: 7px;
1044
+ bottom: 7px;
1045
+ border-left: 2px solid rgba(255, 255, 255, 0.82);
1046
+ }
1047
+ .markdy-arch-node[data-markdy-system-kind="flow"] .markdy-arch-node__icon::before {
1048
+ inset: 8px;
1049
+ border: 2px solid rgba(255, 255, 255, 0.92);
1050
+ transform: rotate(45deg);
1051
+ }
1052
+ .markdy-arch-node[data-markdy-system-kind="flow"] .markdy-arch-node__icon::after {
1053
+ left: 14px;
1054
+ right: 14px;
1055
+ top: 15px;
1056
+ border-top: 2px solid rgba(255, 255, 255, 0.82);
1057
+ }
1022
1058
  `;
1023
1059
  doc.head.appendChild(style);
1024
1060
  }
1025
1061
  function isArchitectureNodeType(type) {
1026
1062
  return ARCHITECTURE_NODE_TYPES.has(type);
1027
1063
  }
1028
- function isShowcaseSurfaceType(type) {
1029
- return SHOWCASE_SURFACE_TYPES.has(type);
1064
+ function isVisualPrimitiveType(type) {
1065
+ return VISUAL_PRIMITIVE_TYPE_SET.has(type);
1030
1066
  }
1031
1067
  function architectureTypeLabel(type) {
1032
1068
  if (type === "db") return "database";
1033
1069
  if (type === "api") return "service";
1034
1070
  return type;
1035
1071
  }
1072
+ function architectureNodeKind(type) {
1073
+ return TECHNICAL_NODE_KINDS[type] ?? "compute";
1074
+ }
1036
1075
  function createActorEl(name, def, assetDefs, assetOverrides) {
1037
1076
  let el;
1038
1077
  switch (def.type) {
@@ -1101,30 +1140,25 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
1101
1140
  el = createFigureEl(def);
1102
1141
  break;
1103
1142
  }
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
- }
1111
- case "service":
1112
- case "api":
1113
- case "microservice":
1114
- case "client":
1115
- case "user":
1116
- case "db":
1117
- case "database":
1118
- case "queue":
1119
- case "cache":
1120
- case "cloud":
1121
- case "region":
1122
- case "container":
1123
- case "cluster": {
1143
+ default: {
1144
+ if (VISUAL_PRIMITIVE_TYPE_SET.has(def.type)) {
1145
+ el = createVisualPrimitiveEl(def.type, def);
1146
+ break;
1147
+ }
1148
+ if (!isArchitectureNodeType(def.type)) {
1149
+ const div = document.createElement("div");
1150
+ div.style.width = "100px";
1151
+ div.style.height = "100px";
1152
+ div.style.background = "#999";
1153
+ div.style.boxSizing = "border-box";
1154
+ el = div;
1155
+ break;
1156
+ }
1124
1157
  ensureArchitectureStyles(document);
1125
1158
  const card = document.createElement("div");
1126
1159
  card.className = "markdy-arch-node";
1127
1160
  card.dataset.markdySystemType = def.type;
1161
+ card.dataset.markdySystemKind = architectureNodeKind(def.type);
1128
1162
  card.setAttribute("role", "img");
1129
1163
  card.setAttribute("aria-label", `${architectureTypeLabel(def.type)} ${def.args[0] ?? name}`);
1130
1164
  const icon = document.createElement("span");
@@ -1143,15 +1177,6 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
1143
1177
  el = card;
1144
1178
  break;
1145
1179
  }
1146
- default: {
1147
- const div = document.createElement("div");
1148
- div.style.width = "100px";
1149
- div.style.height = "100px";
1150
- div.style.background = "#999";
1151
- div.style.boxSizing = "border-box";
1152
- el = div;
1153
- break;
1154
- }
1155
1180
  }
1156
1181
  el.dataset.markdyActor = name;
1157
1182
  el.style.position = "absolute";
@@ -1162,7 +1187,7 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
1162
1187
  el.style.opacity = String(def.opacity ?? 1);
1163
1188
  if (def.z !== void 0) el.style.zIndex = String(def.z);
1164
1189
  else if (def.type === "caption") el.style.zIndex = "100";
1165
- else if (isShowcaseSurfaceType(def.type)) el.style.zIndex = "25";
1190
+ else if (isVisualPrimitiveType(def.type)) el.style.zIndex = "25";
1166
1191
  else if (isArchitectureNodeType(def.type)) el.style.zIndex = "10";
1167
1192
  return el;
1168
1193
  }
@@ -1620,27 +1645,32 @@ var parallax = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
1620
1645
  };
1621
1646
 
1622
1647
  // src/geometry/rect.ts
1648
+ import { TECHNICAL_NODE_TYPES } from "@markdy/core";
1649
+ var SYSTEM_NODE_SIZE = { width: 184, height: 88 };
1623
1650
  var ACTOR_SIZES = {
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 },
1651
+ ...Object.fromEntries(TECHNICAL_NODE_TYPES.map((type) => [type, SYSTEM_NODE_SIZE])),
1637
1652
  box: { width: 100, height: 100 },
1638
1653
  caption: { width: 260, height: 56 },
1639
1654
  figure: { width: 120, height: 170 },
1655
+ panel: { width: 390, height: 250 },
1656
+ surface: { width: 390, height: 250 },
1657
+ terminal: { width: 390, height: 250 },
1658
+ metric: { width: 112, height: 44 },
1659
+ stat: { width: 112, height: 44 },
1660
+ grid: { width: 190, height: 92 },
1661
+ matrix: { width: 190, height: 92 },
1662
+ lane: { width: 300, height: 44 },
1663
+ track: { width: 300, height: 44 },
1664
+ marker: { width: 50, height: 24 },
1665
+ dot: { width: 50, height: 24 },
1666
+ token_strip: { width: 300, height: 54 },
1667
+ chips: { width: 300, height: 54 },
1668
+ glyph_card: { width: 98, height: 120 },
1669
+ glyph: { width: 98, height: 120 },
1640
1670
  parking_map: { width: 390, height: 250 },
1641
1671
  ascii_map: { width: 390, height: 250 },
1642
1672
  game_scene: { width: 390, height: 250 },
1643
- byte_viz: { width: 420, height: 250 }
1673
+ byte_viz: { width: 390, height: 250 }
1644
1674
  };
1645
1675
  var DEFAULT_ACTOR_SIZE = { width: 140, height: 42 };
1646
1676
  function actorSizeByType(type) {
@@ -1718,25 +1748,59 @@ function polylineLength(points) {
1718
1748
  function segmentLength(a, b) {
1719
1749
  return Math.hypot(b.x - a.x, b.y - a.y);
1720
1750
  }
1721
- function labelPointForPath(points, lane) {
1722
- if (points.length < 2) return points[0] ?? { x: 0, y: 0 };
1751
+ function rectsOverlap(a, b) {
1752
+ return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
1753
+ }
1754
+ function overlapCount(rect, obstacles) {
1755
+ let hits = 0;
1756
+ for (const o of obstacles) if (rectsOverlap(rect, o)) hits++;
1757
+ return hits;
1758
+ }
1759
+ var LABEL_BOX_HEIGHT = 18;
1760
+ function placeFlowLabel(points, textWidth, obstacles, ast) {
1723
1761
  let bestIndex = 0;
1724
1762
  let bestLength = -1;
1725
1763
  for (let i = 0; i < points.length - 1; i++) {
1726
1764
  const len = segmentLength(points[i], points[i + 1]);
1727
1765
  if (len > bestLength) {
1728
- bestIndex = i;
1729
1766
  bestLength = len;
1767
+ bestIndex = i;
1730
1768
  }
1731
1769
  }
1732
- const a = points[bestIndex];
1733
- const b = points[bestIndex + 1];
1770
+ const a = points[bestIndex] ?? { x: 0, y: 0 };
1771
+ const b = points[bestIndex + 1] ?? a;
1734
1772
  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 };
1773
+ const horizontal = Math.abs(a.x - b.x) >= Math.abs(a.y - b.y);
1774
+ const half = textWidth / 2;
1775
+ const halfH = LABEL_BOX_HEIGHT / 2;
1776
+ const pad = 8;
1777
+ const base = horizontal ? 15 : half + 12;
1778
+ const step = horizontal ? LABEL_BOX_HEIGHT + 3 : textWidth + 12;
1779
+ const order = horizontal ? [-1, 1] : [1, -1];
1780
+ const offsets = [];
1781
+ for (let k = 0; k < 7; k++) {
1782
+ for (const sign of order) offsets.push(sign * (base + k * step));
1783
+ }
1784
+ let fallback = null;
1785
+ let fallbackHits = Number.POSITIVE_INFINITY;
1786
+ for (const off of offsets) {
1787
+ let cx = horizontal ? mid.x : mid.x + off;
1788
+ let cy = horizontal ? mid.y + off : mid.y;
1789
+ cx = clamp(cx, pad + half, ast.meta.width - pad - half);
1790
+ cy = clamp(cy, pad + halfH, ast.meta.height - pad - halfH);
1791
+ const rect = { x1: cx - half, y1: cy - halfH, x2: cx + half, y2: cy + halfH };
1792
+ const hits = overlapCount(rect, obstacles);
1793
+ if (hits === 0) return { x: round1(cx), y: round1(cy), rect };
1794
+ if (hits < fallbackHits) {
1795
+ fallbackHits = hits;
1796
+ fallback = { x: round1(cx), y: round1(cy), rect };
1797
+ }
1738
1798
  }
1739
- return { x: mid.x + 10 + offset, y: mid.y };
1799
+ return fallback ?? {
1800
+ x: round1(mid.x),
1801
+ y: round1(mid.y),
1802
+ rect: { x1: mid.x - half, y1: mid.y - halfH, x2: mid.x + half, y2: mid.y + halfH }
1803
+ };
1740
1804
  }
1741
1805
  function clamp(n, min, max) {
1742
1806
  if (max < min) return n;
@@ -1833,7 +1897,9 @@ var STROKE_BY_ACTION = {
1833
1897
  };
1834
1898
  var DEFAULT_STROKE = "#38bdf8";
1835
1899
  var LABEL_MAX_CHARS = 28;
1836
- var EDGE_FADE_MS = 140;
1900
+ var LABEL_CHAR_PX = 6.8;
1901
+ var EDGE_REVEAL_MS = 220;
1902
+ var LABEL_RECTS = /* @__PURE__ */ new WeakMap();
1837
1903
  function ensureEdgeLayer(scene) {
1838
1904
  const existing = scene.querySelector(`svg[${EDGE_LAYER_ATTR}='1']`);
1839
1905
  if (existing) return existing;
@@ -1890,6 +1956,12 @@ function isDashed(ctx) {
1890
1956
  const style = String(ctx.ev.params.style ?? "");
1891
1957
  return style === "dashed" || style === "fire_and_forget" || ctx.ev.action === "response";
1892
1958
  }
1959
+ function nodeObstacleRect(state, type, sceneWidth) {
1960
+ const rect = actorRect(state, type);
1961
+ if (type !== "caption") return rect;
1962
+ const halfH = (rect.y2 - rect.y1) / 2 + 4;
1963
+ return { x1: 0, y1: state.y - halfH, x2: sceneWidth, y2: state.y + halfH };
1964
+ }
1893
1965
  function buildEdge(ctx, targetName) {
1894
1966
  const { ev, state, states, ast, scene, baseOpts, anims } = ctx;
1895
1967
  const targetState = states.get(targetName);
@@ -1901,8 +1973,12 @@ function buildEdge(ctx, targetName) {
1901
1973
  const stroke = STROKE_BY_ACTION[ev.action] ?? DEFAULT_STROKE;
1902
1974
  const svg = ensureEdgeLayer(scene);
1903
1975
  ensureEdgeDefs(svg);
1976
+ const startMs = Number(baseOpts.delay ?? 0);
1977
+ const durationMs = Math.max(1, Number(baseOpts.duration ?? 500));
1978
+ const revealMs = Math.min(EDGE_REVEAL_MS, durationMs);
1904
1979
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
1905
1980
  group.setAttribute("data-markdy-flow-edge", "1");
1981
+ group.style.opacity = "0";
1906
1982
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
1907
1983
  path.setAttribute("d", pathD);
1908
1984
  path.setAttribute("fill", "none");
@@ -1926,37 +2002,51 @@ function buildEdge(ctx, targetName) {
1926
2002
  group.appendChild(marker);
1927
2003
  const labelText = String(ev.params.label ?? "");
1928
2004
  if (labelText) {
1929
- const midPoint = labelPointForPath(points, lane);
2005
+ const shown = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
2006
+ const textWidth = shown.length * LABEL_CHAR_PX + 12;
2007
+ const placed = LABEL_RECTS.get(svg) ?? (LABEL_RECTS.set(svg, []), LABEL_RECTS.get(svg));
2008
+ const obstacles = [...placed];
2009
+ for (const [name, nodeState] of states.entries()) {
2010
+ const type = ast.actors[name]?.type ?? "box";
2011
+ obstacles.push(inflateRect(nodeObstacleRect(nodeState, type, ast.meta.width), 2));
2012
+ }
2013
+ const spot = placeFlowLabel(points, textWidth, obstacles, ast);
2014
+ placed.push(spot.rect);
1930
2015
  const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
1931
- label.setAttribute("x", `${round1(midPoint.x)}`);
1932
- label.setAttribute("y", `${round1(midPoint.y - 8)}`);
2016
+ label.setAttribute("x", `${spot.x}`);
2017
+ label.setAttribute("y", `${spot.y}`);
1933
2018
  label.setAttribute("text-anchor", "middle");
2019
+ label.setAttribute("dominant-baseline", "middle");
1934
2020
  label.setAttribute("font-size", "12");
1935
2021
  label.setAttribute("font-weight", "700");
1936
2022
  label.setAttribute("paint-order", "stroke");
1937
- label.setAttribute("stroke", "rgba(2, 6, 23, 0.88)");
2023
+ label.setAttribute("stroke", "rgba(2, 6, 23, 0.9)");
1938
2024
  label.setAttribute("stroke-width", "5");
1939
2025
  label.setAttribute("stroke-linejoin", "round");
1940
- label.setAttribute("fill", "#cbd5e1");
1941
- label.setAttribute("filter", "url(#markdy-flow-glow)");
2026
+ label.setAttribute("fill", "#dbe4f0");
1942
2027
  label.setAttribute("data-full-label", labelText);
1943
- label.textContent = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
2028
+ label.textContent = shown;
1944
2029
  group.appendChild(label);
1945
2030
  }
1946
2031
  svg.appendChild(group);
1947
2032
  anims.push(
1948
2033
  path.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], baseOpts),
2034
+ // Travel the dot along the edge, then fade it at arrival so the finished
2035
+ // edge is a clean line + arrowhead rather than a dot resting on the tip.
1949
2036
  marker.animate(
1950
2037
  [
1951
- { offsetDistance: "0%", opacity: 1 },
1952
- { offsetDistance: "100%", opacity: 1 }
2038
+ { offsetDistance: "0%", opacity: 1, offset: 0 },
2039
+ { offsetDistance: "100%", opacity: 1, offset: 0.82 },
2040
+ { offsetDistance: "100%", opacity: 0, offset: 1 }
1953
2041
  ],
1954
2042
  baseOpts
1955
2043
  ),
1956
- group.animate([{ opacity: 1 }, { opacity: 0 }], {
1957
- delay: Number(baseOpts.delay ?? 0) + Number(baseOpts.duration ?? 0),
1958
- duration: EDGE_FADE_MS,
1959
- fill: "forwards"
2044
+ // Reveal the edge at its scheduled time and keep it (persist). `fill: both`
2045
+ // holds opacity 0 during the delay, so nothing appears before its turn.
2046
+ group.animate([{ opacity: 0 }, { opacity: 1 }], {
2047
+ delay: startMs,
2048
+ duration: revealMs,
2049
+ fill: "both"
1960
2050
  })
1961
2051
  );
1962
2052
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.7.27",
3
+ "version": "0.7.29",
4
4
  "description": "Web Animations API renderer for MarkdyScript scenes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,14 +43,14 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@markdy/core": "0.7.27"
46
+ "@markdy/core": "0.7.29"
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.7.27"
53
+ "@markdy/stdlib-systems": "0.7.29"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsup",