@markdy/renderer-dom 0.1.0
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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +739 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hoang Yell (https://hoangyell.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @markdy/renderer-dom
|
|
2
|
+
|
|
3
|
+
Web Animations API renderer for [MarkdyScript](../../docs/SYNTAX.md) scenes. Translates a parsed AST into DOM elements and drives the animation timeline.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Browser-native** — Web Animations API + CSS transforms, no Canvas or GSAP
|
|
8
|
+
- **Emoji stick figures** — `figure` actor type with articulatable limbs (punch, kick, rotate_part)
|
|
9
|
+
- **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
|
|
10
|
+
- **Face expressions** — instant emoji face swaps that work correctly on seek-back
|
|
11
|
+
- **Speech bubbles** — auto-positioned bubbles with fade-in/fade-out
|
|
12
|
+
- **Single dependency** — only `@markdy/core`
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pnpm add @markdy/core @markdy/renderer-dom
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createPlayer } from "@markdy/renderer-dom";
|
|
24
|
+
|
|
25
|
+
const player = createPlayer({
|
|
26
|
+
container: document.getElementById("scene")!,
|
|
27
|
+
code: `
|
|
28
|
+
scene width=600 height=300 bg=white
|
|
29
|
+
actor hero = figure(#c68642, m, 😎) at (200, 150)
|
|
30
|
+
@0.0: hero.enter(from=left, dur=0.8)
|
|
31
|
+
@1.5: hero.say("Hello!", dur=1.2)
|
|
32
|
+
@1.5: hero.face("😄")
|
|
33
|
+
`,
|
|
34
|
+
autoplay: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Playback control
|
|
38
|
+
player.pause();
|
|
39
|
+
player.seek(1.5); // jump to 1.5 seconds
|
|
40
|
+
player.play();
|
|
41
|
+
player.destroy(); // clean up DOM + cancel animations
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
### `createPlayer(options: PlayerOptions): Player`
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `container` | `HTMLElement` | *(required)* | DOM element to mount the scene into |
|
|
51
|
+
| `code` | `string` | *(required)* | MarkdyScript source code |
|
|
52
|
+
| `assets` | `Record<string, string>` | `{}` | Asset URL overrides (key = asset name) |
|
|
53
|
+
| `autoplay` | `boolean` | `false` | Start playing immediately |
|
|
54
|
+
|
|
55
|
+
### `Player`
|
|
56
|
+
|
|
57
|
+
| Method | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `play()` | Start or resume playback |
|
|
60
|
+
| `pause()` | Pause at current position |
|
|
61
|
+
| `seek(seconds)` | Jump to a specific time |
|
|
62
|
+
| `destroy()` | Remove DOM elements and cancel all animations |
|
|
63
|
+
|
|
64
|
+
## Module Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/
|
|
68
|
+
types.ts — ActorState, FaceSwap, easing utilities
|
|
69
|
+
figure.ts — Stick-figure DOM factory (emoji body parts)
|
|
70
|
+
actors.ts — Actor element factory (sprite, text, figure, box)
|
|
71
|
+
animations.ts — Timeline → WAAPI Animation builder
|
|
72
|
+
player.ts — Public API, rAF loop, face-swap engine
|
|
73
|
+
index.ts — Barrel exports
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Documentation
|
|
77
|
+
|
|
78
|
+
- **[Syntax Reference](../../docs/SYNTAX.md)** — complete DSL language spec
|
|
79
|
+
- **[Architecture](../../docs/ARCHITECTURE.md)** — renderer internals and playback design
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
[MIT](../../LICENSE)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @markdy/renderer-dom — Player
|
|
3
|
+
*
|
|
4
|
+
* Translates a MarkdyScript program into DOM elements and drives the
|
|
5
|
+
* timeline via the Web Animations API (WAAPI).
|
|
6
|
+
*
|
|
7
|
+
* Playback architecture: all WAAPI animations stay permanently paused.
|
|
8
|
+
* A requestAnimationFrame loop advances `sceneMs` each frame and sets
|
|
9
|
+
* `anim.currentTime = sceneMs` on every animation. This avoids two
|
|
10
|
+
* known pitfalls with WAAPI's startTime-based resumption:
|
|
11
|
+
*
|
|
12
|
+
* 1. Setting `startTime` on a paused animation does not reliably change
|
|
13
|
+
* the play state to "running" across all browsers.
|
|
14
|
+
* 2. `fill:"both"` causes later-created animations to win the cascade
|
|
15
|
+
* during their before-phase, overriding earlier animations' off-screen
|
|
16
|
+
* backward fill.
|
|
17
|
+
*
|
|
18
|
+
* By using `fill:"forwards"` only and pre-initialising actor inline styles,
|
|
19
|
+
* each actor's before-phase state falls through to the inline style we set,
|
|
20
|
+
* which gives correct initial positions and opacity values.
|
|
21
|
+
*/
|
|
22
|
+
interface PlayerOptions {
|
|
23
|
+
container: HTMLElement;
|
|
24
|
+
code: string;
|
|
25
|
+
assets?: Record<string, string>;
|
|
26
|
+
autoplay?: boolean;
|
|
27
|
+
}
|
|
28
|
+
interface Player {
|
|
29
|
+
play(): void;
|
|
30
|
+
pause(): void;
|
|
31
|
+
seek(seconds: number): void;
|
|
32
|
+
destroy(): void;
|
|
33
|
+
}
|
|
34
|
+
declare function createPlayer(opts: PlayerOptions): Player;
|
|
35
|
+
|
|
36
|
+
export { type Player, type PlayerOptions, createPlayer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
// src/player.ts
|
|
2
|
+
import { parse } from "@markdy/core";
|
|
3
|
+
|
|
4
|
+
// src/types.ts
|
|
5
|
+
function stateFrom(def) {
|
|
6
|
+
return {
|
|
7
|
+
x: def.x,
|
|
8
|
+
y: def.y,
|
|
9
|
+
scale: def.scale ?? 1,
|
|
10
|
+
rotate: def.rotate ?? 0,
|
|
11
|
+
opacity: def.opacity ?? 1
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function tx(s) {
|
|
15
|
+
return `translate(${s.x}px, ${s.y}px) scale(${s.scale}) rotate(${s.rotate}deg)`;
|
|
16
|
+
}
|
|
17
|
+
var EASE_MAP = {
|
|
18
|
+
linear: "linear",
|
|
19
|
+
in: "ease-in",
|
|
20
|
+
out: "ease-out",
|
|
21
|
+
inout: "ease-in-out"
|
|
22
|
+
};
|
|
23
|
+
function toEasing(val) {
|
|
24
|
+
return EASE_MAP[String(val ?? "")] ?? "linear";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/figure.ts
|
|
28
|
+
function createFigureEl(def) {
|
|
29
|
+
const skinColor = def.args[0] || "#ffdbac";
|
|
30
|
+
const isFemale = def.args[1] === "f";
|
|
31
|
+
const startFace = def.args[2] || (isFemale ? "\u{1F642}" : "\u{1F636}");
|
|
32
|
+
const ink = "#2a2a2a";
|
|
33
|
+
const WRAP_W = 80;
|
|
34
|
+
const FACE_FS = 40;
|
|
35
|
+
const SHIRT_FS = isFemale ? 48 : 44;
|
|
36
|
+
const vShirtW = SHIRT_FS * 0.9;
|
|
37
|
+
const shLx = (WRAP_W - vShirtW) / 2 + vShirtW * 0.18;
|
|
38
|
+
const shRx = WRAP_W - shLx;
|
|
39
|
+
const shY = Math.round(SHIRT_FS * 0.28);
|
|
40
|
+
const ARM_W = 36, ARM_H = 22;
|
|
41
|
+
const LEG_H = 54, LEG_STICK_H = 34;
|
|
42
|
+
const wrap = document.createElement("div");
|
|
43
|
+
Object.assign(wrap.style, {
|
|
44
|
+
position: "relative",
|
|
45
|
+
display: "flex",
|
|
46
|
+
flexDirection: "column",
|
|
47
|
+
alignItems: "center",
|
|
48
|
+
width: `${WRAP_W}px`,
|
|
49
|
+
overflow: "visible"
|
|
50
|
+
});
|
|
51
|
+
const faceEl = document.createElement("span");
|
|
52
|
+
faceEl.dataset.figFace = "";
|
|
53
|
+
faceEl.dataset.figHead = "";
|
|
54
|
+
faceEl.textContent = startFace;
|
|
55
|
+
Object.assign(faceEl.style, {
|
|
56
|
+
fontSize: `${FACE_FS}px`,
|
|
57
|
+
lineHeight: "1",
|
|
58
|
+
flexShrink: "0",
|
|
59
|
+
userSelect: "none",
|
|
60
|
+
pointerEvents: "none",
|
|
61
|
+
zIndex: "5"
|
|
62
|
+
});
|
|
63
|
+
const neck = document.createElement("div");
|
|
64
|
+
Object.assign(neck.style, {
|
|
65
|
+
width: "8px",
|
|
66
|
+
height: "8px",
|
|
67
|
+
background: skinColor,
|
|
68
|
+
borderRadius: "3px",
|
|
69
|
+
flexShrink: "0",
|
|
70
|
+
marginTop: "-2px",
|
|
71
|
+
marginBottom: "-2px",
|
|
72
|
+
zIndex: "4"
|
|
73
|
+
});
|
|
74
|
+
const shirtRow = document.createElement("div");
|
|
75
|
+
Object.assign(shirtRow.style, {
|
|
76
|
+
position: "relative",
|
|
77
|
+
width: `${WRAP_W}px`,
|
|
78
|
+
height: `${SHIRT_FS}px`,
|
|
79
|
+
textAlign: "center",
|
|
80
|
+
flexShrink: "0",
|
|
81
|
+
zIndex: "2",
|
|
82
|
+
overflow: "visible"
|
|
83
|
+
});
|
|
84
|
+
const torso = document.createElement("span");
|
|
85
|
+
torso.dataset.figBody = "";
|
|
86
|
+
torso.textContent = isFemale ? "\u{1F457}" : "\u{1F455}";
|
|
87
|
+
Object.assign(torso.style, {
|
|
88
|
+
fontSize: `${SHIRT_FS}px`,
|
|
89
|
+
lineHeight: "1",
|
|
90
|
+
userSelect: "none",
|
|
91
|
+
pointerEvents: "none"
|
|
92
|
+
});
|
|
93
|
+
shirtRow.appendChild(torso);
|
|
94
|
+
const armHandEmoji = isFemale ? "\u{1F485}" : "\u{1F91C}";
|
|
95
|
+
shirtRow.appendChild(
|
|
96
|
+
buildArm("left", armHandEmoji, skinColor, {
|
|
97
|
+
w: ARM_W,
|
|
98
|
+
h: ARM_H,
|
|
99
|
+
anchorX: WRAP_W - shLx,
|
|
100
|
+
anchorY: shY,
|
|
101
|
+
restDeg: 20,
|
|
102
|
+
flipFist: !isFemale
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
shirtRow.appendChild(
|
|
106
|
+
buildArm("right", armHandEmoji, skinColor, {
|
|
107
|
+
w: ARM_W,
|
|
108
|
+
h: ARM_H,
|
|
109
|
+
anchorX: shRx,
|
|
110
|
+
anchorY: shY,
|
|
111
|
+
restDeg: -20,
|
|
112
|
+
flipFist: false
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
const legsRow = document.createElement("div");
|
|
116
|
+
Object.assign(legsRow.style, {
|
|
117
|
+
display: "flex",
|
|
118
|
+
justifyContent: "center",
|
|
119
|
+
gap: "10px",
|
|
120
|
+
flexShrink: "0",
|
|
121
|
+
overflow: "visible"
|
|
122
|
+
});
|
|
123
|
+
legsRow.append(
|
|
124
|
+
buildLeg(true, isFemale, ink, LEG_H, LEG_STICK_H),
|
|
125
|
+
buildLeg(false, isFemale, ink, LEG_H, LEG_STICK_H)
|
|
126
|
+
);
|
|
127
|
+
wrap.append(faceEl, neck, shirtRow, legsRow);
|
|
128
|
+
return wrap;
|
|
129
|
+
}
|
|
130
|
+
function buildArm(side, handEmoji, skinColor, g) {
|
|
131
|
+
const arm = document.createElement("div");
|
|
132
|
+
const ds = arm.dataset;
|
|
133
|
+
ds[side === "left" ? "figArmL" : "figArmR"] = "";
|
|
134
|
+
const isLeft = side === "left";
|
|
135
|
+
const origin = isLeft ? "right center" : "left center";
|
|
136
|
+
Object.assign(arm.style, {
|
|
137
|
+
position: "absolute",
|
|
138
|
+
width: `${g.w}px`,
|
|
139
|
+
height: `${g.h}px`,
|
|
140
|
+
...isLeft ? { right: `${g.anchorX}px` } : { left: `${g.anchorX}px` },
|
|
141
|
+
top: `${g.anchorY - g.h / 2}px`,
|
|
142
|
+
transformOrigin: origin,
|
|
143
|
+
transform: `rotate(${g.restDeg}deg)`,
|
|
144
|
+
zIndex: "4",
|
|
145
|
+
overflow: "visible"
|
|
146
|
+
});
|
|
147
|
+
const stick = document.createElement("div");
|
|
148
|
+
Object.assign(stick.style, {
|
|
149
|
+
position: "absolute",
|
|
150
|
+
...isLeft ? { right: "5px" } : { left: "5px" },
|
|
151
|
+
top: `${g.h / 2 - 2}px`,
|
|
152
|
+
width: "18px",
|
|
153
|
+
height: "3px",
|
|
154
|
+
background: skinColor,
|
|
155
|
+
borderRadius: "2px"
|
|
156
|
+
});
|
|
157
|
+
const fist = document.createElement("span");
|
|
158
|
+
fist.textContent = handEmoji;
|
|
159
|
+
Object.assign(fist.style, {
|
|
160
|
+
position: "absolute",
|
|
161
|
+
fontSize: "17px",
|
|
162
|
+
lineHeight: "1",
|
|
163
|
+
...isLeft ? { left: "0" } : { right: "0" },
|
|
164
|
+
top: `${g.h / 2 - 10}px`,
|
|
165
|
+
...g.flipFist ? { transform: "scaleX(-1)" } : {},
|
|
166
|
+
userSelect: "none",
|
|
167
|
+
pointerEvents: "none"
|
|
168
|
+
});
|
|
169
|
+
arm.append(stick, fist);
|
|
170
|
+
return arm;
|
|
171
|
+
}
|
|
172
|
+
function buildLeg(isLeft, isFemale, ink, legH, stickH) {
|
|
173
|
+
const leg = document.createElement("div");
|
|
174
|
+
const ds = leg.dataset;
|
|
175
|
+
ds[isLeft ? "figLegL" : "figLegR"] = "";
|
|
176
|
+
Object.assign(leg.style, {
|
|
177
|
+
position: "relative",
|
|
178
|
+
width: "20px",
|
|
179
|
+
height: `${legH}px`,
|
|
180
|
+
transformOrigin: "top center",
|
|
181
|
+
transform: "rotate(0deg)",
|
|
182
|
+
overflow: "visible"
|
|
183
|
+
});
|
|
184
|
+
const stick = document.createElement("div");
|
|
185
|
+
Object.assign(stick.style, {
|
|
186
|
+
position: "absolute",
|
|
187
|
+
width: "3px",
|
|
188
|
+
height: `${stickH}px`,
|
|
189
|
+
background: ink,
|
|
190
|
+
borderRadius: "1px",
|
|
191
|
+
left: "50%",
|
|
192
|
+
top: "0",
|
|
193
|
+
transform: "translateX(-50%)"
|
|
194
|
+
});
|
|
195
|
+
const shoe = document.createElement("span");
|
|
196
|
+
shoe.textContent = isFemale ? "\u{1F460}" : "\u{1F45F}";
|
|
197
|
+
Object.assign(shoe.style, {
|
|
198
|
+
position: "absolute",
|
|
199
|
+
fontSize: "17px",
|
|
200
|
+
lineHeight: "1",
|
|
201
|
+
bottom: "0",
|
|
202
|
+
userSelect: "none",
|
|
203
|
+
pointerEvents: "none"
|
|
204
|
+
});
|
|
205
|
+
if (isLeft) {
|
|
206
|
+
shoe.style.left = "0";
|
|
207
|
+
shoe.style.transform = "scaleX(-1)";
|
|
208
|
+
} else {
|
|
209
|
+
shoe.style.right = "0";
|
|
210
|
+
}
|
|
211
|
+
leg.append(stick, shoe);
|
|
212
|
+
return leg;
|
|
213
|
+
}
|
|
214
|
+
var PART_SEL = {
|
|
215
|
+
head: "[data-fig-head]",
|
|
216
|
+
face: "[data-fig-face]",
|
|
217
|
+
body: "[data-fig-body]",
|
|
218
|
+
arm_left: "[data-fig-arm-l]",
|
|
219
|
+
arm_right: "[data-fig-arm-r]",
|
|
220
|
+
leg_left: "[data-fig-leg-l]",
|
|
221
|
+
leg_right: "[data-fig-leg-r]"
|
|
222
|
+
};
|
|
223
|
+
function readRotation(el) {
|
|
224
|
+
const m = /rotate\((-?[\d.]+)deg\)/.exec(el.style.transform ?? "");
|
|
225
|
+
return m ? Number(m[1]) : 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/actors.ts
|
|
229
|
+
function createActorEl(name, def, assetDefs, assetOverrides) {
|
|
230
|
+
let el;
|
|
231
|
+
switch (def.type) {
|
|
232
|
+
case "sprite": {
|
|
233
|
+
const assetName = def.args[0] ?? "";
|
|
234
|
+
const assetDef = assetDefs[assetName];
|
|
235
|
+
if (assetDef?.type === "icon") {
|
|
236
|
+
const span = document.createElement("span");
|
|
237
|
+
span.style.display = "inline-block";
|
|
238
|
+
span.style.fontSize = `${def.size ?? 32}px`;
|
|
239
|
+
span.style.lineHeight = "1";
|
|
240
|
+
span.dataset.icon = assetDef.value;
|
|
241
|
+
span.setAttribute("aria-label", assetDef.value.split(":").pop() ?? "icon");
|
|
242
|
+
el = span;
|
|
243
|
+
} else {
|
|
244
|
+
const img = document.createElement("img");
|
|
245
|
+
img.src = assetOverrides[assetName] ?? assetDef?.value ?? "";
|
|
246
|
+
img.alt = assetName;
|
|
247
|
+
img.style.display = "block";
|
|
248
|
+
img.setAttribute("draggable", "false");
|
|
249
|
+
el = img;
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "text": {
|
|
254
|
+
const div = document.createElement("div");
|
|
255
|
+
div.textContent = def.args[0] ?? "";
|
|
256
|
+
div.style.fontSize = `${def.size ?? 24}px`;
|
|
257
|
+
div.style.fontFamily = "sans-serif";
|
|
258
|
+
div.style.whiteSpace = "nowrap";
|
|
259
|
+
div.style.userSelect = "none";
|
|
260
|
+
div.style.pointerEvents = "none";
|
|
261
|
+
el = div;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
case "figure": {
|
|
265
|
+
el = createFigureEl(def);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
default: {
|
|
269
|
+
const div = document.createElement("div");
|
|
270
|
+
div.style.width = "100px";
|
|
271
|
+
div.style.height = "100px";
|
|
272
|
+
div.style.background = "#999";
|
|
273
|
+
div.style.boxSizing = "border-box";
|
|
274
|
+
el = div;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
el.dataset.markdyActor = name;
|
|
279
|
+
el.style.position = "absolute";
|
|
280
|
+
el.style.left = "0";
|
|
281
|
+
el.style.top = "0";
|
|
282
|
+
el.style.transformOrigin = "center center";
|
|
283
|
+
el.style.transform = tx(stateFrom(def));
|
|
284
|
+
el.style.opacity = String(def.opacity ?? 1);
|
|
285
|
+
return el;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/animations.ts
|
|
289
|
+
function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
|
|
290
|
+
const anims = [];
|
|
291
|
+
const states = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const [name, def] of Object.entries(ast.actors)) {
|
|
293
|
+
states.set(name, stateFrom(def));
|
|
294
|
+
}
|
|
295
|
+
const events = [...ast.events].sort((a, b) => a.time - b.time);
|
|
296
|
+
preInitInlineStyles(ast, actorEls, states, events);
|
|
297
|
+
for (const ev of events) {
|
|
298
|
+
const el = actorEls.get(ev.actor);
|
|
299
|
+
const s = states.get(ev.actor);
|
|
300
|
+
if (!el || !s) continue;
|
|
301
|
+
const delayMs = ev.time * 1e3;
|
|
302
|
+
const durMs = Math.max(
|
|
303
|
+
1,
|
|
304
|
+
(typeof ev.params.dur === "number" ? ev.params.dur : 0.5) * 1e3
|
|
305
|
+
);
|
|
306
|
+
const easing = toEasing(ev.params.ease);
|
|
307
|
+
const baseOpts = {
|
|
308
|
+
delay: delayMs,
|
|
309
|
+
duration: durMs,
|
|
310
|
+
fill: "forwards",
|
|
311
|
+
easing
|
|
312
|
+
};
|
|
313
|
+
buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, assetOverrides, faceSwaps, anims);
|
|
314
|
+
}
|
|
315
|
+
return anims;
|
|
316
|
+
}
|
|
317
|
+
function preInitInlineStyles(ast, actorEls, states, events) {
|
|
318
|
+
const firstEventByActor = /* @__PURE__ */ new Map();
|
|
319
|
+
for (const ev of events) {
|
|
320
|
+
if (!firstEventByActor.has(ev.actor)) firstEventByActor.set(ev.actor, ev);
|
|
321
|
+
}
|
|
322
|
+
for (const [name, def] of Object.entries(ast.actors)) {
|
|
323
|
+
const el = actorEls.get(name);
|
|
324
|
+
const s = states.get(name);
|
|
325
|
+
if (!el || !s) continue;
|
|
326
|
+
const firstEv = firstEventByActor.get(name);
|
|
327
|
+
if (firstEv?.action === "enter") {
|
|
328
|
+
const from = String(firstEv.params.from ?? "left");
|
|
329
|
+
const offscreen = { ...s };
|
|
330
|
+
switch (from) {
|
|
331
|
+
case "left":
|
|
332
|
+
offscreen.x = -ast.meta.width * 1.1;
|
|
333
|
+
break;
|
|
334
|
+
case "right":
|
|
335
|
+
offscreen.x = ast.meta.width * 2.1;
|
|
336
|
+
break;
|
|
337
|
+
case "top":
|
|
338
|
+
offscreen.y = -ast.meta.height * 1.1;
|
|
339
|
+
break;
|
|
340
|
+
case "bottom":
|
|
341
|
+
offscreen.y = ast.meta.height * 2.1;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
el.style.transform = tx(offscreen);
|
|
345
|
+
}
|
|
346
|
+
if (firstEv?.action === "fade_in" && (def.opacity === void 0 || def.opacity > 0)) {
|
|
347
|
+
el.style.opacity = "0";
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, assetOverrides, faceSwaps, anims) {
|
|
352
|
+
switch (ev.action) {
|
|
353
|
+
// ── move ────────────────────────────────────────────────────────────────
|
|
354
|
+
case "move": {
|
|
355
|
+
const toArr = ev.params.to;
|
|
356
|
+
const toX = toArr?.[0] ?? s.x;
|
|
357
|
+
const toY = toArr?.[1] ?? s.y;
|
|
358
|
+
anims.push(
|
|
359
|
+
el.animate(
|
|
360
|
+
[{ transform: tx(s) }, { transform: tx({ ...s, x: toX, y: toY }) }],
|
|
361
|
+
baseOpts
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
s.x = toX;
|
|
365
|
+
s.y = toY;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
// ── enter ───────────────────────────────────────────────────────────────
|
|
369
|
+
case "enter": {
|
|
370
|
+
const from = String(ev.params.from ?? "left");
|
|
371
|
+
const fromState = { ...s };
|
|
372
|
+
switch (from) {
|
|
373
|
+
case "left":
|
|
374
|
+
fromState.x = -ast.meta.width;
|
|
375
|
+
break;
|
|
376
|
+
case "right":
|
|
377
|
+
fromState.x = ast.meta.width * 2;
|
|
378
|
+
break;
|
|
379
|
+
case "top":
|
|
380
|
+
fromState.y = -ast.meta.height;
|
|
381
|
+
break;
|
|
382
|
+
case "bottom":
|
|
383
|
+
fromState.y = ast.meta.height * 2;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
anims.push(
|
|
387
|
+
el.animate(
|
|
388
|
+
[{ transform: tx(fromState) }, { transform: tx(s) }],
|
|
389
|
+
baseOpts
|
|
390
|
+
)
|
|
391
|
+
);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
// ── fade_in ─────────────────────────────────────────────────────────────
|
|
395
|
+
case "fade_in": {
|
|
396
|
+
anims.push(el.animate([{ opacity: 0 }, { opacity: 1 }], baseOpts));
|
|
397
|
+
s.opacity = 1;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
// ── fade_out ────────────────────────────────────────────────────────────
|
|
401
|
+
case "fade_out": {
|
|
402
|
+
anims.push(
|
|
403
|
+
el.animate([{ opacity: s.opacity }, { opacity: 0 }], baseOpts)
|
|
404
|
+
);
|
|
405
|
+
s.opacity = 0;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
// ── scale ───────────────────────────────────────────────────────────────
|
|
409
|
+
case "scale": {
|
|
410
|
+
const toScale = typeof ev.params.to === "number" ? ev.params.to : s.scale;
|
|
411
|
+
anims.push(
|
|
412
|
+
el.animate(
|
|
413
|
+
[{ transform: tx(s) }, { transform: tx({ ...s, scale: toScale }) }],
|
|
414
|
+
baseOpts
|
|
415
|
+
)
|
|
416
|
+
);
|
|
417
|
+
s.scale = toScale;
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
// ── rotate ──────────────────────────────────────────────────────────────
|
|
421
|
+
case "rotate": {
|
|
422
|
+
const toDeg = typeof ev.params.to === "number" ? ev.params.to : s.rotate;
|
|
423
|
+
anims.push(
|
|
424
|
+
el.animate(
|
|
425
|
+
[{ transform: tx(s) }, { transform: tx({ ...s, rotate: toDeg }) }],
|
|
426
|
+
baseOpts
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
s.rotate = toDeg;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
// ── shake ───────────────────────────────────────────────────────────────
|
|
433
|
+
case "shake": {
|
|
434
|
+
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : 5;
|
|
435
|
+
anims.push(
|
|
436
|
+
el.animate(
|
|
437
|
+
[
|
|
438
|
+
{ transform: tx(s), offset: 0 },
|
|
439
|
+
{ transform: tx({ ...s, x: s.x + mag }), offset: 0.2 },
|
|
440
|
+
{ transform: tx({ ...s, x: s.x - mag }), offset: 0.4 },
|
|
441
|
+
{ transform: tx({ ...s, x: s.x + mag }), offset: 0.6 },
|
|
442
|
+
{ transform: tx({ ...s, x: s.x - mag }), offset: 0.8 },
|
|
443
|
+
{ transform: tx(s), offset: 1 }
|
|
444
|
+
],
|
|
445
|
+
{ ...baseOpts, easing: "linear" }
|
|
446
|
+
)
|
|
447
|
+
);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
// ── punch ───────────────────────────────────────────────────────────────
|
|
451
|
+
case "punch": {
|
|
452
|
+
const pSide = String(ev.params.side ?? "right");
|
|
453
|
+
const pArmEl = el.querySelector(
|
|
454
|
+
pSide === "left" ? "[data-fig-arm-l]" : "[data-fig-arm-r]"
|
|
455
|
+
);
|
|
456
|
+
if (!pArmEl) break;
|
|
457
|
+
const pRest = readRotation(pArmEl);
|
|
458
|
+
const pExtend = pSide === "left" ? -75 : 75;
|
|
459
|
+
anims.push(
|
|
460
|
+
pArmEl.animate(
|
|
461
|
+
[
|
|
462
|
+
{ transform: `rotate(${pRest}deg)` },
|
|
463
|
+
{ transform: `rotate(${pExtend}deg)`, offset: 0.35 },
|
|
464
|
+
{ transform: `rotate(${pRest}deg)` }
|
|
465
|
+
],
|
|
466
|
+
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
467
|
+
)
|
|
468
|
+
);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
// ── kick ────────────────────────────────────────────────────────────────
|
|
472
|
+
case "kick": {
|
|
473
|
+
const kSide = String(ev.params.side ?? "right");
|
|
474
|
+
const kLegEl = el.querySelector(
|
|
475
|
+
kSide === "left" ? "[data-fig-leg-l]" : "[data-fig-leg-r]"
|
|
476
|
+
);
|
|
477
|
+
if (!kLegEl) break;
|
|
478
|
+
const kRest = readRotation(kLegEl);
|
|
479
|
+
const kExtend = kSide === "left" ? -100 : 100;
|
|
480
|
+
anims.push(
|
|
481
|
+
kLegEl.animate(
|
|
482
|
+
[
|
|
483
|
+
{ transform: `rotate(${kRest}deg)` },
|
|
484
|
+
{ transform: `rotate(${kExtend}deg)`, offset: 0.38 },
|
|
485
|
+
{ transform: `rotate(${kRest}deg)` }
|
|
486
|
+
],
|
|
487
|
+
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
488
|
+
)
|
|
489
|
+
);
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
// ── rotate_part ─────────────────────────────────────────────────────────
|
|
493
|
+
case "rotate_part": {
|
|
494
|
+
const rpName = String(ev.params.part ?? "");
|
|
495
|
+
const rpSel = PART_SEL[rpName];
|
|
496
|
+
if (!rpSel) break;
|
|
497
|
+
const rpEl = el.querySelector(rpSel);
|
|
498
|
+
if (!rpEl) break;
|
|
499
|
+
const rpFrom = readRotation(rpEl);
|
|
500
|
+
const rpTo = typeof ev.params.to === "number" ? ev.params.to : rpFrom;
|
|
501
|
+
anims.push(
|
|
502
|
+
rpEl.animate(
|
|
503
|
+
[
|
|
504
|
+
{ transform: `rotate(${rpFrom}deg)` },
|
|
505
|
+
{ transform: `rotate(${rpTo}deg)` }
|
|
506
|
+
],
|
|
507
|
+
{ ...baseOpts, fill: "forwards" }
|
|
508
|
+
)
|
|
509
|
+
);
|
|
510
|
+
rpEl.style.transform = rpEl.style.transform.replace(
|
|
511
|
+
/rotate\([^)]*\)/,
|
|
512
|
+
`rotate(${rpTo}deg)`
|
|
513
|
+
);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
// ── face ────────────────────────────────────────────────────────────────
|
|
517
|
+
case "face": {
|
|
518
|
+
const fEl = el.querySelector("[data-fig-face]");
|
|
519
|
+
if (!fEl) break;
|
|
520
|
+
const emoji = String(ev.params.text ?? ev.params._0 ?? "");
|
|
521
|
+
if (emoji) faceSwaps.push({ timeMs: ev.time * 1e3, el: fEl, emoji });
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
// ── say ─────────────────────────────────────────────────────────────────
|
|
525
|
+
case "say": {
|
|
526
|
+
const text = String(ev.params.text ?? "");
|
|
527
|
+
const inverseScale = 1 / (s.scale || 1);
|
|
528
|
+
const bubble = document.createElement("div");
|
|
529
|
+
bubble.textContent = text;
|
|
530
|
+
bubble.style.opacity = "0";
|
|
531
|
+
Object.assign(bubble.style, {
|
|
532
|
+
position: "absolute",
|
|
533
|
+
bottom: "calc(100% + 8px)",
|
|
534
|
+
left: "50%",
|
|
535
|
+
transform: `translateX(-50%) scale(${inverseScale})`,
|
|
536
|
+
transformOrigin: "center bottom",
|
|
537
|
+
background: "white",
|
|
538
|
+
border: "2px solid #222",
|
|
539
|
+
borderRadius: "10px",
|
|
540
|
+
padding: "4px 10px",
|
|
541
|
+
fontFamily: "sans-serif",
|
|
542
|
+
fontSize: "14px",
|
|
543
|
+
whiteSpace: "nowrap",
|
|
544
|
+
pointerEvents: "none",
|
|
545
|
+
zIndex: "10",
|
|
546
|
+
boxShadow: "0 2px 6px rgba(0,0,0,0.15)"
|
|
547
|
+
});
|
|
548
|
+
const tail = document.createElement("span");
|
|
549
|
+
Object.assign(tail.style, {
|
|
550
|
+
position: "absolute",
|
|
551
|
+
bottom: "-10px",
|
|
552
|
+
left: "50%",
|
|
553
|
+
transform: "translateX(-50%)",
|
|
554
|
+
width: "0",
|
|
555
|
+
height: "0",
|
|
556
|
+
borderLeft: "6px solid transparent",
|
|
557
|
+
borderRight: "6px solid transparent",
|
|
558
|
+
borderTop: "10px solid #222"
|
|
559
|
+
});
|
|
560
|
+
bubble.appendChild(tail);
|
|
561
|
+
el.style.overflow = "visible";
|
|
562
|
+
el.appendChild(bubble);
|
|
563
|
+
const fadeDur = Math.min(200, durMs * 0.15);
|
|
564
|
+
anims.push(
|
|
565
|
+
bubble.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
566
|
+
delay: delayMs,
|
|
567
|
+
duration: fadeDur,
|
|
568
|
+
fill: "forwards"
|
|
569
|
+
}),
|
|
570
|
+
bubble.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
571
|
+
delay: delayMs + durMs - fadeDur,
|
|
572
|
+
duration: fadeDur,
|
|
573
|
+
fill: "forwards"
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
// ── throw ───────────────────────────────────────────────────────────────
|
|
579
|
+
case "throw": {
|
|
580
|
+
const assetName = String(ev.params.asset ?? "");
|
|
581
|
+
const targetActorName = String(ev.params.to ?? "");
|
|
582
|
+
const targetState = states.get(targetActorName);
|
|
583
|
+
const assetDef = ast.assets[assetName];
|
|
584
|
+
if (!assetDef || !targetState) break;
|
|
585
|
+
let projectile;
|
|
586
|
+
if (assetDef.type === "image") {
|
|
587
|
+
const img = document.createElement("img");
|
|
588
|
+
img.src = assetOverrides[assetName] ?? assetDef.value;
|
|
589
|
+
img.alt = assetName;
|
|
590
|
+
img.setAttribute("draggable", "false");
|
|
591
|
+
img.style.width = "32px";
|
|
592
|
+
img.style.height = "32px";
|
|
593
|
+
projectile = img;
|
|
594
|
+
} else {
|
|
595
|
+
const span = document.createElement("span");
|
|
596
|
+
span.dataset.icon = assetDef.value;
|
|
597
|
+
span.style.fontSize = "32px";
|
|
598
|
+
span.style.lineHeight = "1";
|
|
599
|
+
span.style.display = "inline-block";
|
|
600
|
+
projectile = span;
|
|
601
|
+
}
|
|
602
|
+
Object.assign(projectile.style, {
|
|
603
|
+
position: "absolute",
|
|
604
|
+
left: "0",
|
|
605
|
+
top: "0",
|
|
606
|
+
pointerEvents: "none",
|
|
607
|
+
zIndex: "9",
|
|
608
|
+
opacity: "0"
|
|
609
|
+
});
|
|
610
|
+
scene.appendChild(projectile);
|
|
611
|
+
const throwAnim = projectile.animate(
|
|
612
|
+
[
|
|
613
|
+
{ transform: tx(s), opacity: 1 },
|
|
614
|
+
{ transform: tx(targetState), opacity: 0 }
|
|
615
|
+
],
|
|
616
|
+
{ ...baseOpts, easing: "ease-in" }
|
|
617
|
+
);
|
|
618
|
+
throwAnim.addEventListener("finish", () => {
|
|
619
|
+
if (projectile.parentNode === scene) scene.removeChild(projectile);
|
|
620
|
+
});
|
|
621
|
+
anims.push(throwAnim);
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
default:
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/player.ts
|
|
630
|
+
function createPlayer(opts) {
|
|
631
|
+
const { container, code, assets: assetOverrides = {}, autoplay = false } = opts;
|
|
632
|
+
const ast = parse(code);
|
|
633
|
+
const scene = document.createElement("div");
|
|
634
|
+
Object.assign(scene.style, {
|
|
635
|
+
position: "relative",
|
|
636
|
+
width: `${ast.meta.width}px`,
|
|
637
|
+
height: `${ast.meta.height}px`,
|
|
638
|
+
background: ast.meta.bg,
|
|
639
|
+
overflow: "hidden",
|
|
640
|
+
userSelect: "none"
|
|
641
|
+
});
|
|
642
|
+
container.appendChild(scene);
|
|
643
|
+
const actorEls = /* @__PURE__ */ new Map();
|
|
644
|
+
for (const [name, def] of Object.entries(ast.actors)) {
|
|
645
|
+
const el = createActorEl(name, def, ast.assets, assetOverrides);
|
|
646
|
+
scene.appendChild(el);
|
|
647
|
+
actorEls.set(name, el);
|
|
648
|
+
}
|
|
649
|
+
const faceSwaps = [];
|
|
650
|
+
const allAnims = buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps);
|
|
651
|
+
faceSwaps.sort((a, b) => a.timeMs - b.timeMs);
|
|
652
|
+
for (const anim of allAnims) {
|
|
653
|
+
anim.pause();
|
|
654
|
+
anim.currentTime = 0;
|
|
655
|
+
}
|
|
656
|
+
let sceneMs = 0;
|
|
657
|
+
let lastRafTs = null;
|
|
658
|
+
let isPlaying = false;
|
|
659
|
+
let rafId = null;
|
|
660
|
+
for (const { el } of faceSwaps) {
|
|
661
|
+
if (!el.dataset["figFaceInitial"]) {
|
|
662
|
+
el.dataset["figFaceInitial"] = el.textContent ?? "";
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function applyCurrentTime() {
|
|
666
|
+
for (const anim of allAnims) {
|
|
667
|
+
anim.currentTime = sceneMs;
|
|
668
|
+
}
|
|
669
|
+
applyFaceSwaps();
|
|
670
|
+
}
|
|
671
|
+
function applyFaceSwaps() {
|
|
672
|
+
if (faceSwaps.length === 0) return;
|
|
673
|
+
const elEmoji = /* @__PURE__ */ new Map();
|
|
674
|
+
for (const { timeMs, el, emoji } of faceSwaps) {
|
|
675
|
+
if (timeMs <= sceneMs) elEmoji.set(el, emoji);
|
|
676
|
+
}
|
|
677
|
+
for (const [el, emoji] of elEmoji) {
|
|
678
|
+
if (el.textContent !== emoji) el.textContent = emoji;
|
|
679
|
+
}
|
|
680
|
+
const elFirst = /* @__PURE__ */ new Map();
|
|
681
|
+
for (const { el, emoji } of faceSwaps) {
|
|
682
|
+
if (!elFirst.has(el)) elFirst.set(el, emoji);
|
|
683
|
+
}
|
|
684
|
+
for (const [el, firstEmoji] of elFirst) {
|
|
685
|
+
if (!elEmoji.has(el)) {
|
|
686
|
+
const initial = el.dataset["figFaceInitial"] ?? firstEmoji;
|
|
687
|
+
if (el.textContent !== initial) el.textContent = initial;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function rafTick(timestamp) {
|
|
692
|
+
if (lastRafTs !== null) {
|
|
693
|
+
sceneMs += timestamp - lastRafTs;
|
|
694
|
+
}
|
|
695
|
+
lastRafTs = timestamp;
|
|
696
|
+
const totalMs = (ast.meta.duration ?? 0) * 1e3;
|
|
697
|
+
if (totalMs > 0 && sceneMs >= totalMs) {
|
|
698
|
+
sceneMs = totalMs;
|
|
699
|
+
applyCurrentTime();
|
|
700
|
+
isPlaying = false;
|
|
701
|
+
lastRafTs = null;
|
|
702
|
+
rafId = null;
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
applyCurrentTime();
|
|
706
|
+
rafId = requestAnimationFrame(rafTick);
|
|
707
|
+
}
|
|
708
|
+
const player = {
|
|
709
|
+
play() {
|
|
710
|
+
if (isPlaying) return;
|
|
711
|
+
isPlaying = true;
|
|
712
|
+
lastRafTs = null;
|
|
713
|
+
rafId = requestAnimationFrame(rafTick);
|
|
714
|
+
},
|
|
715
|
+
pause() {
|
|
716
|
+
if (!isPlaying) return;
|
|
717
|
+
isPlaying = false;
|
|
718
|
+
if (rafId !== null) {
|
|
719
|
+
cancelAnimationFrame(rafId);
|
|
720
|
+
rafId = null;
|
|
721
|
+
}
|
|
722
|
+
lastRafTs = null;
|
|
723
|
+
},
|
|
724
|
+
seek(seconds) {
|
|
725
|
+
sceneMs = seconds * 1e3;
|
|
726
|
+
applyCurrentTime();
|
|
727
|
+
},
|
|
728
|
+
destroy() {
|
|
729
|
+
player.pause();
|
|
730
|
+
for (const anim of allAnims) anim.cancel();
|
|
731
|
+
if (scene.parentNode === container) container.removeChild(scene);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
if (autoplay) player.play();
|
|
735
|
+
return player;
|
|
736
|
+
}
|
|
737
|
+
export {
|
|
738
|
+
createPlayer
|
|
739
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@markdy/renderer-dom",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web Animations API renderer for MarkdyScript scenes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"markdy",
|
|
23
|
+
"animation",
|
|
24
|
+
"web-animations-api",
|
|
25
|
+
"renderer",
|
|
26
|
+
"dom"
|
|
27
|
+
],
|
|
28
|
+
"author": "Hoang Yell <hoangyell@gmail.com> (https://hoangyell.com)",
|
|
29
|
+
"homepage": "https://markdy.com",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/HoangYell/markdy-com.git",
|
|
33
|
+
"directory": "packages/renderer-dom"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/HoangYell/markdy-com/issues"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@markdy/core": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"tsup": "^8.3.0",
|
|
46
|
+
"typescript": "^5.4.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"lint": "tsc --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|