@markdy/renderer-dom 0.7.25 → 0.7.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/dist/index.js +592 -659
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -459,79 +459,69 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
|
|
|
459
459
|
return el;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
// src/
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
fade_in: "out",
|
|
466
|
-
exit: "in",
|
|
467
|
-
fade_out: "in"
|
|
468
|
-
};
|
|
469
|
-
function isSceneDark(scene) {
|
|
470
|
-
const bg = scene.style.background || "white";
|
|
471
|
-
let hex = bg.trim().replace(/^#/, "");
|
|
472
|
-
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
473
|
-
if (hex.length === 6) {
|
|
474
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
475
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
476
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
477
|
-
return 0.299 * r + 0.587 * g + 0.114 * b <= 140;
|
|
478
|
-
}
|
|
479
|
-
const dark = { black: true, "#000": true, "#000000": true };
|
|
480
|
-
return dark[bg.toLowerCase()] ?? false;
|
|
462
|
+
// src/camera.ts
|
|
463
|
+
function freshCameraState() {
|
|
464
|
+
return { x: 0, y: 0, zoom: 1 };
|
|
481
465
|
}
|
|
482
|
-
function
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const easing = toEasing(ev.params.ease ?? DEFAULT_EASE_BY_ACTION[ev.action]);
|
|
503
|
-
const baseOpts = {
|
|
504
|
-
delay: delayMs,
|
|
505
|
-
duration: durMs,
|
|
506
|
-
fill: "forwards",
|
|
507
|
-
easing
|
|
508
|
-
};
|
|
509
|
-
if (ev.actor === "camera") {
|
|
510
|
-
buildCameraAction(ev, scene, ast, baseOpts, anims, cameraState);
|
|
511
|
-
continue;
|
|
466
|
+
function cameraTx(s) {
|
|
467
|
+
return `translate(${-s.x}px, ${-s.y}px) scale(${s.zoom})`;
|
|
468
|
+
}
|
|
469
|
+
var DEFAULT_SHAKE_INTENSITY = 8;
|
|
470
|
+
function buildCameraAction(ev, scene, ast, baseOpts, anims, state) {
|
|
471
|
+
switch (ev.action) {
|
|
472
|
+
case "pan": {
|
|
473
|
+
const to = ev.params.to;
|
|
474
|
+
if (!to) break;
|
|
475
|
+
const next = {
|
|
476
|
+
...state,
|
|
477
|
+
x: to[0] - ast.meta.width / 2,
|
|
478
|
+
y: to[1] - ast.meta.height / 2
|
|
479
|
+
};
|
|
480
|
+
anims.push(
|
|
481
|
+
scene.animate([{ transform: cameraTx(state) }, { transform: cameraTx(next) }], baseOpts)
|
|
482
|
+
);
|
|
483
|
+
state.x = next.x;
|
|
484
|
+
state.y = next.y;
|
|
485
|
+
break;
|
|
512
486
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
487
|
+
case "zoom": {
|
|
488
|
+
const next = {
|
|
489
|
+
...state,
|
|
490
|
+
zoom: typeof ev.params.to === "number" ? ev.params.to : state.zoom
|
|
491
|
+
};
|
|
492
|
+
anims.push(
|
|
493
|
+
scene.animate([{ transform: cameraTx(state) }, { transform: cameraTx(next) }], baseOpts)
|
|
494
|
+
);
|
|
495
|
+
state.zoom = next.zoom;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case "shake": {
|
|
499
|
+
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : DEFAULT_SHAKE_INTENSITY;
|
|
500
|
+
const at = (dx, dy, offset) => ({
|
|
501
|
+
transform: cameraTx({ ...state, x: state.x + dx, y: state.y + dy }),
|
|
502
|
+
offset
|
|
503
|
+
});
|
|
504
|
+
anims.push(
|
|
505
|
+
scene.animate(
|
|
506
|
+
[
|
|
507
|
+
at(0, 0, 0),
|
|
508
|
+
at(-mag, -mag * 0.4, 0.15),
|
|
509
|
+
at(mag, mag * 0.4, 0.35),
|
|
510
|
+
at(-mag * 0.6, mag * 0.3, 0.55),
|
|
511
|
+
at(mag * 0.5, -mag * 0.3, 0.75),
|
|
512
|
+
at(0, 0, 1)
|
|
513
|
+
],
|
|
514
|
+
{ ...baseOpts, easing: "linear" }
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
default:
|
|
520
|
+
break;
|
|
532
521
|
}
|
|
533
|
-
return anims;
|
|
534
522
|
}
|
|
523
|
+
|
|
524
|
+
// src/stage.ts
|
|
535
525
|
function offscreenState(s, direction, sceneWidth, sceneHeight) {
|
|
536
526
|
const out = { ...s };
|
|
537
527
|
switch (direction) {
|
|
@@ -561,114 +551,252 @@ function preInitInlineStyles(ast, actorEls, states, events, txFor) {
|
|
|
561
551
|
const s = states.get(name);
|
|
562
552
|
if (!el || !s) continue;
|
|
563
553
|
const firstEv = firstEventByActor.get(name);
|
|
564
|
-
|
|
565
|
-
if (firstEv
|
|
554
|
+
if (!firstEv) continue;
|
|
555
|
+
if (firstEv.action === "enter") {
|
|
566
556
|
const from = String(firstEv.params.from ?? "left");
|
|
567
|
-
|
|
568
|
-
el.style.transform = txFn(offscreen);
|
|
557
|
+
el.style.transform = txFor(name)(offscreenState(s, from, ast.meta.width, ast.meta.height));
|
|
569
558
|
}
|
|
570
|
-
if (firstEv
|
|
559
|
+
if (firstEv.action === "fade_in" && (def.opacity === void 0 || def.opacity > 0)) {
|
|
571
560
|
el.style.opacity = "0";
|
|
572
561
|
}
|
|
573
562
|
}
|
|
574
563
|
}
|
|
575
|
-
|
|
576
|
-
|
|
564
|
+
|
|
565
|
+
// src/actions/figure.ts
|
|
566
|
+
function sideOf(ctx) {
|
|
567
|
+
return String(ctx.ev.params.side ?? "right") === "left" ? "left" : "right";
|
|
577
568
|
}
|
|
578
|
-
function
|
|
579
|
-
|
|
569
|
+
function partEl(ctx, part) {
|
|
570
|
+
const selector = PART_SEL[part];
|
|
571
|
+
return selector ? ctx.el.querySelector(selector) : null;
|
|
580
572
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
573
|
+
function rotateKeyframe(deg, offset) {
|
|
574
|
+
return offset === void 0 ? { transform: `rotate(${deg}deg)` } : { transform: `rotate(${deg}deg)`, offset };
|
|
575
|
+
}
|
|
576
|
+
function swingLimb(ctx, part, extendDeg, peakOffset) {
|
|
577
|
+
const el = partEl(ctx, part);
|
|
578
|
+
if (!el) return;
|
|
579
|
+
const rest = readRotation(el);
|
|
580
|
+
ctx.anims.push(
|
|
581
|
+
el.animate([rotateKeyframe(rest), rotateKeyframe(extendDeg, peakOffset), rotateKeyframe(rest)], {
|
|
582
|
+
...ctx.baseOpts,
|
|
583
|
+
easing: "ease-in-out",
|
|
584
|
+
fill: "forwards"
|
|
585
|
+
})
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
var punch = (ctx) => {
|
|
589
|
+
const side = sideOf(ctx);
|
|
590
|
+
swingLimb(ctx, side === "left" ? "arm_left" : "arm_right", side === "left" ? -75 : 75, 0.35);
|
|
591
|
+
};
|
|
592
|
+
var kick = (ctx) => {
|
|
593
|
+
const side = sideOf(ctx);
|
|
594
|
+
swingLimb(ctx, side === "left" ? "leg_left" : "leg_right", side === "left" ? -100 : 100, 0.38);
|
|
595
|
+
};
|
|
596
|
+
var wave = (ctx) => {
|
|
597
|
+
const side = sideOf(ctx);
|
|
598
|
+
const el = partEl(ctx, side === "left" ? "arm_left" : "arm_right");
|
|
599
|
+
if (!el) return;
|
|
600
|
+
const rest = readRotation(el);
|
|
601
|
+
const up = side === "left" ? 70 : -70;
|
|
602
|
+
const inward = side === "left" ? 50 : -50;
|
|
603
|
+
ctx.anims.push(
|
|
604
|
+
el.animate(
|
|
605
|
+
[
|
|
606
|
+
rotateKeyframe(rest, 0),
|
|
607
|
+
rotateKeyframe(up, 0.2),
|
|
608
|
+
rotateKeyframe(inward, 0.4),
|
|
609
|
+
rotateKeyframe(up, 0.55),
|
|
610
|
+
rotateKeyframe(inward, 0.7),
|
|
611
|
+
rotateKeyframe(up, 0.85),
|
|
612
|
+
rotateKeyframe(rest, 1)
|
|
613
|
+
],
|
|
614
|
+
{ ...ctx.baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
615
|
+
)
|
|
616
|
+
);
|
|
617
|
+
};
|
|
618
|
+
var nod = (ctx) => {
|
|
619
|
+
const el = partEl(ctx, "head");
|
|
620
|
+
if (!el) return;
|
|
621
|
+
const rest = readRotation(el);
|
|
622
|
+
const down = 15;
|
|
623
|
+
ctx.anims.push(
|
|
624
|
+
el.animate(
|
|
625
|
+
[
|
|
626
|
+
rotateKeyframe(rest, 0),
|
|
627
|
+
rotateKeyframe(down, 0.35),
|
|
628
|
+
rotateKeyframe(rest, 0.65),
|
|
629
|
+
rotateKeyframe(down, 0.8),
|
|
630
|
+
rotateKeyframe(rest, 1)
|
|
631
|
+
],
|
|
632
|
+
{ ...ctx.baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
633
|
+
)
|
|
634
|
+
);
|
|
635
|
+
};
|
|
636
|
+
function commitRotation(el, deg) {
|
|
637
|
+
el.style.transform = el.style.transform.replace(/rotate\([^)]*\)/, `rotate(${deg}deg)`);
|
|
638
|
+
}
|
|
639
|
+
var rotatePart = (ctx) => {
|
|
640
|
+
const el = partEl(ctx, String(ctx.ev.params.part ?? ""));
|
|
641
|
+
if (!el) return;
|
|
642
|
+
const from = readRotation(el);
|
|
643
|
+
const to = typeof ctx.ev.params.to === "number" ? ctx.ev.params.to : from;
|
|
644
|
+
ctx.anims.push(
|
|
645
|
+
el.animate([rotateKeyframe(from), rotateKeyframe(to)], { ...ctx.baseOpts, fill: "forwards" })
|
|
646
|
+
);
|
|
647
|
+
commitRotation(el, to);
|
|
648
|
+
};
|
|
649
|
+
var POSEABLE_PARTS = ["arm_left", "arm_right", "leg_left", "leg_right", "head", "body"];
|
|
650
|
+
var pose = (ctx) => {
|
|
651
|
+
for (const part of POSEABLE_PARTS) {
|
|
652
|
+
const to = ctx.ev.params[part];
|
|
653
|
+
if (typeof to !== "number") continue;
|
|
654
|
+
const el = partEl(ctx, part);
|
|
655
|
+
if (!el) continue;
|
|
656
|
+
ctx.anims.push(
|
|
657
|
+
el.animate([rotateKeyframe(readRotation(el)), rotateKeyframe(to)], {
|
|
658
|
+
...ctx.baseOpts,
|
|
659
|
+
fill: "forwards"
|
|
660
|
+
})
|
|
661
|
+
);
|
|
662
|
+
commitRotation(el, to);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
var face = (ctx) => {
|
|
666
|
+
const el = ctx.el.querySelector("[data-fig-face]");
|
|
667
|
+
if (!el) return;
|
|
668
|
+
const emoji = String(ctx.ev.params.text ?? ctx.ev.params._0 ?? "");
|
|
669
|
+
if (emoji) ctx.faceSwaps.push({ timeMs: ctx.ev.time * 1e3, el, emoji });
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// src/actions/transform.ts
|
|
673
|
+
var move = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
674
|
+
const to = ev.params.to;
|
|
675
|
+
const toX = to?.[0] ?? state.x;
|
|
676
|
+
const toY = to?.[1] ?? state.y;
|
|
677
|
+
anims.push(el.animate([{ transform: tx2(state) }, { transform: tx2({ ...state, x: toX, y: toY }) }], baseOpts));
|
|
678
|
+
state.x = toX;
|
|
679
|
+
state.y = toY;
|
|
680
|
+
};
|
|
681
|
+
var enter = ({ ev, el, state, baseOpts, ast, anims, tx: tx2 }) => {
|
|
682
|
+
const from = String(ev.params.from ?? "left");
|
|
683
|
+
const fromState = offscreenState(state, from, ast.meta.width, ast.meta.height);
|
|
684
|
+
anims.push(
|
|
685
|
+
el.animate(
|
|
686
|
+
[
|
|
687
|
+
{ transform: tx2(fromState), opacity: state.opacity },
|
|
688
|
+
{ transform: tx2(state), opacity: 1 }
|
|
689
|
+
],
|
|
690
|
+
baseOpts
|
|
691
|
+
)
|
|
692
|
+
);
|
|
693
|
+
state.opacity = 1;
|
|
694
|
+
};
|
|
695
|
+
var exit = ({ ev, el, state, baseOpts, ast, anims, tx: tx2 }) => {
|
|
696
|
+
const to = String(ev.params.to ?? "right");
|
|
697
|
+
const toState = offscreenState(state, to, ast.meta.width, ast.meta.height);
|
|
698
|
+
anims.push(
|
|
699
|
+
el.animate(
|
|
700
|
+
[
|
|
701
|
+
{ transform: tx2(state), opacity: state.opacity },
|
|
702
|
+
{ transform: tx2(toState), opacity: 0 }
|
|
703
|
+
],
|
|
704
|
+
baseOpts
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
state.x = toState.x;
|
|
708
|
+
state.y = toState.y;
|
|
709
|
+
state.opacity = 0;
|
|
710
|
+
};
|
|
711
|
+
var fadeIn = ({ el, state, baseOpts, anims }) => {
|
|
712
|
+
anims.push(el.animate([{ opacity: 0 }, { opacity: 1 }], baseOpts));
|
|
713
|
+
state.opacity = 1;
|
|
714
|
+
};
|
|
715
|
+
var fadeOut = ({ el, state, baseOpts, anims }) => {
|
|
716
|
+
anims.push(el.animate([{ opacity: state.opacity }, { opacity: 0 }], baseOpts));
|
|
717
|
+
state.opacity = 0;
|
|
718
|
+
};
|
|
719
|
+
var scale = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
720
|
+
const to = typeof ev.params.to === "number" ? ev.params.to : state.scale;
|
|
721
|
+
anims.push(el.animate([{ transform: tx2(state) }, { transform: tx2({ ...state, scale: to }) }], baseOpts));
|
|
722
|
+
state.scale = to;
|
|
723
|
+
};
|
|
724
|
+
var rotate = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
725
|
+
const to = typeof ev.params.to === "number" ? ev.params.to : state.rotate;
|
|
726
|
+
anims.push(el.animate([{ transform: tx2(state) }, { transform: tx2({ ...state, rotate: to }) }], baseOpts));
|
|
727
|
+
state.rotate = to;
|
|
728
|
+
};
|
|
729
|
+
var shake = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
730
|
+
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : 5;
|
|
731
|
+
anims.push(
|
|
732
|
+
el.animate(
|
|
733
|
+
[
|
|
734
|
+
{ transform: tx2(state), offset: 0 },
|
|
735
|
+
{ transform: tx2({ ...state, x: state.x + mag }), offset: 0.2 },
|
|
736
|
+
{ transform: tx2({ ...state, x: state.x - mag }), offset: 0.4 },
|
|
737
|
+
{ transform: tx2({ ...state, x: state.x + mag }), offset: 0.6 },
|
|
738
|
+
{ transform: tx2({ ...state, x: state.x - mag }), offset: 0.8 },
|
|
739
|
+
{ transform: tx2(state), offset: 1 }
|
|
740
|
+
],
|
|
741
|
+
{ ...baseOpts, easing: "linear" }
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
};
|
|
745
|
+
var jump = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
746
|
+
const height = typeof ev.params.height === "number" ? ev.params.height : 30;
|
|
747
|
+
anims.push(
|
|
748
|
+
el.animate(
|
|
749
|
+
[
|
|
750
|
+
{ transform: tx2(state), offset: 0 },
|
|
751
|
+
{ transform: tx2({ ...state, scale: state.scale * 0.9 }), offset: 0.1 },
|
|
752
|
+
{ transform: tx2({ ...state, y: state.y - height, scale: state.scale * 1.1 }), offset: 0.45 },
|
|
753
|
+
{ transform: tx2({ ...state, y: state.y - height * 0.3, scale: state.scale * 1.05 }), offset: 0.7 },
|
|
754
|
+
{ transform: tx2({ ...state, scale: state.scale * 0.92 }), offset: 0.88 },
|
|
755
|
+
{ transform: tx2(state), offset: 1 }
|
|
756
|
+
],
|
|
757
|
+
{ ...baseOpts, easing: "ease-in-out" }
|
|
758
|
+
)
|
|
759
|
+
);
|
|
760
|
+
};
|
|
761
|
+
var bounce = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
762
|
+
const intensity = typeof ev.params.intensity === "number" ? ev.params.intensity : 15;
|
|
763
|
+
const count = typeof ev.params.count === "number" ? ev.params.count : 3;
|
|
764
|
+
const keyframes = [{ transform: tx2(state), offset: 0 }];
|
|
765
|
+
for (let i = 0; i < count; i++) {
|
|
766
|
+
const amp = intensity * Math.pow(0.55, i);
|
|
767
|
+
const baseOffset = (i + 0.5) / (count + 0.5);
|
|
768
|
+
const peakOffset = Math.min(baseOffset, 0.98);
|
|
769
|
+
const valleyOffset = Math.min(baseOffset + 0.25 / (count + 0.5), 0.99);
|
|
770
|
+
keyframes.push({ transform: tx2({ ...state, y: state.y - amp }), offset: peakOffset });
|
|
771
|
+
if (i < count - 1) {
|
|
772
|
+
keyframes.push({ transform: tx2(state), offset: valleyOffset });
|
|
630
773
|
}
|
|
631
|
-
default:
|
|
632
|
-
break;
|
|
633
774
|
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
775
|
+
keyframes.push({ transform: tx2(state), offset: 1 });
|
|
776
|
+
anims.push(el.animate(keyframes, { ...baseOpts, easing: "ease-out" }));
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
// src/geometry/rect.ts
|
|
780
|
+
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 },
|
|
785
|
+
box: { width: 100, height: 100 },
|
|
786
|
+
caption: { width: 260, height: 56 },
|
|
787
|
+
figure: { width: 120, height: 170 }
|
|
639
788
|
};
|
|
789
|
+
var DEFAULT_ACTOR_SIZE = { width: 140, height: 42 };
|
|
640
790
|
function actorSizeByType(type) {
|
|
641
|
-
|
|
642
|
-
case "service":
|
|
643
|
-
case "client":
|
|
644
|
-
case "db":
|
|
645
|
-
case "queue":
|
|
646
|
-
return { width: 180, height: 84 };
|
|
647
|
-
case "box":
|
|
648
|
-
return { width: 100, height: 100 };
|
|
649
|
-
case "caption":
|
|
650
|
-
return { width: 260, height: 56 };
|
|
651
|
-
case "figure":
|
|
652
|
-
return { width: 120, height: 170 };
|
|
653
|
-
default:
|
|
654
|
-
return { width: 140, height: 42 };
|
|
655
|
-
}
|
|
791
|
+
return ACTOR_SIZES[type] ?? DEFAULT_ACTOR_SIZE;
|
|
656
792
|
}
|
|
657
793
|
function actorCenter(state, actorType) {
|
|
658
794
|
const { width, height } = actorSizeByType(actorType);
|
|
659
|
-
return {
|
|
660
|
-
x: state.x + width / 2,
|
|
661
|
-
y: state.y + height / 2
|
|
662
|
-
};
|
|
795
|
+
return { x: state.x + width / 2, y: state.y + height / 2 };
|
|
663
796
|
}
|
|
664
797
|
function actorRect(state, actorType) {
|
|
665
798
|
const { width, height } = actorSizeByType(actorType);
|
|
666
|
-
return {
|
|
667
|
-
x1: state.x,
|
|
668
|
-
y1: state.y,
|
|
669
|
-
x2: state.x + width,
|
|
670
|
-
y2: state.y + height
|
|
671
|
-
};
|
|
799
|
+
return { x1: state.x, y1: state.y, x2: state.x + width, y2: state.y + height };
|
|
672
800
|
}
|
|
673
801
|
function inflateRect(rect, pad) {
|
|
674
802
|
return {
|
|
@@ -706,12 +834,14 @@ function countPathIntersections(points, obstacles) {
|
|
|
706
834
|
}
|
|
707
835
|
return hits;
|
|
708
836
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
837
|
+
|
|
838
|
+
// src/geometry/path.ts
|
|
712
839
|
function round1(n) {
|
|
713
840
|
return Math.round(n * 10) / 10;
|
|
714
841
|
}
|
|
842
|
+
function toPathD(points) {
|
|
843
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round1(p.x)} ${round1(p.y)}`).join(" ");
|
|
844
|
+
}
|
|
715
845
|
function polylineLength(points) {
|
|
716
846
|
let total = 0;
|
|
717
847
|
for (let i = 0; i < points.length - 1; i++) {
|
|
@@ -720,13 +850,13 @@ function polylineLength(points) {
|
|
|
720
850
|
return Math.max(1, total);
|
|
721
851
|
}
|
|
722
852
|
function pointAtDistance(points, dist) {
|
|
723
|
-
let remain = dist;
|
|
853
|
+
let remain = Math.max(0, dist);
|
|
724
854
|
for (let i = 0; i < points.length - 1; i++) {
|
|
725
855
|
const a = points[i];
|
|
726
856
|
const b = points[i + 1];
|
|
727
857
|
const seg = Math.hypot(b.x - a.x, b.y - a.y);
|
|
728
858
|
if (remain <= seg || i === points.length - 2) {
|
|
729
|
-
const t = seg <= 0 ? 0 : remain / seg;
|
|
859
|
+
const t = seg <= 0 ? 0 : Math.min(1, remain / seg);
|
|
730
860
|
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
731
861
|
}
|
|
732
862
|
remain -= seg;
|
|
@@ -734,11 +864,13 @@ function pointAtDistance(points, dist) {
|
|
|
734
864
|
return points[0];
|
|
735
865
|
}
|
|
736
866
|
function routeFlowPath(sourceName, targetName, sourceState, targetState, states, ast, lane) {
|
|
737
|
-
const
|
|
738
|
-
const
|
|
867
|
+
const sourceType = ast.actors[sourceName]?.type ?? "box";
|
|
868
|
+
const targetType = ast.actors[targetName]?.type ?? "box";
|
|
869
|
+
const sourceRect = actorRect(sourceState, sourceType);
|
|
870
|
+
const targetRect = actorRect(targetState, targetType);
|
|
739
871
|
const laneShift = lane * 18;
|
|
740
|
-
const sourceCenter = actorCenter(sourceState,
|
|
741
|
-
const targetCenter = actorCenter(targetState,
|
|
872
|
+
const sourceCenter = actorCenter(sourceState, sourceType);
|
|
873
|
+
const targetCenter = actorCenter(targetState, targetType);
|
|
742
874
|
const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
|
|
743
875
|
const source = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? sourceRect.x2 : sourceRect.x1, y: sourceCenter.y } : { x: sourceCenter.x, y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1 };
|
|
744
876
|
const target = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2, y: targetCenter.y } : { x: targetCenter.x, y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2 };
|
|
@@ -778,11 +910,22 @@ function routeFlowPath(sourceName, targetName, sourceState, targetState, states,
|
|
|
778
910
|
}
|
|
779
911
|
return best;
|
|
780
912
|
}
|
|
913
|
+
|
|
914
|
+
// src/actions/flow.ts
|
|
915
|
+
var EDGE_LAYER_ATTR = "data-markdy-edge-layer";
|
|
916
|
+
var STROKE_BY_ACTION = {
|
|
917
|
+
request: "#38bdf8",
|
|
918
|
+
response: "#a78bfa",
|
|
919
|
+
emit: "#f59e0b"
|
|
920
|
+
};
|
|
921
|
+
var DEFAULT_STROKE = "#38bdf8";
|
|
922
|
+
var LABEL_MAX_CHARS = 28;
|
|
923
|
+
var EDGE_FADE_MS = 140;
|
|
781
924
|
function ensureEdgeLayer(scene) {
|
|
782
|
-
const existing = scene.querySelector(
|
|
925
|
+
const existing = scene.querySelector(`svg[${EDGE_LAYER_ATTR}='1']`);
|
|
783
926
|
if (existing) return existing;
|
|
784
927
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
785
|
-
svg.setAttribute(
|
|
928
|
+
svg.setAttribute(EDGE_LAYER_ATTR, "1");
|
|
786
929
|
Object.assign(svg.style, {
|
|
787
930
|
position: "absolute",
|
|
788
931
|
inset: "0",
|
|
@@ -795,23 +938,19 @@ function ensureEdgeLayer(scene) {
|
|
|
795
938
|
scene.appendChild(svg);
|
|
796
939
|
return svg;
|
|
797
940
|
}
|
|
798
|
-
function
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
ast,
|
|
809
|
-
lane
|
|
810
|
-
);
|
|
941
|
+
function isDashed(ctx) {
|
|
942
|
+
const style = String(ctx.ev.params.style ?? "");
|
|
943
|
+
return style === "dashed" || style === "fire_and_forget" || ctx.ev.action === "response";
|
|
944
|
+
}
|
|
945
|
+
function buildEdge(ctx, targetName) {
|
|
946
|
+
const { ev, state, states, ast, scene, baseOpts, anims } = ctx;
|
|
947
|
+
const targetState = states.get(targetName);
|
|
948
|
+
if (!targetState) return;
|
|
949
|
+
const lane = ev.line % 5 - 2;
|
|
950
|
+
const points = routeFlowPath(ev.actor, targetName, state, targetState, states, ast, lane);
|
|
811
951
|
const length = polylineLength(points);
|
|
812
|
-
const midPoint = pointAtDistance(points, length * 0.5);
|
|
813
952
|
const pathD = toPathD(points);
|
|
814
|
-
const
|
|
953
|
+
const stroke = STROKE_BY_ACTION[ev.action] ?? DEFAULT_STROKE;
|
|
815
954
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
816
955
|
group.setAttribute("data-markdy-flow-edge", "1");
|
|
817
956
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
@@ -820,7 +959,7 @@ function renderFlowEdge(ev, sourceName, targetName, sourceState, targetState, st
|
|
|
820
959
|
path.setAttribute("stroke", stroke);
|
|
821
960
|
path.setAttribute("stroke-width", "2.5");
|
|
822
961
|
path.setAttribute("data-markdy-flow-action", ev.action);
|
|
823
|
-
path.style.strokeDasharray = isDashed ? "8 6" : `${length}`;
|
|
962
|
+
path.style.strokeDasharray = isDashed(ctx) ? "8 6" : `${length}`;
|
|
824
963
|
path.style.strokeDashoffset = `${length}`;
|
|
825
964
|
group.appendChild(path);
|
|
826
965
|
const marker = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
@@ -830,491 +969,285 @@ function renderFlowEdge(ev, sourceName, targetName, sourceState, targetState, st
|
|
|
830
969
|
marker.style.offsetDistance = "0%";
|
|
831
970
|
marker.style.opacity = "0";
|
|
832
971
|
group.appendChild(marker);
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
group.appendChild(
|
|
972
|
+
const labelText = String(ev.params.label ?? "");
|
|
973
|
+
if (labelText) {
|
|
974
|
+
const midPoint = pointAtDistance(points, length * 0.5);
|
|
975
|
+
const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
976
|
+
label.setAttribute("x", `${round1(midPoint.x)}`);
|
|
977
|
+
label.setAttribute("y", `${round1(midPoint.y - 8)}`);
|
|
978
|
+
label.setAttribute("text-anchor", "middle");
|
|
979
|
+
label.setAttribute("font-size", "12");
|
|
980
|
+
label.setAttribute("fill", "#cbd5e1");
|
|
981
|
+
label.setAttribute("data-full-label", labelText);
|
|
982
|
+
label.textContent = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
|
|
983
|
+
group.appendChild(label);
|
|
845
984
|
}
|
|
846
|
-
|
|
847
|
-
anims.push(path.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], baseOpts));
|
|
985
|
+
ensureEdgeLayer(scene).appendChild(group);
|
|
848
986
|
anims.push(
|
|
987
|
+
path.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], baseOpts),
|
|
849
988
|
marker.animate(
|
|
850
|
-
[
|
|
989
|
+
[
|
|
990
|
+
{ offsetDistance: "0%", opacity: 1 },
|
|
991
|
+
{ offsetDistance: "100%", opacity: 1 }
|
|
992
|
+
],
|
|
851
993
|
baseOpts
|
|
852
|
-
)
|
|
994
|
+
),
|
|
995
|
+
group.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
996
|
+
delay: Number(baseOpts.delay ?? 0) + Number(baseOpts.duration ?? 0),
|
|
997
|
+
duration: EDGE_FADE_MS,
|
|
998
|
+
fill: "forwards"
|
|
999
|
+
})
|
|
853
1000
|
);
|
|
854
|
-
|
|
1001
|
+
}
|
|
1002
|
+
var flowEdge = (ctx) => {
|
|
1003
|
+
const targetName = String(ctx.ev.params.to ?? "");
|
|
1004
|
+
if (targetName) buildEdge(ctx, targetName);
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
// src/theme.ts
|
|
1008
|
+
var DARK_LUMINANCE_THRESHOLD = 140;
|
|
1009
|
+
var NAMED_DARK = {
|
|
1010
|
+
black: true,
|
|
1011
|
+
"#000": true,
|
|
1012
|
+
"#000000": true
|
|
1013
|
+
};
|
|
1014
|
+
function isSceneDark(scene) {
|
|
1015
|
+
const bg = scene.style.background || "white";
|
|
1016
|
+
let hex = bg.trim().replace(/^#/, "");
|
|
1017
|
+
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
1018
|
+
if (hex.length === 6) {
|
|
1019
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
1020
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
1021
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
1022
|
+
return 0.299 * r + 0.587 * g + 0.114 * b <= DARK_LUMINANCE_THRESHOLD;
|
|
1023
|
+
}
|
|
1024
|
+
return NAMED_DARK[bg.toLowerCase()] ?? false;
|
|
1025
|
+
}
|
|
1026
|
+
function speechBubbleTheme(dark) {
|
|
1027
|
+
return dark ? {
|
|
1028
|
+
background: "#1e2530",
|
|
1029
|
+
border: "#475569",
|
|
1030
|
+
text: "#e2e8f0",
|
|
1031
|
+
shadow: "0 2px 8px rgba(0,0,0,0.35)"
|
|
1032
|
+
} : {
|
|
1033
|
+
background: "white",
|
|
1034
|
+
border: "#222",
|
|
1035
|
+
text: "#222",
|
|
1036
|
+
shadow: "0 2px 8px rgba(0,0,0,0.12)"
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/actions/speech.ts
|
|
1041
|
+
var MAX_FADE_MS = 200;
|
|
1042
|
+
var FADE_FRACTION = 0.15;
|
|
1043
|
+
var say = ({ ev, el, state, delayMs, durMs, scene, anims }) => {
|
|
1044
|
+
const text = String(ev.params.text ?? "");
|
|
1045
|
+
const theme = speechBubbleTheme(isSceneDark(scene));
|
|
1046
|
+
const inverseScale = 1 / (state.scale || 1);
|
|
1047
|
+
const bubble = document.createElement("div");
|
|
1048
|
+
bubble.textContent = text;
|
|
1049
|
+
Object.assign(bubble.style, {
|
|
1050
|
+
position: "absolute",
|
|
1051
|
+
bottom: "calc(100% + 10px)",
|
|
1052
|
+
left: "50%",
|
|
1053
|
+
transform: `translateX(-50%) scale(${inverseScale})`,
|
|
1054
|
+
transformOrigin: "center bottom",
|
|
1055
|
+
background: theme.background,
|
|
1056
|
+
border: `2px solid ${theme.border}`,
|
|
1057
|
+
color: theme.text,
|
|
1058
|
+
borderRadius: "12px",
|
|
1059
|
+
padding: "6px 14px",
|
|
1060
|
+
fontFamily: "system-ui, sans-serif",
|
|
1061
|
+
fontSize: "15px",
|
|
1062
|
+
lineHeight: "1.3",
|
|
1063
|
+
whiteSpace: "nowrap",
|
|
1064
|
+
maxWidth: "220px",
|
|
1065
|
+
overflow: "hidden",
|
|
1066
|
+
textOverflow: "ellipsis",
|
|
1067
|
+
pointerEvents: "none",
|
|
1068
|
+
zIndex: "10",
|
|
1069
|
+
boxShadow: theme.shadow,
|
|
1070
|
+
opacity: "0"
|
|
1071
|
+
});
|
|
1072
|
+
const tail = document.createElement("span");
|
|
1073
|
+
Object.assign(tail.style, {
|
|
1074
|
+
position: "absolute",
|
|
1075
|
+
bottom: "-10px",
|
|
1076
|
+
left: "50%",
|
|
1077
|
+
transform: "translateX(-50%)",
|
|
1078
|
+
width: "0",
|
|
1079
|
+
height: "0",
|
|
1080
|
+
borderLeft: "7px solid transparent",
|
|
1081
|
+
borderRight: "7px solid transparent",
|
|
1082
|
+
borderTop: `10px solid ${theme.border}`
|
|
1083
|
+
});
|
|
1084
|
+
bubble.appendChild(tail);
|
|
1085
|
+
el.style.overflow = "visible";
|
|
1086
|
+
el.appendChild(bubble);
|
|
1087
|
+
const fadeMs = Math.min(MAX_FADE_MS, durMs * FADE_FRACTION);
|
|
855
1088
|
anims.push(
|
|
856
|
-
|
|
857
|
-
delay:
|
|
858
|
-
duration:
|
|
1089
|
+
bubble.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
1090
|
+
delay: delayMs,
|
|
1091
|
+
duration: fadeMs,
|
|
1092
|
+
fill: "forwards"
|
|
1093
|
+
}),
|
|
1094
|
+
bubble.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
1095
|
+
delay: delayMs + durMs - fadeMs,
|
|
1096
|
+
duration: fadeMs,
|
|
859
1097
|
fill: "forwards"
|
|
860
1098
|
})
|
|
861
1099
|
);
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/actions/projectile.ts
|
|
1103
|
+
var PROJECTILE_SIZE_PX = 32;
|
|
1104
|
+
function createProjectile(assetName, assetDef, assetOverrides) {
|
|
1105
|
+
if (assetDef.type === "image") {
|
|
1106
|
+
const img = document.createElement("img");
|
|
1107
|
+
img.src = assetOverrides[assetName] ?? assetDef.value;
|
|
1108
|
+
img.alt = assetName;
|
|
1109
|
+
img.setAttribute("draggable", "false");
|
|
1110
|
+
img.style.width = `${PROJECTILE_SIZE_PX}px`;
|
|
1111
|
+
img.style.height = `${PROJECTILE_SIZE_PX}px`;
|
|
1112
|
+
return img;
|
|
1113
|
+
}
|
|
1114
|
+
const span = document.createElement("span");
|
|
1115
|
+
span.className = "iconify";
|
|
1116
|
+
span.dataset.icon = assetDef.value;
|
|
1117
|
+
span.style.fontSize = `${PROJECTILE_SIZE_PX}px`;
|
|
1118
|
+
span.style.lineHeight = "1";
|
|
1119
|
+
span.style.display = "inline-block";
|
|
1120
|
+
return span;
|
|
862
1121
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
s.scale = toScale;
|
|
969
|
-
break;
|
|
970
|
-
}
|
|
971
|
-
// ── rotate ──────────────────────────────────────────────────────────────
|
|
972
|
-
case "rotate": {
|
|
973
|
-
const toDeg = typeof ev.params.to === "number" ? ev.params.to : s.rotate;
|
|
974
|
-
anims.push(
|
|
975
|
-
el.animate(
|
|
976
|
-
[{ transform: txFn(s) }, { transform: txFn({ ...s, rotate: toDeg }) }],
|
|
977
|
-
baseOpts
|
|
978
|
-
)
|
|
979
|
-
);
|
|
980
|
-
s.rotate = toDeg;
|
|
981
|
-
break;
|
|
982
|
-
}
|
|
983
|
-
// ── shake ───────────────────────────────────────────────────────────────
|
|
984
|
-
case "shake": {
|
|
985
|
-
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : 5;
|
|
986
|
-
anims.push(
|
|
987
|
-
el.animate(
|
|
988
|
-
[
|
|
989
|
-
{ transform: txFn(s), offset: 0 },
|
|
990
|
-
{ transform: txFn({ ...s, x: s.x + mag }), offset: 0.2 },
|
|
991
|
-
{ transform: txFn({ ...s, x: s.x - mag }), offset: 0.4 },
|
|
992
|
-
{ transform: txFn({ ...s, x: s.x + mag }), offset: 0.6 },
|
|
993
|
-
{ transform: txFn({ ...s, x: s.x - mag }), offset: 0.8 },
|
|
994
|
-
{ transform: txFn(s), offset: 1 }
|
|
995
|
-
],
|
|
996
|
-
{ ...baseOpts, easing: "linear" }
|
|
997
|
-
)
|
|
998
|
-
);
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
// ── punch ───────────────────────────────────────────────────────────────
|
|
1002
|
-
case "punch": {
|
|
1003
|
-
const pSide = String(ev.params.side ?? "right");
|
|
1004
|
-
const pArmEl = el.querySelector(
|
|
1005
|
-
pSide === "left" ? "[data-fig-arm-l]" : "[data-fig-arm-r]"
|
|
1006
|
-
);
|
|
1007
|
-
if (!pArmEl) break;
|
|
1008
|
-
const pRest = readRotation(pArmEl);
|
|
1009
|
-
const pExtend = pSide === "left" ? -75 : 75;
|
|
1010
|
-
anims.push(
|
|
1011
|
-
pArmEl.animate(
|
|
1012
|
-
[
|
|
1013
|
-
{ transform: `rotate(${pRest}deg)` },
|
|
1014
|
-
{ transform: `rotate(${pExtend}deg)`, offset: 0.35 },
|
|
1015
|
-
{ transform: `rotate(${pRest}deg)` }
|
|
1016
|
-
],
|
|
1017
|
-
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
1018
|
-
)
|
|
1019
|
-
);
|
|
1020
|
-
break;
|
|
1021
|
-
}
|
|
1022
|
-
// ── kick ────────────────────────────────────────────────────────────────
|
|
1023
|
-
case "kick": {
|
|
1024
|
-
const kSide = String(ev.params.side ?? "right");
|
|
1025
|
-
const kLegEl = el.querySelector(
|
|
1026
|
-
kSide === "left" ? "[data-fig-leg-l]" : "[data-fig-leg-r]"
|
|
1027
|
-
);
|
|
1028
|
-
if (!kLegEl) break;
|
|
1029
|
-
const kRest = readRotation(kLegEl);
|
|
1030
|
-
const kExtend = kSide === "left" ? -100 : 100;
|
|
1031
|
-
anims.push(
|
|
1032
|
-
kLegEl.animate(
|
|
1033
|
-
[
|
|
1034
|
-
{ transform: `rotate(${kRest}deg)` },
|
|
1035
|
-
{ transform: `rotate(${kExtend}deg)`, offset: 0.38 },
|
|
1036
|
-
{ transform: `rotate(${kRest}deg)` }
|
|
1037
|
-
],
|
|
1038
|
-
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
1039
|
-
)
|
|
1040
|
-
);
|
|
1041
|
-
break;
|
|
1042
|
-
}
|
|
1043
|
-
// ── rotate_part ─────────────────────────────────────────────────────────
|
|
1044
|
-
case "rotate_part": {
|
|
1045
|
-
const rpName = String(ev.params.part ?? "");
|
|
1046
|
-
const rpSel = PART_SEL[rpName];
|
|
1047
|
-
if (!rpSel) break;
|
|
1048
|
-
const rpEl = el.querySelector(rpSel);
|
|
1049
|
-
if (!rpEl) break;
|
|
1050
|
-
const rpFrom = readRotation(rpEl);
|
|
1051
|
-
const rpTo = typeof ev.params.to === "number" ? ev.params.to : rpFrom;
|
|
1052
|
-
anims.push(
|
|
1053
|
-
rpEl.animate(
|
|
1054
|
-
[
|
|
1055
|
-
{ transform: `rotate(${rpFrom}deg)` },
|
|
1056
|
-
{ transform: `rotate(${rpTo}deg)` }
|
|
1057
|
-
],
|
|
1058
|
-
{ ...baseOpts, fill: "forwards" }
|
|
1059
|
-
)
|
|
1060
|
-
);
|
|
1061
|
-
rpEl.style.transform = rpEl.style.transform.replace(
|
|
1062
|
-
/rotate\([^)]*\)/,
|
|
1063
|
-
`rotate(${rpTo}deg)`
|
|
1064
|
-
);
|
|
1065
|
-
break;
|
|
1066
|
-
}
|
|
1067
|
-
// ── face ────────────────────────────────────────────────────────────────
|
|
1068
|
-
case "face": {
|
|
1069
|
-
const fEl = el.querySelector("[data-fig-face]");
|
|
1070
|
-
if (!fEl) break;
|
|
1071
|
-
const emoji = String(ev.params.text ?? ev.params._0 ?? "");
|
|
1072
|
-
if (emoji) faceSwaps.push({ timeMs: ev.time * 1e3, el: fEl, emoji });
|
|
1073
|
-
break;
|
|
1074
|
-
}
|
|
1075
|
-
// ── say ─────────────────────────────────────────────────────────────────
|
|
1076
|
-
case "say": {
|
|
1077
|
-
const text = String(ev.params.text ?? "");
|
|
1078
|
-
const inverseScale = 1 / (s.scale || 1);
|
|
1079
|
-
const sceneDark = isSceneDark(scene);
|
|
1080
|
-
const bubbleBg = sceneDark ? "#1e2530" : "white";
|
|
1081
|
-
const bubbleBorder = sceneDark ? "#475569" : "#222";
|
|
1082
|
-
const bubbleText = sceneDark ? "#e2e8f0" : "#222";
|
|
1083
|
-
const bubbleShadow = sceneDark ? "0 2px 8px rgba(0,0,0,0.35)" : "0 2px 8px rgba(0,0,0,0.12)";
|
|
1084
|
-
const bubble = document.createElement("div");
|
|
1085
|
-
bubble.textContent = text;
|
|
1086
|
-
bubble.style.opacity = "0";
|
|
1087
|
-
Object.assign(bubble.style, {
|
|
1088
|
-
position: "absolute",
|
|
1089
|
-
bottom: "calc(100% + 10px)",
|
|
1090
|
-
left: "50%",
|
|
1091
|
-
transform: `translateX(-50%) scale(${inverseScale})`,
|
|
1092
|
-
transformOrigin: "center bottom",
|
|
1093
|
-
background: bubbleBg,
|
|
1094
|
-
border: `2px solid ${bubbleBorder}`,
|
|
1095
|
-
color: bubbleText,
|
|
1096
|
-
borderRadius: "12px",
|
|
1097
|
-
padding: "6px 14px",
|
|
1098
|
-
fontFamily: "system-ui, sans-serif",
|
|
1099
|
-
fontSize: "15px",
|
|
1100
|
-
lineHeight: "1.3",
|
|
1101
|
-
whiteSpace: "nowrap",
|
|
1102
|
-
maxWidth: "220px",
|
|
1103
|
-
overflow: "hidden",
|
|
1104
|
-
textOverflow: "ellipsis",
|
|
1105
|
-
pointerEvents: "none",
|
|
1106
|
-
zIndex: "10",
|
|
1107
|
-
boxShadow: bubbleShadow
|
|
1108
|
-
});
|
|
1109
|
-
const tail = document.createElement("span");
|
|
1110
|
-
Object.assign(tail.style, {
|
|
1111
|
-
position: "absolute",
|
|
1112
|
-
bottom: "-10px",
|
|
1113
|
-
left: "50%",
|
|
1114
|
-
transform: "translateX(-50%)",
|
|
1115
|
-
width: "0",
|
|
1116
|
-
height: "0",
|
|
1117
|
-
borderLeft: "7px solid transparent",
|
|
1118
|
-
borderRight: "7px solid transparent",
|
|
1119
|
-
borderTop: `10px solid ${bubbleBorder}`
|
|
1120
|
-
});
|
|
1121
|
-
bubble.appendChild(tail);
|
|
1122
|
-
el.style.overflow = "visible";
|
|
1123
|
-
el.appendChild(bubble);
|
|
1124
|
-
const fadeDur = Math.min(200, durMs * 0.15);
|
|
1125
|
-
anims.push(
|
|
1126
|
-
bubble.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
1127
|
-
delay: delayMs,
|
|
1128
|
-
duration: fadeDur,
|
|
1129
|
-
fill: "forwards"
|
|
1130
|
-
}),
|
|
1131
|
-
bubble.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
1132
|
-
delay: delayMs + durMs - fadeDur,
|
|
1133
|
-
duration: fadeDur,
|
|
1134
|
-
fill: "forwards"
|
|
1135
|
-
})
|
|
1136
|
-
);
|
|
1137
|
-
break;
|
|
1138
|
-
}
|
|
1139
|
-
// ── pose ────────────────────────────────────────────────────────────────
|
|
1140
|
-
// Sets multiple body parts to target angles simultaneously.
|
|
1141
|
-
// Usage: @1.0: hero.pose(arm_left=45, arm_right=-45, leg_left=10, dur=0.4)
|
|
1142
|
-
case "pose": {
|
|
1143
|
-
const poseParts = ["arm_left", "arm_right", "leg_left", "leg_right", "head", "body"];
|
|
1144
|
-
for (const partName of poseParts) {
|
|
1145
|
-
if (typeof ev.params[partName] !== "number") continue;
|
|
1146
|
-
const pSel = PART_SEL[partName];
|
|
1147
|
-
if (!pSel) continue;
|
|
1148
|
-
const pEl = el.querySelector(pSel);
|
|
1149
|
-
if (!pEl) continue;
|
|
1150
|
-
const fromDeg = readRotation(pEl);
|
|
1151
|
-
const toDeg = ev.params[partName];
|
|
1152
|
-
anims.push(
|
|
1153
|
-
pEl.animate(
|
|
1154
|
-
[
|
|
1155
|
-
{ transform: `rotate(${fromDeg}deg)` },
|
|
1156
|
-
{ transform: `rotate(${toDeg}deg)` }
|
|
1157
|
-
],
|
|
1158
|
-
{ ...baseOpts, fill: "forwards" }
|
|
1159
|
-
)
|
|
1160
|
-
);
|
|
1161
|
-
pEl.style.transform = pEl.style.transform.replace(
|
|
1162
|
-
/rotate\([^)]*\)/,
|
|
1163
|
-
`rotate(${toDeg}deg)`
|
|
1164
|
-
);
|
|
1165
|
-
}
|
|
1166
|
-
break;
|
|
1167
|
-
}
|
|
1168
|
-
// ── wave ────────────────────────────────────────────────────────────────
|
|
1169
|
-
// Built-in wave gesture: raises arm, oscillates, returns.
|
|
1170
|
-
// Usage: @2.0: hero.wave(side=right, dur=0.8)
|
|
1171
|
-
case "wave": {
|
|
1172
|
-
const wSide = String(ev.params.side ?? "right");
|
|
1173
|
-
const wArmEl = el.querySelector(
|
|
1174
|
-
wSide === "left" ? "[data-fig-arm-l]" : "[data-fig-arm-r]"
|
|
1175
|
-
);
|
|
1176
|
-
if (!wArmEl) break;
|
|
1177
|
-
const wRest = readRotation(wArmEl);
|
|
1178
|
-
const wUp = wSide === "left" ? 70 : -70;
|
|
1179
|
-
const wMid1 = wSide === "left" ? 50 : -50;
|
|
1180
|
-
const wMid2 = wSide === "left" ? 70 : -70;
|
|
1181
|
-
anims.push(
|
|
1182
|
-
wArmEl.animate(
|
|
1183
|
-
[
|
|
1184
|
-
{ transform: `rotate(${wRest}deg)`, offset: 0 },
|
|
1185
|
-
{ transform: `rotate(${wUp}deg)`, offset: 0.2 },
|
|
1186
|
-
{ transform: `rotate(${wMid1}deg)`, offset: 0.4 },
|
|
1187
|
-
{ transform: `rotate(${wMid2}deg)`, offset: 0.55 },
|
|
1188
|
-
{ transform: `rotate(${wMid1}deg)`, offset: 0.7 },
|
|
1189
|
-
{ transform: `rotate(${wMid2}deg)`, offset: 0.85 },
|
|
1190
|
-
{ transform: `rotate(${wRest}deg)`, offset: 1 }
|
|
1191
|
-
],
|
|
1192
|
-
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
1193
|
-
)
|
|
1194
|
-
);
|
|
1195
|
-
break;
|
|
1196
|
-
}
|
|
1197
|
-
// ── jump ────────────────────────────────────────────────────────────────
|
|
1198
|
-
// Jumps the actor up with squash/stretch effect.
|
|
1199
|
-
// Usage: @3.0: hero.jump(height=30, dur=0.5)
|
|
1200
|
-
case "jump": {
|
|
1201
|
-
const jHeight = typeof ev.params.height === "number" ? ev.params.height : 30;
|
|
1202
|
-
anims.push(
|
|
1203
|
-
el.animate(
|
|
1204
|
-
[
|
|
1205
|
-
{ transform: txFn(s), offset: 0 },
|
|
1206
|
-
{ transform: txFn({ ...s, scale: s.scale * 0.9 }), offset: 0.1 },
|
|
1207
|
-
{ transform: txFn({ ...s, y: s.y - jHeight, scale: s.scale * 1.1 }), offset: 0.45 },
|
|
1208
|
-
{ transform: txFn({ ...s, y: s.y - jHeight * 0.3, scale: s.scale * 1.05 }), offset: 0.7 },
|
|
1209
|
-
{ transform: txFn({ ...s, scale: s.scale * 0.92 }), offset: 0.88 },
|
|
1210
|
-
{ transform: txFn(s), offset: 1 }
|
|
1211
|
-
],
|
|
1212
|
-
{ ...baseOpts, easing: "ease-in-out" }
|
|
1213
|
-
)
|
|
1214
|
-
);
|
|
1215
|
-
break;
|
|
1216
|
-
}
|
|
1217
|
-
// ── nod ─────────────────────────────────────────────────────────────────
|
|
1218
|
-
// Nods the head down and back up. Figure actors only.
|
|
1219
|
-
// Usage: @2.0: hero.nod(dur=0.4)
|
|
1220
|
-
case "nod": {
|
|
1221
|
-
const nHeadEl = el.querySelector("[data-fig-head]");
|
|
1222
|
-
if (!nHeadEl) break;
|
|
1223
|
-
const nRest = readRotation(nHeadEl);
|
|
1224
|
-
const nDown = 15;
|
|
1225
|
-
anims.push(
|
|
1226
|
-
nHeadEl.animate(
|
|
1227
|
-
[
|
|
1228
|
-
{ transform: `rotate(${nRest}deg)`, offset: 0 },
|
|
1229
|
-
{ transform: `rotate(${nDown}deg)`, offset: 0.35 },
|
|
1230
|
-
{ transform: `rotate(${nRest}deg)`, offset: 0.65 },
|
|
1231
|
-
{ transform: `rotate(${nDown}deg)`, offset: 0.8 },
|
|
1232
|
-
{ transform: `rotate(${nRest}deg)`, offset: 1 }
|
|
1233
|
-
],
|
|
1234
|
-
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
1235
|
-
)
|
|
1236
|
-
);
|
|
1237
|
-
break;
|
|
1238
|
-
}
|
|
1239
|
-
// ── bounce ──────────────────────────────────────────────────────────────
|
|
1240
|
-
// Bounces the actor vertically with diminishing amplitude.
|
|
1241
|
-
// Usage: @1.0: hero.bounce(intensity=15, count=3, dur=0.6)
|
|
1242
|
-
case "bounce": {
|
|
1243
|
-
const bIntensity = typeof ev.params.intensity === "number" ? ev.params.intensity : 15;
|
|
1244
|
-
const bCount = typeof ev.params.count === "number" ? ev.params.count : 3;
|
|
1245
|
-
const keyframes = [{ transform: txFn(s), offset: 0 }];
|
|
1246
|
-
for (let bi = 0; bi < bCount; bi++) {
|
|
1247
|
-
const amp = bIntensity * Math.pow(0.55, bi);
|
|
1248
|
-
const baseOffset = (bi + 0.5) / (bCount + 0.5);
|
|
1249
|
-
const peakOffset = Math.min(baseOffset, 0.98);
|
|
1250
|
-
const valleyOffset = Math.min(baseOffset + 0.25 / (bCount + 0.5), 0.99);
|
|
1251
|
-
keyframes.push({
|
|
1252
|
-
transform: txFn({ ...s, y: s.y - amp }),
|
|
1253
|
-
offset: peakOffset
|
|
1254
|
-
});
|
|
1255
|
-
if (bi < bCount - 1) {
|
|
1256
|
-
keyframes.push({
|
|
1257
|
-
transform: txFn(s),
|
|
1258
|
-
offset: valleyOffset
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
keyframes.push({ transform: txFn(s), offset: 1 });
|
|
1263
|
-
anims.push(
|
|
1264
|
-
el.animate(keyframes, { ...baseOpts, easing: "ease-out" })
|
|
1265
|
-
);
|
|
1266
|
-
break;
|
|
1267
|
-
}
|
|
1268
|
-
// ── throw ───────────────────────────────────────────────────────────────
|
|
1269
|
-
case "throw": {
|
|
1270
|
-
const assetName = String(ev.params.asset ?? "");
|
|
1271
|
-
const targetActorName = String(ev.params.to ?? "");
|
|
1272
|
-
const targetState = states.get(targetActorName);
|
|
1273
|
-
const assetDef = ast.assets[assetName];
|
|
1274
|
-
if (!assetDef || !targetState) break;
|
|
1275
|
-
let projectile;
|
|
1276
|
-
if (assetDef.type === "image") {
|
|
1277
|
-
const img = document.createElement("img");
|
|
1278
|
-
img.src = assetOverrides[assetName] ?? assetDef.value;
|
|
1279
|
-
img.alt = assetName;
|
|
1280
|
-
img.setAttribute("draggable", "false");
|
|
1281
|
-
img.style.width = "32px";
|
|
1282
|
-
img.style.height = "32px";
|
|
1283
|
-
projectile = img;
|
|
1284
|
-
} else {
|
|
1285
|
-
const span = document.createElement("span");
|
|
1286
|
-
span.className = "iconify";
|
|
1287
|
-
span.dataset.icon = assetDef.value;
|
|
1288
|
-
span.style.fontSize = "32px";
|
|
1289
|
-
span.style.lineHeight = "1";
|
|
1290
|
-
span.style.display = "inline-block";
|
|
1291
|
-
projectile = span;
|
|
1292
|
-
}
|
|
1293
|
-
Object.assign(projectile.style, {
|
|
1294
|
-
position: "absolute",
|
|
1295
|
-
left: "0",
|
|
1296
|
-
top: "0",
|
|
1297
|
-
pointerEvents: "none",
|
|
1298
|
-
zIndex: "9",
|
|
1299
|
-
opacity: "0"
|
|
1300
|
-
});
|
|
1301
|
-
scene.appendChild(projectile);
|
|
1302
|
-
const throwAnim = projectile.animate(
|
|
1303
|
-
[
|
|
1304
|
-
{ transform: tx(s), opacity: 1 },
|
|
1305
|
-
{ transform: tx(targetState), opacity: 0 }
|
|
1306
|
-
],
|
|
1307
|
-
{ ...baseOpts, easing: "ease-in" }
|
|
1308
|
-
);
|
|
1309
|
-
throwAnim.addEventListener("finish", () => {
|
|
1310
|
-
if (projectile.parentNode === scene) scene.removeChild(projectile);
|
|
1311
|
-
});
|
|
1312
|
-
anims.push(throwAnim);
|
|
1313
|
-
break;
|
|
1122
|
+
var throwAsset = ({
|
|
1123
|
+
ev,
|
|
1124
|
+
state,
|
|
1125
|
+
baseOpts,
|
|
1126
|
+
ast,
|
|
1127
|
+
states,
|
|
1128
|
+
scene,
|
|
1129
|
+
assetOverrides,
|
|
1130
|
+
anims
|
|
1131
|
+
}) => {
|
|
1132
|
+
const assetName = String(ev.params.asset ?? "");
|
|
1133
|
+
const targetState = states.get(String(ev.params.to ?? ""));
|
|
1134
|
+
const assetDef = ast.assets[assetName];
|
|
1135
|
+
if (!assetDef || !targetState) return;
|
|
1136
|
+
const projectile = createProjectile(assetName, assetDef, assetOverrides);
|
|
1137
|
+
Object.assign(projectile.style, {
|
|
1138
|
+
position: "absolute",
|
|
1139
|
+
left: "0",
|
|
1140
|
+
top: "0",
|
|
1141
|
+
pointerEvents: "none",
|
|
1142
|
+
zIndex: "9",
|
|
1143
|
+
opacity: "0"
|
|
1144
|
+
});
|
|
1145
|
+
scene.appendChild(projectile);
|
|
1146
|
+
const anim = projectile.animate(
|
|
1147
|
+
[
|
|
1148
|
+
{ transform: tx(state), opacity: 1 },
|
|
1149
|
+
{ transform: tx(targetState), opacity: 0 }
|
|
1150
|
+
],
|
|
1151
|
+
{ ...baseOpts, easing: "ease-in" }
|
|
1152
|
+
);
|
|
1153
|
+
anim.addEventListener("finish", () => {
|
|
1154
|
+
if (projectile.parentNode === scene) scene.removeChild(projectile);
|
|
1155
|
+
});
|
|
1156
|
+
anims.push(anim);
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/actions/registry.ts
|
|
1160
|
+
var ACTION_HANDLERS = Object.freeze({
|
|
1161
|
+
// Universal — every actor type
|
|
1162
|
+
move,
|
|
1163
|
+
enter,
|
|
1164
|
+
exit,
|
|
1165
|
+
fade_in: fadeIn,
|
|
1166
|
+
fade_out: fadeOut,
|
|
1167
|
+
scale,
|
|
1168
|
+
rotate,
|
|
1169
|
+
shake,
|
|
1170
|
+
jump,
|
|
1171
|
+
bounce,
|
|
1172
|
+
say,
|
|
1173
|
+
throw: throwAsset,
|
|
1174
|
+
// Figure-only — the parser rejects these on other actor types
|
|
1175
|
+
punch,
|
|
1176
|
+
kick,
|
|
1177
|
+
wave,
|
|
1178
|
+
nod,
|
|
1179
|
+
rotate_part: rotatePart,
|
|
1180
|
+
pose,
|
|
1181
|
+
face,
|
|
1182
|
+
// System-diagram flow edges (@markdy/stdlib-systems)
|
|
1183
|
+
request: flowEdge,
|
|
1184
|
+
response: flowEdge,
|
|
1185
|
+
emit: flowEdge
|
|
1186
|
+
});
|
|
1187
|
+
function handlerFor(action) {
|
|
1188
|
+
return ACTION_HANDLERS[action];
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/animations.ts
|
|
1192
|
+
var DEFAULT_DURATION_S = 0.5;
|
|
1193
|
+
var DEFAULT_EASE_BY_ACTION = {
|
|
1194
|
+
enter: "out",
|
|
1195
|
+
fade_in: "out",
|
|
1196
|
+
exit: "in",
|
|
1197
|
+
fade_out: "in"
|
|
1198
|
+
};
|
|
1199
|
+
function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
|
|
1200
|
+
const anims = [];
|
|
1201
|
+
const states = /* @__PURE__ */ new Map();
|
|
1202
|
+
for (const [name, def] of Object.entries(ast.actors)) {
|
|
1203
|
+
states.set(name, stateFrom(def));
|
|
1204
|
+
}
|
|
1205
|
+
const captionActors = new Set(
|
|
1206
|
+
Object.entries(ast.actors).filter(([, def]) => def.type === "caption").map(([name]) => name)
|
|
1207
|
+
);
|
|
1208
|
+
const txFor = (actorName) => captionActors.has(actorName) ? txCaption : tx;
|
|
1209
|
+
const events = [...ast.events].sort((a, b) => a.time - b.time);
|
|
1210
|
+
preInitInlineStyles(ast, actorEls, states, events, txFor);
|
|
1211
|
+
const cameraState = freshCameraState();
|
|
1212
|
+
for (const ev of events) {
|
|
1213
|
+
const delayMs = ev.time * 1e3;
|
|
1214
|
+
const durMs = Math.max(
|
|
1215
|
+
1,
|
|
1216
|
+
(typeof ev.params.dur === "number" ? ev.params.dur : DEFAULT_DURATION_S) * 1e3
|
|
1217
|
+
);
|
|
1218
|
+
const baseOpts = {
|
|
1219
|
+
delay: delayMs,
|
|
1220
|
+
duration: durMs,
|
|
1221
|
+
fill: "forwards",
|
|
1222
|
+
easing: toEasing(ev.params.ease ?? DEFAULT_EASE_BY_ACTION[ev.action])
|
|
1223
|
+
};
|
|
1224
|
+
if (ev.actor === "camera") {
|
|
1225
|
+
buildCameraAction(ev, scene, ast, baseOpts, anims, cameraState);
|
|
1226
|
+
continue;
|
|
1314
1227
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1228
|
+
const el = actorEls.get(ev.actor);
|
|
1229
|
+
const state = states.get(ev.actor);
|
|
1230
|
+
const handler = handlerFor(ev.action);
|
|
1231
|
+
if (!el || !state || !handler) continue;
|
|
1232
|
+
const ctx = {
|
|
1233
|
+
ev,
|
|
1234
|
+
el,
|
|
1235
|
+
state,
|
|
1236
|
+
baseOpts,
|
|
1237
|
+
delayMs,
|
|
1238
|
+
durMs,
|
|
1239
|
+
ast,
|
|
1240
|
+
states,
|
|
1241
|
+
actorEls,
|
|
1242
|
+
scene,
|
|
1243
|
+
assetOverrides,
|
|
1244
|
+
faceSwaps,
|
|
1245
|
+
anims,
|
|
1246
|
+
tx: txFor(ev.actor)
|
|
1247
|
+
};
|
|
1248
|
+
handler(ctx);
|
|
1317
1249
|
}
|
|
1250
|
+
return anims;
|
|
1318
1251
|
}
|
|
1319
1252
|
|
|
1320
1253
|
// src/player.ts
|