@markdy/renderer-dom 0.7.28 → 0.8.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/README.md +24 -42
- package/dist/index.d.ts +5 -43
- package/dist/index.js +396 -2289
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,1698 +1,12 @@
|
|
|
1
1
|
// src/player.ts
|
|
2
|
-
import {
|
|
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
|
-
function txCaption(s) {
|
|
18
|
-
return `translate(calc(${s.x}px - 50%), calc(${s.y}px - 50%)) scale(${s.scale}) rotate(${s.rotate}deg)`;
|
|
19
|
-
}
|
|
20
|
-
var EASE_MAP = {
|
|
21
|
-
linear: "linear",
|
|
22
|
-
in: "ease-in",
|
|
23
|
-
out: "ease-out",
|
|
24
|
-
inout: "ease-in-out",
|
|
25
|
-
// Named cubic-bezier presets for authors who want a more polished feel
|
|
26
|
-
// than the four raw CSS keywords above without hand-writing a curve.
|
|
27
|
-
smooth: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
28
|
-
snappy: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
29
|
-
overshoot: "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
30
|
-
sharp: "cubic-bezier(0.4, 0, 1, 1)"
|
|
31
|
-
};
|
|
32
|
-
var CUBIC_BEZIER_RE = /^cubic-bezier\(\s*-?\d*\.?\d+\s*,\s*-?\d*\.?\d+\s*,\s*-?\d*\.?\d+\s*,\s*-?\d*\.?\d+\s*\)$/;
|
|
33
|
-
function toEasing(val) {
|
|
34
|
-
const str = String(val ?? "");
|
|
35
|
-
if (EASE_MAP[str]) return EASE_MAP[str];
|
|
36
|
-
if (CUBIC_BEZIER_RE.test(str)) return str;
|
|
37
|
-
return "linear";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/actors.ts
|
|
41
|
-
import { TECHNICAL_NODE_KINDS, VISUAL_PRIMITIVE_TYPES } from "@markdy/core";
|
|
42
|
-
|
|
43
|
-
// src/figure.ts
|
|
44
|
-
function createFigureEl(def) {
|
|
45
|
-
const skinColor = def.args[0] || "#ffdbac";
|
|
46
|
-
const isFemale = def.args[1] === "f";
|
|
47
|
-
const startFace = def.args[2] || (isFemale ? "\u{1F642}" : "\u{1F636}");
|
|
48
|
-
const ink = "currentColor";
|
|
49
|
-
const WRAP_W = 80;
|
|
50
|
-
const FACE_FS = 40;
|
|
51
|
-
const SHIRT_FS = isFemale ? 48 : 44;
|
|
52
|
-
const vShirtW = SHIRT_FS * 0.9;
|
|
53
|
-
const shLx = (WRAP_W - vShirtW) / 2 + vShirtW * 0.18;
|
|
54
|
-
const shRx = WRAP_W - shLx;
|
|
55
|
-
const shY = Math.round(SHIRT_FS * 0.28);
|
|
56
|
-
const ARM_W = 36, ARM_H = 22;
|
|
57
|
-
const LEG_H = 54, LEG_STICK_H = 34;
|
|
58
|
-
const JOINT_SIZE = 6;
|
|
59
|
-
const wrap = document.createElement("div");
|
|
60
|
-
Object.assign(wrap.style, {
|
|
61
|
-
position: "relative",
|
|
62
|
-
display: "flex",
|
|
63
|
-
flexDirection: "column",
|
|
64
|
-
alignItems: "center",
|
|
65
|
-
width: `${WRAP_W}px`,
|
|
66
|
-
overflow: "visible"
|
|
67
|
-
});
|
|
68
|
-
const faceEl = document.createElement("span");
|
|
69
|
-
faceEl.dataset.figFace = "";
|
|
70
|
-
faceEl.dataset.figHead = "";
|
|
71
|
-
faceEl.textContent = startFace;
|
|
72
|
-
Object.assign(faceEl.style, {
|
|
73
|
-
fontSize: `${FACE_FS}px`,
|
|
74
|
-
lineHeight: "1",
|
|
75
|
-
flexShrink: "0",
|
|
76
|
-
userSelect: "none",
|
|
77
|
-
pointerEvents: "none",
|
|
78
|
-
zIndex: "5"
|
|
79
|
-
});
|
|
80
|
-
const neck = document.createElement("div");
|
|
81
|
-
Object.assign(neck.style, {
|
|
82
|
-
width: "10px",
|
|
83
|
-
height: "10px",
|
|
84
|
-
background: skinColor,
|
|
85
|
-
borderRadius: "4px",
|
|
86
|
-
flexShrink: "0",
|
|
87
|
-
marginTop: "-3px",
|
|
88
|
-
marginBottom: "-3px",
|
|
89
|
-
zIndex: "4"
|
|
90
|
-
});
|
|
91
|
-
const shirtRow = document.createElement("div");
|
|
92
|
-
Object.assign(shirtRow.style, {
|
|
93
|
-
position: "relative",
|
|
94
|
-
width: `${WRAP_W}px`,
|
|
95
|
-
height: `${SHIRT_FS}px`,
|
|
96
|
-
textAlign: "center",
|
|
97
|
-
flexShrink: "0",
|
|
98
|
-
zIndex: "2",
|
|
99
|
-
overflow: "visible"
|
|
100
|
-
});
|
|
101
|
-
const torso = document.createElement("span");
|
|
102
|
-
torso.dataset.figBody = "";
|
|
103
|
-
torso.textContent = isFemale ? "\u{1F457}" : "\u{1F455}";
|
|
104
|
-
Object.assign(torso.style, {
|
|
105
|
-
fontSize: `${SHIRT_FS}px`,
|
|
106
|
-
lineHeight: "1",
|
|
107
|
-
userSelect: "none",
|
|
108
|
-
pointerEvents: "none"
|
|
109
|
-
});
|
|
110
|
-
shirtRow.appendChild(torso);
|
|
111
|
-
const shoulderL = buildJoint("left", skinColor, JOINT_SIZE, shLx, shY, WRAP_W);
|
|
112
|
-
const shoulderR = buildJoint("right", skinColor, JOINT_SIZE, shRx, shY, WRAP_W);
|
|
113
|
-
shoulderL.dataset.figShoulderL = "";
|
|
114
|
-
shoulderR.dataset.figShoulderR = "";
|
|
115
|
-
shirtRow.appendChild(shoulderL);
|
|
116
|
-
shirtRow.appendChild(shoulderR);
|
|
117
|
-
const armHandEmoji = isFemale ? "\u{1F485}" : "\u{1F91C}";
|
|
118
|
-
shirtRow.appendChild(
|
|
119
|
-
buildArm("left", armHandEmoji, skinColor, {
|
|
120
|
-
w: ARM_W,
|
|
121
|
-
h: ARM_H,
|
|
122
|
-
anchorX: WRAP_W - shLx,
|
|
123
|
-
anchorY: shY,
|
|
124
|
-
restDeg: 20,
|
|
125
|
-
flipFist: !isFemale
|
|
126
|
-
})
|
|
127
|
-
);
|
|
128
|
-
shirtRow.appendChild(
|
|
129
|
-
buildArm("right", armHandEmoji, skinColor, {
|
|
130
|
-
w: ARM_W,
|
|
131
|
-
h: ARM_H,
|
|
132
|
-
anchorX: shRx,
|
|
133
|
-
anchorY: shY,
|
|
134
|
-
restDeg: -20,
|
|
135
|
-
flipFist: false
|
|
136
|
-
})
|
|
137
|
-
);
|
|
138
|
-
const hipRow = document.createElement("div");
|
|
139
|
-
Object.assign(hipRow.style, {
|
|
140
|
-
position: "relative",
|
|
141
|
-
display: "flex",
|
|
142
|
-
flexDirection: "column",
|
|
143
|
-
alignItems: "center",
|
|
144
|
-
flexShrink: "0",
|
|
145
|
-
overflow: "visible"
|
|
146
|
-
});
|
|
147
|
-
const hipBridge = document.createElement("div");
|
|
148
|
-
Object.assign(hipBridge.style, {
|
|
149
|
-
width: "28px",
|
|
150
|
-
height: "4px",
|
|
151
|
-
background: skinColor,
|
|
152
|
-
borderRadius: "2px",
|
|
153
|
-
marginBottom: "1px",
|
|
154
|
-
zIndex: "3"
|
|
155
|
-
});
|
|
156
|
-
hipRow.appendChild(hipBridge);
|
|
157
|
-
const legsRow = document.createElement("div");
|
|
158
|
-
Object.assign(legsRow.style, {
|
|
159
|
-
display: "flex",
|
|
160
|
-
justifyContent: "center",
|
|
161
|
-
gap: "10px",
|
|
162
|
-
flexShrink: "0",
|
|
163
|
-
overflow: "visible",
|
|
164
|
-
position: "relative"
|
|
165
|
-
});
|
|
166
|
-
const legL = buildLeg(true, isFemale, ink, skinColor, LEG_H, LEG_STICK_H, JOINT_SIZE);
|
|
167
|
-
const legR = buildLeg(false, isFemale, ink, skinColor, LEG_H, LEG_STICK_H, JOINT_SIZE);
|
|
168
|
-
legsRow.append(legL, legR);
|
|
169
|
-
hipRow.appendChild(legsRow);
|
|
170
|
-
wrap.append(faceEl, neck, shirtRow, hipRow);
|
|
171
|
-
return wrap;
|
|
172
|
-
}
|
|
173
|
-
function buildJoint(side, skinColor, size, anchorX, anchorY, wrapW) {
|
|
174
|
-
const joint = document.createElement("div");
|
|
175
|
-
const isLeft = side === "left";
|
|
176
|
-
Object.assign(joint.style, {
|
|
177
|
-
position: "absolute",
|
|
178
|
-
width: `${size}px`,
|
|
179
|
-
height: `${size}px`,
|
|
180
|
-
borderRadius: "50%",
|
|
181
|
-
background: skinColor,
|
|
182
|
-
...isLeft ? { right: `${wrapW - anchorX - size / 2}px` } : { left: `${anchorX - size / 2}px` },
|
|
183
|
-
top: `${anchorY - size / 2}px`,
|
|
184
|
-
zIndex: "5",
|
|
185
|
-
pointerEvents: "none"
|
|
186
|
-
});
|
|
187
|
-
return joint;
|
|
188
|
-
}
|
|
189
|
-
function buildArm(side, handEmoji, skinColor, g) {
|
|
190
|
-
const arm = document.createElement("div");
|
|
191
|
-
const ds = arm.dataset;
|
|
192
|
-
ds[side === "left" ? "figArmL" : "figArmR"] = "";
|
|
193
|
-
const isLeft = side === "left";
|
|
194
|
-
const origin = isLeft ? "right center" : "left center";
|
|
195
|
-
Object.assign(arm.style, {
|
|
196
|
-
position: "absolute",
|
|
197
|
-
width: `${g.w}px`,
|
|
198
|
-
height: `${g.h}px`,
|
|
199
|
-
...isLeft ? { right: `${g.anchorX}px` } : { left: `${g.anchorX}px` },
|
|
200
|
-
top: `${g.anchorY - g.h / 2}px`,
|
|
201
|
-
transformOrigin: origin,
|
|
202
|
-
transform: `rotate(${g.restDeg}deg)`,
|
|
203
|
-
zIndex: "4",
|
|
204
|
-
overflow: "visible"
|
|
205
|
-
});
|
|
206
|
-
const stick = document.createElement("div");
|
|
207
|
-
Object.assign(stick.style, {
|
|
208
|
-
position: "absolute",
|
|
209
|
-
...isLeft ? { right: "5px" } : { left: "5px" },
|
|
210
|
-
top: `${g.h / 2 - 2}px`,
|
|
211
|
-
width: "18px",
|
|
212
|
-
height: "4px",
|
|
213
|
-
background: skinColor,
|
|
214
|
-
borderRadius: "2px"
|
|
215
|
-
});
|
|
216
|
-
const fist = document.createElement("span");
|
|
217
|
-
fist.textContent = handEmoji;
|
|
218
|
-
Object.assign(fist.style, {
|
|
219
|
-
position: "absolute",
|
|
220
|
-
fontSize: "17px",
|
|
221
|
-
lineHeight: "1",
|
|
222
|
-
...isLeft ? { left: "0" } : { right: "0" },
|
|
223
|
-
top: `${g.h / 2 - 10}px`,
|
|
224
|
-
...g.flipFist ? { transform: "scaleX(-1)" } : {},
|
|
225
|
-
userSelect: "none",
|
|
226
|
-
pointerEvents: "none"
|
|
227
|
-
});
|
|
228
|
-
arm.append(stick, fist);
|
|
229
|
-
return arm;
|
|
230
|
-
}
|
|
231
|
-
function buildLeg(isLeft, isFemale, ink, skinColor, legH, stickH, jointSize) {
|
|
232
|
-
const leg = document.createElement("div");
|
|
233
|
-
const ds = leg.dataset;
|
|
234
|
-
ds[isLeft ? "figLegL" : "figLegR"] = "";
|
|
235
|
-
Object.assign(leg.style, {
|
|
236
|
-
position: "relative",
|
|
237
|
-
width: "20px",
|
|
238
|
-
height: `${legH}px`,
|
|
239
|
-
transformOrigin: "top center",
|
|
240
|
-
transform: "rotate(0deg)",
|
|
241
|
-
overflow: "visible"
|
|
242
|
-
});
|
|
243
|
-
const hipJoint = document.createElement("div");
|
|
244
|
-
hipJoint.dataset[isLeft ? "figHipL" : "figHipR"] = "";
|
|
245
|
-
Object.assign(hipJoint.style, {
|
|
246
|
-
position: "absolute",
|
|
247
|
-
width: `${jointSize}px`,
|
|
248
|
-
height: `${jointSize}px`,
|
|
249
|
-
borderRadius: "50%",
|
|
250
|
-
background: skinColor,
|
|
251
|
-
left: "50%",
|
|
252
|
-
top: `-${jointSize / 2}px`,
|
|
253
|
-
transform: "translateX(-50%)",
|
|
254
|
-
zIndex: "3",
|
|
255
|
-
pointerEvents: "none"
|
|
256
|
-
});
|
|
257
|
-
const stick = document.createElement("div");
|
|
258
|
-
Object.assign(stick.style, {
|
|
259
|
-
position: "absolute",
|
|
260
|
-
width: "4px",
|
|
261
|
-
height: `${stickH}px`,
|
|
262
|
-
background: ink,
|
|
263
|
-
borderRadius: "2px",
|
|
264
|
-
left: "50%",
|
|
265
|
-
top: "0",
|
|
266
|
-
transform: "translateX(-50%)"
|
|
267
|
-
});
|
|
268
|
-
const knee = document.createElement("div");
|
|
269
|
-
Object.assign(knee.style, {
|
|
270
|
-
position: "absolute",
|
|
271
|
-
width: "4px",
|
|
272
|
-
height: "4px",
|
|
273
|
-
borderRadius: "50%",
|
|
274
|
-
background: skinColor,
|
|
275
|
-
left: "50%",
|
|
276
|
-
top: `${stickH * 0.55}px`,
|
|
277
|
-
transform: "translateX(-50%)",
|
|
278
|
-
zIndex: "2",
|
|
279
|
-
pointerEvents: "none"
|
|
280
|
-
});
|
|
281
|
-
const shoe = document.createElement("span");
|
|
282
|
-
shoe.textContent = isFemale ? "\u{1F460}" : "\u{1F45F}";
|
|
283
|
-
Object.assign(shoe.style, {
|
|
284
|
-
position: "absolute",
|
|
285
|
-
fontSize: "17px",
|
|
286
|
-
lineHeight: "1",
|
|
287
|
-
bottom: "0",
|
|
288
|
-
userSelect: "none",
|
|
289
|
-
pointerEvents: "none"
|
|
290
|
-
});
|
|
291
|
-
if (isLeft) {
|
|
292
|
-
shoe.style.left = "0";
|
|
293
|
-
shoe.style.transform = "scaleX(-1)";
|
|
294
|
-
} else {
|
|
295
|
-
shoe.style.right = "0";
|
|
296
|
-
}
|
|
297
|
-
leg.append(hipJoint, stick, knee, shoe);
|
|
298
|
-
return leg;
|
|
299
|
-
}
|
|
300
|
-
var PART_SEL = {
|
|
301
|
-
head: "[data-fig-head]",
|
|
302
|
-
face: "[data-fig-face]",
|
|
303
|
-
body: "[data-fig-body]",
|
|
304
|
-
arm_left: "[data-fig-arm-l]",
|
|
305
|
-
arm_right: "[data-fig-arm-r]",
|
|
306
|
-
leg_left: "[data-fig-leg-l]",
|
|
307
|
-
leg_right: "[data-fig-leg-r]"
|
|
308
|
-
};
|
|
309
|
-
function readRotation(el) {
|
|
310
|
-
const m = /rotate\((-?[\d.]+)deg\)/.exec(el.style.transform ?? "");
|
|
311
|
-
return m ? Number(m[1]) : 0;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// src/actors.ts
|
|
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
|
|
320
|
-
]);
|
|
321
|
-
var ARCHITECTURE_STYLE_ID = "markdy-architecture-node-styles";
|
|
322
|
-
var VISUAL_PRIMITIVE_STYLE_ID = "markdy-visual-primitive-styles";
|
|
323
|
-
function ensureVisualPrimitiveStyles(doc) {
|
|
324
|
-
if (doc.getElementById(VISUAL_PRIMITIVE_STYLE_ID)) return;
|
|
325
|
-
const style = doc.createElement("style");
|
|
326
|
-
style.id = VISUAL_PRIMITIVE_STYLE_ID;
|
|
327
|
-
style.textContent = `
|
|
328
|
-
.markdy-visual {
|
|
329
|
-
--surface-a: rgba(15, 23, 42, 0.9);
|
|
330
|
-
--accent: #38bdf8;
|
|
331
|
-
--accent-2: #22c55e;
|
|
332
|
-
position: relative;
|
|
333
|
-
box-sizing: border-box;
|
|
334
|
-
width: 390px;
|
|
335
|
-
height: 250px;
|
|
336
|
-
overflow: hidden;
|
|
337
|
-
border: 1px solid rgba(148, 163, 184, 0.34);
|
|
338
|
-
border-radius: 24px;
|
|
339
|
-
color: #e5f3ff;
|
|
340
|
-
background:
|
|
341
|
-
radial-gradient(circle at 12% 0%, color-mix(in srgb, var(--accent) 32%, transparent), transparent 34%),
|
|
342
|
-
radial-gradient(circle at 90% 15%, color-mix(in srgb, var(--accent-2) 26%, transparent), transparent 34%),
|
|
343
|
-
linear-gradient(145deg, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0.025) 32%, var(--surface-a));
|
|
344
|
-
box-shadow:
|
|
345
|
-
0 26px 70px rgba(2, 6, 23, 0.42),
|
|
346
|
-
0 0 0 1px rgba(255, 255, 255, 0.06) inset,
|
|
347
|
-
0 1px 0 rgba(255, 255, 255, 0.18) inset,
|
|
348
|
-
0 0 44px color-mix(in srgb, var(--accent) 24%, transparent);
|
|
349
|
-
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
350
|
-
line-height: 1.2;
|
|
351
|
-
isolation: isolate;
|
|
352
|
-
contain: layout paint style;
|
|
353
|
-
backdrop-filter: blur(16px) saturate(1.2);
|
|
354
|
-
-webkit-backdrop-filter: blur(16px) saturate(1.2);
|
|
355
|
-
}
|
|
356
|
-
.markdy-visual,
|
|
357
|
-
.markdy-visual * {
|
|
358
|
-
box-sizing: border-box;
|
|
359
|
-
}
|
|
360
|
-
.markdy-visual::before {
|
|
361
|
-
content: "";
|
|
362
|
-
position: absolute;
|
|
363
|
-
inset: 0;
|
|
364
|
-
z-index: -1;
|
|
365
|
-
background:
|
|
366
|
-
linear-gradient(90deg, transparent 0 48%, rgba(255, 255, 255, 0.09) 50%, transparent 52%) 0 0 / 34px 34px,
|
|
367
|
-
linear-gradient(0deg, transparent 0 48%, rgba(255, 255, 255, 0.055) 50%, transparent 52%) 0 0 / 34px 34px;
|
|
368
|
-
mask-image: linear-gradient(to bottom, rgba(0,0,0,0.85), transparent 82%);
|
|
369
|
-
opacity: 0.48;
|
|
370
|
-
animation: markdyGridDrift 7s linear infinite;
|
|
371
|
-
}
|
|
372
|
-
.markdy-visual::after {
|
|
373
|
-
content: "";
|
|
374
|
-
position: absolute;
|
|
375
|
-
inset: -40% -20%;
|
|
376
|
-
z-index: -1;
|
|
377
|
-
background: conic-gradient(from 90deg, transparent, color-mix(in srgb, var(--accent) 20%, transparent), transparent 32%);
|
|
378
|
-
opacity: 0.6;
|
|
379
|
-
animation: markdyAurora 9s linear infinite;
|
|
380
|
-
}
|
|
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 {
|
|
386
|
-
display: flex;
|
|
387
|
-
align-items: center;
|
|
388
|
-
justify-content: space-between;
|
|
389
|
-
gap: 12px;
|
|
390
|
-
padding: 14px 16px 8px;
|
|
391
|
-
}
|
|
392
|
-
.markdy-visual__title {
|
|
393
|
-
min-width: 0;
|
|
394
|
-
overflow: hidden;
|
|
395
|
-
text-overflow: ellipsis;
|
|
396
|
-
white-space: nowrap;
|
|
397
|
-
font-size: 15px;
|
|
398
|
-
font-weight: 820;
|
|
399
|
-
letter-spacing: 0.01em;
|
|
400
|
-
color: #f8fafc;
|
|
401
|
-
}
|
|
402
|
-
.markdy-visual__pill {
|
|
403
|
-
flex: 0 0 auto;
|
|
404
|
-
border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
|
|
405
|
-
border-radius: 999px;
|
|
406
|
-
padding: 4px 8px;
|
|
407
|
-
color: #bae6fd;
|
|
408
|
-
background: rgba(8, 47, 73, 0.54);
|
|
409
|
-
font-size: 10px;
|
|
410
|
-
font-weight: 800;
|
|
411
|
-
letter-spacing: 0.08em;
|
|
412
|
-
text-transform: uppercase;
|
|
413
|
-
box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 22%, transparent);
|
|
414
|
-
}
|
|
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;
|
|
429
|
-
border-radius: 999px;
|
|
430
|
-
}
|
|
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 {
|
|
447
|
-
position: absolute;
|
|
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);
|
|
456
|
-
animation: markdyScanPulse 1.8s ease-in-out infinite;
|
|
457
|
-
}
|
|
458
|
-
.visual-panel__rails {
|
|
459
|
-
position: absolute;
|
|
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 {
|
|
471
|
-
position: absolute;
|
|
472
|
-
left: 110px;
|
|
473
|
-
right: 78px;
|
|
474
|
-
bottom: 64px;
|
|
475
|
-
height: 3px;
|
|
476
|
-
border-radius: 999px;
|
|
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;
|
|
480
|
-
}
|
|
481
|
-
.visual-panel__cells {
|
|
482
|
-
position: absolute;
|
|
483
|
-
right: 18px;
|
|
484
|
-
top: 22px;
|
|
485
|
-
display: grid;
|
|
486
|
-
grid-template-columns: repeat(4, 42px);
|
|
487
|
-
gap: 7px;
|
|
488
|
-
}
|
|
489
|
-
.visual-panel__cell {
|
|
490
|
-
height: 30px;
|
|
491
|
-
border-radius: 10px;
|
|
492
|
-
border: 1px solid rgba(148, 163, 184, 0.34);
|
|
493
|
-
background: rgba(15, 23, 42, 0.7);
|
|
494
|
-
box-shadow: 0 0 0 1px rgba(255,255,255,0.035) inset;
|
|
495
|
-
}
|
|
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));
|
|
501
|
-
animation: markdySlotGlow 1.7s ease-in-out infinite;
|
|
502
|
-
}
|
|
503
|
-
.visual-terminal__chrome {
|
|
504
|
-
height: 28px;
|
|
505
|
-
padding-left: 12px;
|
|
506
|
-
display: flex;
|
|
507
|
-
align-items: center;
|
|
508
|
-
gap: 6px;
|
|
509
|
-
border-bottom: 1px solid rgba(148, 163, 184, 0.16);
|
|
510
|
-
}
|
|
511
|
-
.visual-terminal__chrome span {
|
|
512
|
-
width: 8px;
|
|
513
|
-
height: 8px;
|
|
514
|
-
border-radius: 999px;
|
|
515
|
-
background: #ef4444;
|
|
516
|
-
box-shadow: 14px 0 #f59e0b, 28px 0 #22c55e;
|
|
517
|
-
}
|
|
518
|
-
.visual-terminal__lines {
|
|
519
|
-
position: absolute;
|
|
520
|
-
left: 16px;
|
|
521
|
-
right: 16px;
|
|
522
|
-
top: 46px;
|
|
523
|
-
font-family: "SFMono-Regular", Consolas, monospace;
|
|
524
|
-
font-size: 18px;
|
|
525
|
-
line-height: 1.55;
|
|
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);
|
|
528
|
-
}
|
|
529
|
-
.visual-terminal__line {
|
|
530
|
-
overflow: hidden;
|
|
531
|
-
text-overflow: ellipsis;
|
|
532
|
-
white-space: nowrap;
|
|
533
|
-
}
|
|
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;
|
|
544
|
-
}
|
|
545
|
-
.visual-metric strong {
|
|
546
|
-
flex: 0 0 auto;
|
|
547
|
-
color: #f8fafc;
|
|
548
|
-
font-size: 15px;
|
|
549
|
-
line-height: 1;
|
|
550
|
-
}
|
|
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;
|
|
561
|
-
}
|
|
562
|
-
.visual-grid {
|
|
563
|
-
position: absolute;
|
|
564
|
-
inset: 10px;
|
|
565
|
-
display: grid;
|
|
566
|
-
gap: 7px;
|
|
567
|
-
}
|
|
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);
|
|
572
|
-
}
|
|
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);
|
|
577
|
-
}
|
|
578
|
-
.visual-lane {
|
|
579
|
-
position: absolute;
|
|
580
|
-
inset: 0;
|
|
581
|
-
border-radius: inherit;
|
|
582
|
-
background:
|
|
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);
|
|
586
|
-
}
|
|
587
|
-
.visual-lane::after {
|
|
588
|
-
content: "";
|
|
589
|
-
position: absolute;
|
|
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;
|
|
599
|
-
}
|
|
600
|
-
.visual-marker {
|
|
601
|
-
position: absolute;
|
|
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);
|
|
606
|
-
}
|
|
607
|
-
.visual-marker::before,
|
|
608
|
-
.visual-marker::after {
|
|
609
|
-
content: "";
|
|
610
|
-
position: absolute;
|
|
611
|
-
bottom: -3px;
|
|
612
|
-
width: 8px;
|
|
613
|
-
height: 8px;
|
|
614
|
-
border-radius: 999px;
|
|
615
|
-
background: #020617;
|
|
616
|
-
border: 2px solid #cbd5e1;
|
|
617
|
-
}
|
|
618
|
-
.visual-marker::before { left: 9px; }
|
|
619
|
-
.visual-marker::after { right: 9px; }
|
|
620
|
-
.visual-tokens {
|
|
621
|
-
height: 100%;
|
|
622
|
-
padding: 8px;
|
|
623
|
-
display: flex;
|
|
624
|
-
align-items: center;
|
|
625
|
-
gap: 7px;
|
|
626
|
-
}
|
|
627
|
-
.visual-token {
|
|
628
|
-
flex: 1 1 0;
|
|
629
|
-
min-width: 0;
|
|
630
|
-
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
631
|
-
border-radius: 12px;
|
|
632
|
-
padding: 8px 10px;
|
|
633
|
-
background: rgba(2, 6, 23, 0.42);
|
|
634
|
-
color: color-mix(in srgb, var(--accent) 45%, #f8fafc);
|
|
635
|
-
font-family: "SFMono-Regular", Consolas, monospace;
|
|
636
|
-
font-size: 12px;
|
|
637
|
-
font-weight: 800;
|
|
638
|
-
overflow: hidden;
|
|
639
|
-
text-overflow: ellipsis;
|
|
640
|
-
white-space: nowrap;
|
|
641
|
-
}
|
|
642
|
-
.visual-glyph {
|
|
643
|
-
height: 100%;
|
|
644
|
-
display: grid;
|
|
645
|
-
place-items: center;
|
|
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;
|
|
661
|
-
}
|
|
662
|
-
@keyframes markdyGridDrift { to { background-position: 34px 34px, 34px 34px; } }
|
|
663
|
-
@keyframes markdyAurora { to { transform: rotate(1turn); } }
|
|
664
|
-
@keyframes markdyScanPulse { 50% { transform: translateX(18px); opacity: 0.72; } }
|
|
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)); } }
|
|
667
|
-
`;
|
|
668
|
-
doc.head.appendChild(style);
|
|
669
|
-
}
|
|
670
|
-
function escapeHtml(value) {
|
|
671
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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("_", " ");
|
|
737
|
-
const root = document.createElement("div");
|
|
738
|
-
root.className = `markdy-visual markdy-visual--${visualType}`;
|
|
739
|
-
root.dataset.visualType = visualType;
|
|
740
|
-
root.setAttribute("role", "img");
|
|
741
|
-
root.setAttribute("aria-label", label);
|
|
742
|
-
if (visualType === "panel") {
|
|
743
|
-
const tag = escapeHtml(args[1] || "live");
|
|
744
|
-
root.dataset.tone = toneArg({ ...def, args }, 2);
|
|
745
|
-
root.innerHTML = `
|
|
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>
|
|
752
|
-
</div>`;
|
|
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("");
|
|
756
|
-
root.innerHTML = `
|
|
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>`;
|
|
777
|
-
} else {
|
|
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>`;
|
|
780
|
-
}
|
|
781
|
-
return root;
|
|
782
|
-
}
|
|
783
|
-
function ensureArchitectureStyles(doc) {
|
|
784
|
-
if (doc.getElementById(ARCHITECTURE_STYLE_ID)) return;
|
|
785
|
-
const style = doc.createElement("style");
|
|
786
|
-
style.id = ARCHITECTURE_STYLE_ID;
|
|
787
|
-
style.textContent = `
|
|
788
|
-
.markdy-arch-node {
|
|
789
|
-
--markdy-node-accent: #38bdf8;
|
|
790
|
-
--markdy-node-accent-2: #22c55e;
|
|
791
|
-
--markdy-node-surface: rgba(15, 23, 42, 0.82);
|
|
792
|
-
--markdy-node-border: rgba(148, 163, 184, 0.34);
|
|
793
|
-
--markdy-node-glow: rgba(56, 189, 248, 0.24);
|
|
794
|
-
position: relative;
|
|
795
|
-
isolation: isolate;
|
|
796
|
-
width: 184px;
|
|
797
|
-
min-height: 88px;
|
|
798
|
-
box-sizing: border-box;
|
|
799
|
-
display: grid;
|
|
800
|
-
grid-template-columns: 34px minmax(0, 1fr);
|
|
801
|
-
align-items: center;
|
|
802
|
-
gap: 10px;
|
|
803
|
-
padding: 13px 14px;
|
|
804
|
-
border: 1px solid var(--markdy-node-border);
|
|
805
|
-
border-radius: 14px;
|
|
806
|
-
color: #e5eefb;
|
|
807
|
-
background:
|
|
808
|
-
linear-gradient(145deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.02) 34%, rgba(15, 23, 42, 0.88) 100%),
|
|
809
|
-
radial-gradient(circle at 18% 0%, color-mix(in srgb, var(--markdy-node-accent) 34%, transparent), transparent 42%),
|
|
810
|
-
var(--markdy-node-surface);
|
|
811
|
-
box-shadow:
|
|
812
|
-
0 16px 34px rgba(2, 6, 23, 0.28),
|
|
813
|
-
0 0 0 1px rgba(255, 255, 255, 0.04) inset,
|
|
814
|
-
0 1px 0 rgba(255, 255, 255, 0.12) inset,
|
|
815
|
-
0 0 28px var(--markdy-node-glow);
|
|
816
|
-
overflow: hidden;
|
|
817
|
-
contain: layout paint style;
|
|
818
|
-
backdrop-filter: blur(14px) saturate(1.18);
|
|
819
|
-
-webkit-backdrop-filter: blur(14px) saturate(1.18);
|
|
820
|
-
}
|
|
821
|
-
.markdy-arch-node::before {
|
|
822
|
-
content: "";
|
|
823
|
-
position: absolute;
|
|
824
|
-
inset: 0;
|
|
825
|
-
z-index: -1;
|
|
826
|
-
background:
|
|
827
|
-
linear-gradient(90deg, transparent 0 24%, rgba(255, 255, 255, 0.06) 50%, transparent 76%) -180px 0 / 180px 100% no-repeat,
|
|
828
|
-
repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.055) 0 1px, transparent 1px 10px);
|
|
829
|
-
opacity: 0.6;
|
|
830
|
-
}
|
|
831
|
-
.markdy-arch-node::after {
|
|
832
|
-
content: "";
|
|
833
|
-
position: absolute;
|
|
834
|
-
left: 14px;
|
|
835
|
-
right: 14px;
|
|
836
|
-
bottom: 9px;
|
|
837
|
-
height: 2px;
|
|
838
|
-
border-radius: 999px;
|
|
839
|
-
background: linear-gradient(90deg, transparent, var(--markdy-node-accent), var(--markdy-node-accent-2), transparent);
|
|
840
|
-
opacity: 0.82;
|
|
841
|
-
filter: drop-shadow(0 0 6px var(--markdy-node-accent));
|
|
842
|
-
}
|
|
843
|
-
.markdy-arch-node__icon {
|
|
844
|
-
position: relative;
|
|
845
|
-
width: 32px;
|
|
846
|
-
height: 32px;
|
|
847
|
-
border-radius: 11px;
|
|
848
|
-
background:
|
|
849
|
-
radial-gradient(circle at 35% 25%, rgba(255, 255, 255, 0.28), transparent 42%),
|
|
850
|
-
linear-gradient(145deg, var(--markdy-node-accent), color-mix(in srgb, var(--markdy-node-accent) 38%, #020617));
|
|
851
|
-
box-shadow:
|
|
852
|
-
0 0 0 1px rgba(255, 255, 255, 0.16) inset,
|
|
853
|
-
0 10px 20px color-mix(in srgb, var(--markdy-node-accent) 25%, transparent);
|
|
854
|
-
}
|
|
855
|
-
.markdy-arch-node__icon::before,
|
|
856
|
-
.markdy-arch-node__icon::after {
|
|
857
|
-
content: "";
|
|
858
|
-
position: absolute;
|
|
859
|
-
box-sizing: border-box;
|
|
860
|
-
border-color: rgba(255, 255, 255, 0.9);
|
|
861
|
-
}
|
|
862
|
-
.markdy-arch-node__label {
|
|
863
|
-
min-width: 0;
|
|
864
|
-
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
865
|
-
font-size: 15px;
|
|
866
|
-
font-weight: 720;
|
|
867
|
-
line-height: 1.08;
|
|
868
|
-
color: #f8fafc;
|
|
869
|
-
text-shadow: 0 1px 12px rgba(15, 23, 42, 0.8);
|
|
870
|
-
white-space: nowrap;
|
|
871
|
-
overflow: hidden;
|
|
872
|
-
text-overflow: ellipsis;
|
|
873
|
-
}
|
|
874
|
-
.markdy-arch-node__type {
|
|
875
|
-
display: block;
|
|
876
|
-
margin-top: 5px;
|
|
877
|
-
font-size: 9px;
|
|
878
|
-
font-weight: 740;
|
|
879
|
-
letter-spacing: 0.08em;
|
|
880
|
-
text-transform: uppercase;
|
|
881
|
-
color: color-mix(in srgb, var(--markdy-node-accent) 70%, #e2e8f0);
|
|
882
|
-
opacity: 0.86;
|
|
883
|
-
}
|
|
884
|
-
.markdy-arch-node[data-markdy-system-kind="compute"],
|
|
885
|
-
.markdy-arch-node[data-markdy-system-kind="code"] {
|
|
886
|
-
--markdy-node-accent: #38bdf8;
|
|
887
|
-
--markdy-node-accent-2: #818cf8;
|
|
888
|
-
}
|
|
889
|
-
.markdy-arch-node[data-markdy-system-kind="client"] {
|
|
890
|
-
--markdy-node-accent: #f59e0b;
|
|
891
|
-
--markdy-node-accent-2: #fb7185;
|
|
892
|
-
}
|
|
893
|
-
.markdy-arch-node[data-markdy-system-kind="data"] {
|
|
894
|
-
--markdy-node-accent: #22c55e;
|
|
895
|
-
--markdy-node-accent-2: #14b8a6;
|
|
896
|
-
}
|
|
897
|
-
.markdy-arch-node[data-markdy-system-kind="messaging"] {
|
|
898
|
-
--markdy-node-accent: #a78bfa;
|
|
899
|
-
--markdy-node-accent-2: #38bdf8;
|
|
900
|
-
}
|
|
901
|
-
.markdy-arch-node[data-markdy-system-kind="network"] {
|
|
902
|
-
--markdy-node-accent: #60a5fa;
|
|
903
|
-
--markdy-node-accent-2: #67e8f9;
|
|
904
|
-
}
|
|
905
|
-
.markdy-arch-node[data-markdy-system-kind="platform"] {
|
|
906
|
-
--markdy-node-accent: #c084fc;
|
|
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"] {
|
|
936
|
-
border-radius: 10px;
|
|
937
|
-
}
|
|
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 {
|
|
943
|
-
inset: 9px 7px;
|
|
944
|
-
border-top: 3px solid rgba(255, 255, 255, 0.92);
|
|
945
|
-
border-bottom: 3px solid rgba(255, 255, 255, 0.92);
|
|
946
|
-
}
|
|
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 {
|
|
949
|
-
inset: 14px 7px auto;
|
|
950
|
-
border-top: 3px solid rgba(255, 255, 255, 0.92);
|
|
951
|
-
}
|
|
952
|
-
.markdy-arch-node[data-markdy-system-kind="client"] .markdy-arch-node__icon::before {
|
|
953
|
-
left: 8px;
|
|
954
|
-
right: 8px;
|
|
955
|
-
top: 8px;
|
|
956
|
-
height: 13px;
|
|
957
|
-
border: 2px solid rgba(255, 255, 255, 0.92);
|
|
958
|
-
border-radius: 3px;
|
|
959
|
-
}
|
|
960
|
-
.markdy-arch-node[data-markdy-system-kind="client"] .markdy-arch-node__icon::after {
|
|
961
|
-
left: 12px;
|
|
962
|
-
right: 12px;
|
|
963
|
-
bottom: 7px;
|
|
964
|
-
border-top: 2px solid rgba(255, 255, 255, 0.92);
|
|
965
|
-
}
|
|
966
|
-
.markdy-arch-node[data-markdy-system-kind="data"] .markdy-arch-node__icon::before {
|
|
967
|
-
inset: 7px 7px 9px;
|
|
968
|
-
border: 2px solid rgba(255, 255, 255, 0.92);
|
|
969
|
-
border-radius: 50% / 16%;
|
|
970
|
-
}
|
|
971
|
-
.markdy-arch-node[data-markdy-system-kind="data"] .markdy-arch-node__icon::after {
|
|
972
|
-
left: 8px;
|
|
973
|
-
right: 8px;
|
|
974
|
-
top: 12px;
|
|
975
|
-
border-top: 2px solid rgba(255, 255, 255, 0.72);
|
|
976
|
-
}
|
|
977
|
-
.markdy-arch-node[data-markdy-system-kind="messaging"] .markdy-arch-node__icon::before {
|
|
978
|
-
inset: 9px 8px;
|
|
979
|
-
border: 2px solid rgba(255, 255, 255, 0.92);
|
|
980
|
-
border-radius: 999px;
|
|
981
|
-
}
|
|
982
|
-
.markdy-arch-node[data-markdy-system-kind="messaging"] .markdy-arch-node__icon::after {
|
|
983
|
-
left: 13px;
|
|
984
|
-
top: 9px;
|
|
985
|
-
width: 8px;
|
|
986
|
-
height: 14px;
|
|
987
|
-
border-left: 2px solid rgba(255, 255, 255, 0.82);
|
|
988
|
-
border-right: 2px solid rgba(255, 255, 255, 0.82);
|
|
989
|
-
}
|
|
990
|
-
.markdy-arch-node[data-markdy-system-kind="network"] .markdy-arch-node__icon::before {
|
|
991
|
-
left: 7px;
|
|
992
|
-
right: 7px;
|
|
993
|
-
bottom: 9px;
|
|
994
|
-
height: 12px;
|
|
995
|
-
border: 2px solid rgba(255, 255, 255, 0.92);
|
|
996
|
-
border-radius: 999px;
|
|
997
|
-
}
|
|
998
|
-
.markdy-arch-node[data-markdy-system-kind="network"] .markdy-arch-node__icon::after {
|
|
999
|
-
left: 10px;
|
|
1000
|
-
top: 7px;
|
|
1001
|
-
width: 13px;
|
|
1002
|
-
height: 13px;
|
|
1003
|
-
border-top: 2px solid rgba(255, 255, 255, 0.92);
|
|
1004
|
-
border-left: 2px solid rgba(255, 255, 255, 0.92);
|
|
1005
|
-
border-radius: 999px 0 0 0;
|
|
1006
|
-
}
|
|
1007
|
-
.markdy-arch-node[data-markdy-system-kind="platform"] .markdy-arch-node__icon::before {
|
|
1008
|
-
inset: 8px;
|
|
1009
|
-
border: 2px solid rgba(255, 255, 255, 0.92);
|
|
1010
|
-
border-radius: 4px;
|
|
1011
|
-
}
|
|
1012
|
-
.markdy-arch-node[data-markdy-system-kind="platform"] .markdy-arch-node__icon::after {
|
|
1013
|
-
left: 10px;
|
|
1014
|
-
right: 10px;
|
|
1015
|
-
top: 14px;
|
|
1016
|
-
border-top: 2px solid rgba(255, 255, 255, 0.72);
|
|
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
|
-
}
|
|
1058
|
-
`;
|
|
1059
|
-
doc.head.appendChild(style);
|
|
1060
|
-
}
|
|
1061
|
-
function isArchitectureNodeType(type) {
|
|
1062
|
-
return ARCHITECTURE_NODE_TYPES.has(type);
|
|
1063
|
-
}
|
|
1064
|
-
function isVisualPrimitiveType(type) {
|
|
1065
|
-
return VISUAL_PRIMITIVE_TYPE_SET.has(type);
|
|
1066
|
-
}
|
|
1067
|
-
function architectureTypeLabel(type) {
|
|
1068
|
-
if (type === "db") return "database";
|
|
1069
|
-
if (type === "api") return "service";
|
|
1070
|
-
return type;
|
|
1071
|
-
}
|
|
1072
|
-
function architectureNodeKind(type) {
|
|
1073
|
-
return TECHNICAL_NODE_KINDS[type] ?? "compute";
|
|
1074
|
-
}
|
|
1075
|
-
function createActorEl(name, def, assetDefs, assetOverrides) {
|
|
1076
|
-
let el;
|
|
1077
|
-
switch (def.type) {
|
|
1078
|
-
case "sprite": {
|
|
1079
|
-
const assetName = def.args[0] ?? "";
|
|
1080
|
-
const assetDef = assetDefs[assetName];
|
|
1081
|
-
if (assetDef?.type === "icon") {
|
|
1082
|
-
const span = document.createElement("span");
|
|
1083
|
-
span.className = "iconify";
|
|
1084
|
-
span.style.display = "inline-block";
|
|
1085
|
-
span.style.fontSize = `${def.size ?? 32}px`;
|
|
1086
|
-
span.style.lineHeight = "1";
|
|
1087
|
-
span.dataset.icon = assetDef.value;
|
|
1088
|
-
span.setAttribute("aria-label", assetDef.value.split(":").pop() ?? "icon");
|
|
1089
|
-
el = span;
|
|
1090
|
-
} else {
|
|
1091
|
-
const img = document.createElement("img");
|
|
1092
|
-
img.src = assetOverrides[assetName] ?? assetDef?.value ?? "";
|
|
1093
|
-
img.alt = assetName;
|
|
1094
|
-
img.style.display = "block";
|
|
1095
|
-
img.style.maxWidth = "100%";
|
|
1096
|
-
img.style.maxHeight = "200px";
|
|
1097
|
-
img.style.objectFit = "contain";
|
|
1098
|
-
img.setAttribute("draggable", "false");
|
|
1099
|
-
el = img;
|
|
1100
|
-
}
|
|
1101
|
-
break;
|
|
1102
|
-
}
|
|
1103
|
-
case "text": {
|
|
1104
|
-
const div = document.createElement("div");
|
|
1105
|
-
div.textContent = def.args[0] ?? "";
|
|
1106
|
-
div.style.fontSize = `${def.size ?? 24}px`;
|
|
1107
|
-
div.style.fontFamily = "sans-serif";
|
|
1108
|
-
div.style.whiteSpace = "nowrap";
|
|
1109
|
-
div.style.userSelect = "none";
|
|
1110
|
-
div.style.pointerEvents = "none";
|
|
1111
|
-
el = div;
|
|
1112
|
-
break;
|
|
1113
|
-
}
|
|
1114
|
-
case "caption": {
|
|
1115
|
-
const div = document.createElement("div");
|
|
1116
|
-
div.textContent = def.args[0] ?? "";
|
|
1117
|
-
div.dataset.markdyCaption = def.anchor ?? "top";
|
|
1118
|
-
Object.assign(div.style, {
|
|
1119
|
-
fontSize: `${def.size ?? 32}px`,
|
|
1120
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
1121
|
-
fontWeight: "700",
|
|
1122
|
-
whiteSpace: "nowrap",
|
|
1123
|
-
textAlign: "center",
|
|
1124
|
-
lineHeight: "1.1",
|
|
1125
|
-
padding: "6px 14px",
|
|
1126
|
-
borderRadius: "4px",
|
|
1127
|
-
background: "rgba(0, 0, 0, 0.55)",
|
|
1128
|
-
color: "#fff",
|
|
1129
|
-
textShadow: "0 2px 6px rgba(0, 0, 0, 0.45)",
|
|
1130
|
-
userSelect: "none",
|
|
1131
|
-
pointerEvents: "none"
|
|
1132
|
-
// Center the caption on its (x, y) point (x = sceneWidth/2).
|
|
1133
|
-
// We combine translate-centering with the actor transform in the
|
|
1134
|
-
// dataset below so the player can re-apply on state changes.
|
|
1135
|
-
});
|
|
1136
|
-
el = div;
|
|
1137
|
-
break;
|
|
1138
|
-
}
|
|
1139
|
-
case "figure": {
|
|
1140
|
-
el = createFigureEl(def);
|
|
1141
|
-
break;
|
|
1142
|
-
}
|
|
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
|
-
}
|
|
1157
|
-
ensureArchitectureStyles(document);
|
|
1158
|
-
const card = document.createElement("div");
|
|
1159
|
-
card.className = "markdy-arch-node";
|
|
1160
|
-
card.dataset.markdySystemType = def.type;
|
|
1161
|
-
card.dataset.markdySystemKind = architectureNodeKind(def.type);
|
|
1162
|
-
card.setAttribute("role", "img");
|
|
1163
|
-
card.setAttribute("aria-label", `${architectureTypeLabel(def.type)} ${def.args[0] ?? name}`);
|
|
1164
|
-
const icon = document.createElement("span");
|
|
1165
|
-
icon.className = "markdy-arch-node__icon";
|
|
1166
|
-
icon.setAttribute("aria-hidden", "true");
|
|
1167
|
-
const label = document.createElement("div");
|
|
1168
|
-
label.className = "markdy-arch-node__label";
|
|
1169
|
-
label.textContent = def.args[0] ?? "";
|
|
1170
|
-
label.style.fontSize = `${def.size ?? 15}px`;
|
|
1171
|
-
const typeLabel = document.createElement("span");
|
|
1172
|
-
typeLabel.className = "markdy-arch-node__type";
|
|
1173
|
-
typeLabel.textContent = architectureTypeLabel(def.type);
|
|
1174
|
-
label.appendChild(typeLabel);
|
|
1175
|
-
card.appendChild(icon);
|
|
1176
|
-
card.appendChild(label);
|
|
1177
|
-
el = card;
|
|
1178
|
-
break;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
el.dataset.markdyActor = name;
|
|
1182
|
-
el.style.position = "absolute";
|
|
1183
|
-
el.style.left = "0";
|
|
1184
|
-
el.style.top = "0";
|
|
1185
|
-
el.style.transformOrigin = "center center";
|
|
1186
|
-
el.style.transform = def.type === "caption" ? txCaption(stateFrom(def)) : tx(stateFrom(def));
|
|
1187
|
-
el.style.opacity = String(def.opacity ?? 1);
|
|
1188
|
-
if (def.z !== void 0) el.style.zIndex = String(def.z);
|
|
1189
|
-
else if (def.type === "caption") el.style.zIndex = "100";
|
|
1190
|
-
else if (isVisualPrimitiveType(def.type)) el.style.zIndex = "25";
|
|
1191
|
-
else if (isArchitectureNodeType(def.type)) el.style.zIndex = "10";
|
|
1192
|
-
return el;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
// src/camera.ts
|
|
1196
|
-
function freshCameraState() {
|
|
1197
|
-
return { x: 0, y: 0, zoom: 1 };
|
|
1198
|
-
}
|
|
1199
|
-
function cameraTx(s) {
|
|
1200
|
-
return `translate(${-s.x}px, ${-s.y}px) scale(${s.zoom})`;
|
|
1201
|
-
}
|
|
1202
|
-
var DEFAULT_SHAKE_INTENSITY = 8;
|
|
1203
|
-
function buildCameraAction(ev, scene, ast, baseOpts, anims, state) {
|
|
1204
|
-
switch (ev.action) {
|
|
1205
|
-
case "pan": {
|
|
1206
|
-
const to = ev.params.to;
|
|
1207
|
-
if (!to) break;
|
|
1208
|
-
const next = {
|
|
1209
|
-
...state,
|
|
1210
|
-
x: to[0] - ast.meta.width / 2,
|
|
1211
|
-
y: to[1] - ast.meta.height / 2
|
|
1212
|
-
};
|
|
1213
|
-
anims.push(
|
|
1214
|
-
scene.animate([{ transform: cameraTx(state) }, { transform: cameraTx(next) }], baseOpts)
|
|
1215
|
-
);
|
|
1216
|
-
state.x = next.x;
|
|
1217
|
-
state.y = next.y;
|
|
1218
|
-
break;
|
|
1219
|
-
}
|
|
1220
|
-
case "zoom": {
|
|
1221
|
-
const next = {
|
|
1222
|
-
...state,
|
|
1223
|
-
zoom: typeof ev.params.to === "number" ? ev.params.to : state.zoom
|
|
1224
|
-
};
|
|
1225
|
-
anims.push(
|
|
1226
|
-
scene.animate([{ transform: cameraTx(state) }, { transform: cameraTx(next) }], baseOpts)
|
|
1227
|
-
);
|
|
1228
|
-
state.zoom = next.zoom;
|
|
1229
|
-
break;
|
|
1230
|
-
}
|
|
1231
|
-
case "shake": {
|
|
1232
|
-
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : DEFAULT_SHAKE_INTENSITY;
|
|
1233
|
-
const at = (dx, dy, offset) => ({
|
|
1234
|
-
transform: cameraTx({ ...state, x: state.x + dx, y: state.y + dy }),
|
|
1235
|
-
offset
|
|
1236
|
-
});
|
|
1237
|
-
anims.push(
|
|
1238
|
-
scene.animate(
|
|
1239
|
-
[
|
|
1240
|
-
at(0, 0, 0),
|
|
1241
|
-
at(-mag, -mag * 0.4, 0.15),
|
|
1242
|
-
at(mag, mag * 0.4, 0.35),
|
|
1243
|
-
at(-mag * 0.6, mag * 0.3, 0.55),
|
|
1244
|
-
at(mag * 0.5, -mag * 0.3, 0.75),
|
|
1245
|
-
at(0, 0, 1)
|
|
1246
|
-
],
|
|
1247
|
-
{ ...baseOpts, easing: "linear" }
|
|
1248
|
-
)
|
|
1249
|
-
);
|
|
1250
|
-
break;
|
|
1251
|
-
}
|
|
1252
|
-
default:
|
|
1253
|
-
break;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// src/stage.ts
|
|
1258
|
-
function offscreenState(s, direction, sceneWidth, sceneHeight) {
|
|
1259
|
-
const out = { ...s };
|
|
1260
|
-
switch (direction) {
|
|
1261
|
-
case "left":
|
|
1262
|
-
out.x = -sceneWidth * 1.1;
|
|
1263
|
-
break;
|
|
1264
|
-
case "right":
|
|
1265
|
-
out.x = sceneWidth * 2.1;
|
|
1266
|
-
break;
|
|
1267
|
-
case "top":
|
|
1268
|
-
out.y = -sceneHeight * 1.1;
|
|
1269
|
-
break;
|
|
1270
|
-
case "bottom":
|
|
1271
|
-
out.y = sceneHeight * 2.1;
|
|
1272
|
-
break;
|
|
1273
|
-
}
|
|
1274
|
-
return out;
|
|
1275
|
-
}
|
|
1276
|
-
function preInitInlineStyles(ast, actorEls, states, events, txFor) {
|
|
1277
|
-
const firstEventByActor = /* @__PURE__ */ new Map();
|
|
1278
|
-
for (const ev of events) {
|
|
1279
|
-
if (ev.actor === "camera") continue;
|
|
1280
|
-
if (!firstEventByActor.has(ev.actor)) firstEventByActor.set(ev.actor, ev);
|
|
1281
|
-
}
|
|
1282
|
-
for (const [name, def] of Object.entries(ast.actors)) {
|
|
1283
|
-
const el = actorEls.get(name);
|
|
1284
|
-
const s = states.get(name);
|
|
1285
|
-
if (!el || !s) continue;
|
|
1286
|
-
const firstEv = firstEventByActor.get(name);
|
|
1287
|
-
if (!firstEv) continue;
|
|
1288
|
-
if (firstEv.action === "enter") {
|
|
1289
|
-
const from = String(firstEv.params.from ?? "left");
|
|
1290
|
-
el.style.transform = txFor(name)(offscreenState(s, from, ast.meta.width, ast.meta.height));
|
|
1291
|
-
}
|
|
1292
|
-
if (firstEv.action === "fade_in" && (def.opacity === void 0 || def.opacity > 0)) {
|
|
1293
|
-
el.style.opacity = "0";
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
// src/actions/figure.ts
|
|
1299
|
-
function sideOf(ctx) {
|
|
1300
|
-
return String(ctx.ev.params.side ?? "right") === "left" ? "left" : "right";
|
|
1301
|
-
}
|
|
1302
|
-
function partEl(ctx, part) {
|
|
1303
|
-
const selector = PART_SEL[part];
|
|
1304
|
-
return selector ? ctx.el.querySelector(selector) : null;
|
|
1305
|
-
}
|
|
1306
|
-
function rotateKeyframe(deg, offset) {
|
|
1307
|
-
return offset === void 0 ? { transform: `rotate(${deg}deg)` } : { transform: `rotate(${deg}deg)`, offset };
|
|
1308
|
-
}
|
|
1309
|
-
function swingLimb(ctx, part, extendDeg, peakOffset) {
|
|
1310
|
-
const el = partEl(ctx, part);
|
|
1311
|
-
if (!el) return;
|
|
1312
|
-
const rest = readRotation(el);
|
|
1313
|
-
ctx.anims.push(
|
|
1314
|
-
el.animate([rotateKeyframe(rest), rotateKeyframe(extendDeg, peakOffset), rotateKeyframe(rest)], {
|
|
1315
|
-
...ctx.baseOpts,
|
|
1316
|
-
easing: "ease-in-out",
|
|
1317
|
-
fill: "forwards"
|
|
1318
|
-
})
|
|
1319
|
-
);
|
|
1320
|
-
}
|
|
1321
|
-
var punch = (ctx) => {
|
|
1322
|
-
const side = sideOf(ctx);
|
|
1323
|
-
swingLimb(ctx, side === "left" ? "arm_left" : "arm_right", side === "left" ? -75 : 75, 0.35);
|
|
1324
|
-
};
|
|
1325
|
-
var kick = (ctx) => {
|
|
1326
|
-
const side = sideOf(ctx);
|
|
1327
|
-
swingLimb(ctx, side === "left" ? "leg_left" : "leg_right", side === "left" ? -100 : 100, 0.38);
|
|
1328
|
-
};
|
|
1329
|
-
var wave = (ctx) => {
|
|
1330
|
-
const side = sideOf(ctx);
|
|
1331
|
-
const el = partEl(ctx, side === "left" ? "arm_left" : "arm_right");
|
|
1332
|
-
if (!el) return;
|
|
1333
|
-
const rest = readRotation(el);
|
|
1334
|
-
const up = side === "left" ? 70 : -70;
|
|
1335
|
-
const inward = side === "left" ? 50 : -50;
|
|
1336
|
-
ctx.anims.push(
|
|
1337
|
-
el.animate(
|
|
1338
|
-
[
|
|
1339
|
-
rotateKeyframe(rest, 0),
|
|
1340
|
-
rotateKeyframe(up, 0.2),
|
|
1341
|
-
rotateKeyframe(inward, 0.4),
|
|
1342
|
-
rotateKeyframe(up, 0.55),
|
|
1343
|
-
rotateKeyframe(inward, 0.7),
|
|
1344
|
-
rotateKeyframe(up, 0.85),
|
|
1345
|
-
rotateKeyframe(rest, 1)
|
|
1346
|
-
],
|
|
1347
|
-
{ ...ctx.baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
1348
|
-
)
|
|
1349
|
-
);
|
|
1350
|
-
};
|
|
1351
|
-
var nod = (ctx) => {
|
|
1352
|
-
const el = partEl(ctx, "head");
|
|
1353
|
-
if (!el) return;
|
|
1354
|
-
const rest = readRotation(el);
|
|
1355
|
-
const down = 15;
|
|
1356
|
-
ctx.anims.push(
|
|
1357
|
-
el.animate(
|
|
1358
|
-
[
|
|
1359
|
-
rotateKeyframe(rest, 0),
|
|
1360
|
-
rotateKeyframe(down, 0.35),
|
|
1361
|
-
rotateKeyframe(rest, 0.65),
|
|
1362
|
-
rotateKeyframe(down, 0.8),
|
|
1363
|
-
rotateKeyframe(rest, 1)
|
|
1364
|
-
],
|
|
1365
|
-
{ ...ctx.baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
1366
|
-
)
|
|
1367
|
-
);
|
|
1368
|
-
};
|
|
1369
|
-
function commitRotation(el, deg) {
|
|
1370
|
-
el.style.transform = el.style.transform.replace(/rotate\([^)]*\)/, `rotate(${deg}deg)`);
|
|
1371
|
-
}
|
|
1372
|
-
var rotatePart = (ctx) => {
|
|
1373
|
-
const el = partEl(ctx, String(ctx.ev.params.part ?? ""));
|
|
1374
|
-
if (!el) return;
|
|
1375
|
-
const from = readRotation(el);
|
|
1376
|
-
const to = typeof ctx.ev.params.to === "number" ? ctx.ev.params.to : from;
|
|
1377
|
-
ctx.anims.push(
|
|
1378
|
-
el.animate([rotateKeyframe(from), rotateKeyframe(to)], { ...ctx.baseOpts, fill: "forwards" })
|
|
1379
|
-
);
|
|
1380
|
-
commitRotation(el, to);
|
|
1381
|
-
};
|
|
1382
|
-
var POSEABLE_PARTS = ["arm_left", "arm_right", "leg_left", "leg_right", "head", "body"];
|
|
1383
|
-
var pose = (ctx) => {
|
|
1384
|
-
for (const part of POSEABLE_PARTS) {
|
|
1385
|
-
const to = ctx.ev.params[part];
|
|
1386
|
-
if (typeof to !== "number") continue;
|
|
1387
|
-
const el = partEl(ctx, part);
|
|
1388
|
-
if (!el) continue;
|
|
1389
|
-
ctx.anims.push(
|
|
1390
|
-
el.animate([rotateKeyframe(readRotation(el)), rotateKeyframe(to)], {
|
|
1391
|
-
...ctx.baseOpts,
|
|
1392
|
-
fill: "forwards"
|
|
1393
|
-
})
|
|
1394
|
-
);
|
|
1395
|
-
commitRotation(el, to);
|
|
1396
|
-
}
|
|
1397
|
-
};
|
|
1398
|
-
var face = (ctx) => {
|
|
1399
|
-
const el = ctx.el.querySelector("[data-fig-face]");
|
|
1400
|
-
if (!el) return;
|
|
1401
|
-
const emoji = String(ctx.ev.params.text ?? ctx.ev.params._0 ?? "");
|
|
1402
|
-
if (emoji) ctx.faceSwaps.push({ timeMs: ctx.ev.time * 1e3, el, emoji });
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
|
-
// src/actions/transform.ts
|
|
1406
|
-
var move = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1407
|
-
const to = ev.params.to;
|
|
1408
|
-
const toX = to?.[0] ?? state.x;
|
|
1409
|
-
const toY = to?.[1] ?? state.y;
|
|
1410
|
-
anims.push(el.animate([{ transform: tx2(state) }, { transform: tx2({ ...state, x: toX, y: toY }) }], baseOpts));
|
|
1411
|
-
state.x = toX;
|
|
1412
|
-
state.y = toY;
|
|
1413
|
-
};
|
|
1414
|
-
var enter = ({ ev, el, state, baseOpts, ast, anims, tx: tx2 }) => {
|
|
1415
|
-
const from = String(ev.params.from ?? "left");
|
|
1416
|
-
const fromState = offscreenState(state, from, ast.meta.width, ast.meta.height);
|
|
1417
|
-
anims.push(
|
|
1418
|
-
el.animate(
|
|
1419
|
-
[
|
|
1420
|
-
{ transform: tx2(fromState), opacity: state.opacity },
|
|
1421
|
-
{ transform: tx2(state), opacity: 1 }
|
|
1422
|
-
],
|
|
1423
|
-
baseOpts
|
|
1424
|
-
)
|
|
1425
|
-
);
|
|
1426
|
-
state.opacity = 1;
|
|
1427
|
-
};
|
|
1428
|
-
var exit = ({ ev, el, state, baseOpts, ast, anims, tx: tx2 }) => {
|
|
1429
|
-
const to = String(ev.params.to ?? "right");
|
|
1430
|
-
const toState = offscreenState(state, to, ast.meta.width, ast.meta.height);
|
|
1431
|
-
anims.push(
|
|
1432
|
-
el.animate(
|
|
1433
|
-
[
|
|
1434
|
-
{ transform: tx2(state), opacity: state.opacity },
|
|
1435
|
-
{ transform: tx2(toState), opacity: 0 }
|
|
1436
|
-
],
|
|
1437
|
-
baseOpts
|
|
1438
|
-
)
|
|
1439
|
-
);
|
|
1440
|
-
state.x = toState.x;
|
|
1441
|
-
state.y = toState.y;
|
|
1442
|
-
state.opacity = 0;
|
|
1443
|
-
};
|
|
1444
|
-
var fadeIn = ({ el, state, baseOpts, anims }) => {
|
|
1445
|
-
anims.push(el.animate([{ opacity: 0 }, { opacity: 1 }], baseOpts));
|
|
1446
|
-
state.opacity = 1;
|
|
1447
|
-
};
|
|
1448
|
-
var fadeOut = ({ el, state, baseOpts, anims }) => {
|
|
1449
|
-
anims.push(el.animate([{ opacity: state.opacity }, { opacity: 0 }], baseOpts));
|
|
1450
|
-
state.opacity = 0;
|
|
1451
|
-
};
|
|
1452
|
-
var scale = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1453
|
-
const to = typeof ev.params.to === "number" ? ev.params.to : state.scale;
|
|
1454
|
-
anims.push(el.animate([{ transform: tx2(state) }, { transform: tx2({ ...state, scale: to }) }], baseOpts));
|
|
1455
|
-
state.scale = to;
|
|
1456
|
-
};
|
|
1457
|
-
var rotate = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1458
|
-
const to = typeof ev.params.to === "number" ? ev.params.to : state.rotate;
|
|
1459
|
-
anims.push(el.animate([{ transform: tx2(state) }, { transform: tx2({ ...state, rotate: to }) }], baseOpts));
|
|
1460
|
-
state.rotate = to;
|
|
1461
|
-
};
|
|
1462
|
-
var shake = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1463
|
-
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : 5;
|
|
1464
|
-
anims.push(
|
|
1465
|
-
el.animate(
|
|
1466
|
-
[
|
|
1467
|
-
{ transform: tx2(state), offset: 0 },
|
|
1468
|
-
{ transform: tx2({ ...state, x: state.x + mag }), offset: 0.2 },
|
|
1469
|
-
{ transform: tx2({ ...state, x: state.x - mag }), offset: 0.4 },
|
|
1470
|
-
{ transform: tx2({ ...state, x: state.x + mag }), offset: 0.6 },
|
|
1471
|
-
{ transform: tx2({ ...state, x: state.x - mag }), offset: 0.8 },
|
|
1472
|
-
{ transform: tx2(state), offset: 1 }
|
|
1473
|
-
],
|
|
1474
|
-
{ ...baseOpts, easing: "linear" }
|
|
1475
|
-
)
|
|
1476
|
-
);
|
|
1477
|
-
};
|
|
1478
|
-
var jump = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1479
|
-
const height = typeof ev.params.height === "number" ? ev.params.height : 30;
|
|
1480
|
-
anims.push(
|
|
1481
|
-
el.animate(
|
|
1482
|
-
[
|
|
1483
|
-
{ transform: tx2(state), offset: 0 },
|
|
1484
|
-
{ transform: tx2({ ...state, scale: state.scale * 0.9 }), offset: 0.1 },
|
|
1485
|
-
{ transform: tx2({ ...state, y: state.y - height, scale: state.scale * 1.1 }), offset: 0.45 },
|
|
1486
|
-
{ transform: tx2({ ...state, y: state.y - height * 0.3, scale: state.scale * 1.05 }), offset: 0.7 },
|
|
1487
|
-
{ transform: tx2({ ...state, scale: state.scale * 0.92 }), offset: 0.88 },
|
|
1488
|
-
{ transform: tx2(state), offset: 1 }
|
|
1489
|
-
],
|
|
1490
|
-
{ ...baseOpts, easing: "ease-in-out" }
|
|
1491
|
-
)
|
|
1492
|
-
);
|
|
1493
|
-
};
|
|
1494
|
-
var bounce = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1495
|
-
const intensity = typeof ev.params.intensity === "number" ? ev.params.intensity : 15;
|
|
1496
|
-
const count = typeof ev.params.count === "number" ? ev.params.count : 3;
|
|
1497
|
-
const keyframes = [{ transform: tx2(state), offset: 0 }];
|
|
1498
|
-
for (let i = 0; i < count; i++) {
|
|
1499
|
-
const amp = intensity * Math.pow(0.55, i);
|
|
1500
|
-
const baseOffset = (i + 0.5) / (count + 0.5);
|
|
1501
|
-
const peakOffset = Math.min(baseOffset, 0.98);
|
|
1502
|
-
const valleyOffset = Math.min(baseOffset + 0.25 / (count + 0.5), 0.99);
|
|
1503
|
-
keyframes.push({ transform: tx2({ ...state, y: state.y - amp }), offset: peakOffset });
|
|
1504
|
-
if (i < count - 1) {
|
|
1505
|
-
keyframes.push({ transform: tx2(state), offset: valleyOffset });
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
keyframes.push({ transform: tx2(state), offset: 1 });
|
|
1509
|
-
anims.push(el.animate(keyframes, { ...baseOpts, easing: "ease-out" }));
|
|
1510
|
-
};
|
|
1511
|
-
|
|
1512
|
-
// src/actions/effects.ts
|
|
1513
|
-
function numeric(value, fallback) {
|
|
1514
|
-
return typeof value === "number" ? value : fallback;
|
|
1515
|
-
}
|
|
1516
|
-
function stringParam(value, fallback) {
|
|
1517
|
-
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
1518
|
-
}
|
|
1519
|
-
var spring = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1520
|
-
const to = ev.params.to;
|
|
1521
|
-
const toX = to?.[0] ?? state.x;
|
|
1522
|
-
const toY = to?.[1] ?? state.y;
|
|
1523
|
-
const stiffness = numeric(ev.params.stiffness, 0.18);
|
|
1524
|
-
const overshootX = toX + (toX - state.x) * stiffness;
|
|
1525
|
-
const overshootY = toY + (toY - state.y) * stiffness;
|
|
1526
|
-
anims.push(
|
|
1527
|
-
el.animate(
|
|
1528
|
-
[
|
|
1529
|
-
{ transform: tx2(state), offset: 0 },
|
|
1530
|
-
{ transform: tx2({ ...state, x: overshootX, y: overshootY }), offset: 0.72 },
|
|
1531
|
-
{ transform: tx2({ ...state, x: toX, y: toY }), offset: 1 }
|
|
1532
|
-
],
|
|
1533
|
-
{ ...baseOpts, easing: "cubic-bezier(.2,1.35,.35,1)" }
|
|
1534
|
-
)
|
|
1535
|
-
);
|
|
1536
|
-
state.x = toX;
|
|
1537
|
-
state.y = toY;
|
|
1538
|
-
};
|
|
1539
|
-
var followPath = ({ ev, el, baseOpts, anims }) => {
|
|
1540
|
-
const path = stringParam(ev.params.path, "M 0 0 L 120 0");
|
|
1541
|
-
const rotate2 = ev.params.rotate === false ? "0deg" : "auto";
|
|
1542
|
-
anims.push(
|
|
1543
|
-
el.animate(
|
|
1544
|
-
[
|
|
1545
|
-
{ offsetPath: `path('${path}')`, offsetDistance: "0%", offsetRotate: rotate2 },
|
|
1546
|
-
{ offsetPath: `path('${path}')`, offsetDistance: "100%", offsetRotate: rotate2 }
|
|
1547
|
-
],
|
|
1548
|
-
baseOpts
|
|
1549
|
-
)
|
|
1550
|
-
);
|
|
1551
|
-
};
|
|
1552
|
-
var pulse = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1553
|
-
const amount = numeric(ev.params.amount, 1.08);
|
|
1554
|
-
anims.push(
|
|
1555
|
-
el.animate(
|
|
1556
|
-
[
|
|
1557
|
-
{ transform: tx2(state), offset: 0 },
|
|
1558
|
-
{ transform: tx2({ ...state, scale: state.scale * amount }), offset: 0.48 },
|
|
1559
|
-
{ transform: tx2(state), offset: 1 }
|
|
1560
|
-
],
|
|
1561
|
-
{ ...baseOpts, easing: "ease-in-out" }
|
|
1562
|
-
)
|
|
1563
|
-
);
|
|
1564
|
-
};
|
|
1565
|
-
var glow = ({ ev, el, baseOpts, anims }) => {
|
|
1566
|
-
const color = stringParam(ev.params.color, "#38bdf8");
|
|
1567
|
-
const strength = numeric(ev.params.strength, 24);
|
|
1568
|
-
anims.push(
|
|
1569
|
-
el.animate(
|
|
1570
|
-
[
|
|
1571
|
-
{ filter: "drop-shadow(0 0 0 transparent)", boxShadow: "0 0 0 rgba(0,0,0,0)" },
|
|
1572
|
-
{ filter: `drop-shadow(0 0 ${strength}px ${color})`, boxShadow: `0 0 ${strength}px ${color}` },
|
|
1573
|
-
{ filter: "drop-shadow(0 0 0 transparent)", boxShadow: "0 0 0 rgba(0,0,0,0)" }
|
|
1574
|
-
],
|
|
1575
|
-
{ ...baseOpts, easing: "ease-in-out" }
|
|
1576
|
-
)
|
|
1577
|
-
);
|
|
1578
|
-
};
|
|
1579
|
-
var ripple = ({ ev, el, delayMs, durMs, anims }) => {
|
|
1580
|
-
const color = stringParam(ev.params.color, "#38bdf8");
|
|
1581
|
-
const size = numeric(ev.params.size, 140);
|
|
1582
|
-
const ring = document.createElement("span");
|
|
1583
|
-
ring.setAttribute("data-markdy-ripple", "1");
|
|
1584
|
-
Object.assign(ring.style, {
|
|
1585
|
-
position: "absolute",
|
|
1586
|
-
left: "50%",
|
|
1587
|
-
top: "50%",
|
|
1588
|
-
width: `${size}px`,
|
|
1589
|
-
height: `${size}px`,
|
|
1590
|
-
marginLeft: `${-size / 2}px`,
|
|
1591
|
-
marginTop: `${-size / 2}px`,
|
|
1592
|
-
border: `2px solid ${color}`,
|
|
1593
|
-
borderRadius: "999px",
|
|
1594
|
-
pointerEvents: "none",
|
|
1595
|
-
opacity: "0"
|
|
1596
|
-
});
|
|
1597
|
-
el.appendChild(ring);
|
|
1598
|
-
anims.push(
|
|
1599
|
-
ring.animate(
|
|
1600
|
-
[
|
|
1601
|
-
{ transform: "scale(0.2)", opacity: 0.6 },
|
|
1602
|
-
{ transform: "scale(1)", opacity: 0 }
|
|
1603
|
-
],
|
|
1604
|
-
{ delay: delayMs, duration: durMs, fill: "forwards", easing: "ease-out" }
|
|
1605
|
-
)
|
|
1606
|
-
);
|
|
1607
|
-
};
|
|
1608
|
-
var blur = ({ ev, el, baseOpts, anims }) => {
|
|
1609
|
-
const from = numeric(ev.params.from, 0);
|
|
1610
|
-
const to = numeric(ev.params.to, 8);
|
|
1611
|
-
anims.push(el.animate([{ filter: `blur(${from}px)` }, { filter: `blur(${to}px)` }], baseOpts));
|
|
1612
|
-
};
|
|
1613
|
-
var lineReveal = ({ ev, el, baseOpts, anims }) => {
|
|
1614
|
-
const from = stringParam(ev.params.from, "left");
|
|
1615
|
-
const start = from === "right" ? "inset(0 0 0 100%)" : "inset(0 100% 0 0)";
|
|
1616
|
-
anims.push(el.animate([{ clipPath: start }, { clipPath: "inset(0 0 0 0)" }], baseOpts));
|
|
1617
|
-
};
|
|
1618
|
-
var mask = ({ ev, el, baseOpts, anims }) => {
|
|
1619
|
-
const from = numeric(ev.params.from, 0);
|
|
1620
|
-
const to = numeric(ev.params.to, 120);
|
|
1621
|
-
anims.push(
|
|
1622
|
-
el.animate(
|
|
1623
|
-
[
|
|
1624
|
-
{ clipPath: `circle(${from}% at 50% 50%)` },
|
|
1625
|
-
{ clipPath: `circle(${to}% at 50% 50%)` }
|
|
1626
|
-
],
|
|
1627
|
-
baseOpts
|
|
1628
|
-
)
|
|
1629
|
-
);
|
|
1630
|
-
};
|
|
1631
|
-
var parallax = ({ ev, el, state, baseOpts, anims, tx: tx2 }) => {
|
|
1632
|
-
const depth = numeric(ev.params.depth, 0.35);
|
|
1633
|
-
const by = ev.params.by;
|
|
1634
|
-
const dx = (by?.[0] ?? 24) * depth;
|
|
1635
|
-
const dy = (by?.[1] ?? 0) * depth;
|
|
1636
|
-
anims.push(
|
|
1637
|
-
el.animate(
|
|
1638
|
-
[
|
|
1639
|
-
{ transform: tx2(state) },
|
|
1640
|
-
{ transform: tx2({ ...state, x: state.x + dx, y: state.y + dy }) }
|
|
1641
|
-
],
|
|
1642
|
-
baseOpts
|
|
1643
|
-
)
|
|
1644
|
-
);
|
|
1645
|
-
};
|
|
2
|
+
import { parseAndCompile } from "@markdy/core";
|
|
1646
3
|
|
|
1647
4
|
// src/geometry/rect.ts
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
var ACTOR_SIZES = {
|
|
1651
|
-
...Object.fromEntries(TECHNICAL_NODE_TYPES.map((type) => [type, SYSTEM_NODE_SIZE])),
|
|
1652
|
-
box: { width: 100, height: 100 },
|
|
1653
|
-
caption: { width: 260, height: 56 },
|
|
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 },
|
|
1670
|
-
parking_map: { width: 390, height: 250 },
|
|
1671
|
-
ascii_map: { width: 390, height: 250 },
|
|
1672
|
-
game_scene: { width: 390, height: 250 },
|
|
1673
|
-
byte_viz: { width: 390, height: 250 }
|
|
1674
|
-
};
|
|
1675
|
-
var DEFAULT_ACTOR_SIZE = { width: 140, height: 42 };
|
|
1676
|
-
function actorSizeByType(type) {
|
|
1677
|
-
return ACTOR_SIZES[type] ?? DEFAULT_ACTOR_SIZE;
|
|
1678
|
-
}
|
|
1679
|
-
function actorCenter(state, actorType) {
|
|
1680
|
-
const rect = actorRect(state, actorType);
|
|
1681
|
-
return { x: rect.x1 + (rect.x2 - rect.x1) / 2, y: rect.y1 + (rect.y2 - rect.y1) / 2 };
|
|
5
|
+
function boxRect(box) {
|
|
6
|
+
return { x1: box.x, y1: box.y, x2: box.x + box.width, y2: box.y + box.height };
|
|
1682
7
|
}
|
|
1683
|
-
function
|
|
1684
|
-
|
|
1685
|
-
const scale2 = Math.max(1e-3, state.scale);
|
|
1686
|
-
const scaledWidth = width * scale2;
|
|
1687
|
-
const scaledHeight = height * scale2;
|
|
1688
|
-
const dx = (scaledWidth - width) / 2;
|
|
1689
|
-
const dy = (scaledHeight - height) / 2;
|
|
1690
|
-
return {
|
|
1691
|
-
x1: state.x - dx,
|
|
1692
|
-
y1: state.y - dy,
|
|
1693
|
-
x2: state.x + width + dx,
|
|
1694
|
-
y2: state.y + height + dy
|
|
1695
|
-
};
|
|
8
|
+
function rectCenter(rect) {
|
|
9
|
+
return { x: (rect.x1 + rect.x2) / 2, y: (rect.y1 + rect.y2) / 2 };
|
|
1696
10
|
}
|
|
1697
11
|
function inflateRect(rect, pad) {
|
|
1698
12
|
return {
|
|
@@ -1764,19 +78,19 @@ function labelPointForPath(points, lane) {
|
|
|
1764
78
|
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
1765
79
|
const offset = lane * 8;
|
|
1766
80
|
if (Math.abs(a.x - b.x) >= Math.abs(a.y - b.y)) {
|
|
1767
|
-
return { x: mid.x, y: mid.y - 10 - offset };
|
|
81
|
+
return { x: round1(mid.x), y: round1(mid.y - 10 - offset) };
|
|
1768
82
|
}
|
|
1769
|
-
return { x: mid.x + 10 + offset, y: mid.y };
|
|
83
|
+
return { x: round1(mid.x + 10 + offset), y: round1(mid.y) };
|
|
1770
84
|
}
|
|
1771
85
|
function clamp(n, min, max) {
|
|
1772
86
|
if (max < min) return n;
|
|
1773
87
|
return Math.min(max, Math.max(min, n));
|
|
1774
88
|
}
|
|
1775
|
-
function clampPointToScene(point,
|
|
89
|
+
function clampPointToScene(point, bounds) {
|
|
1776
90
|
const pad = 14;
|
|
1777
91
|
return {
|
|
1778
|
-
x: round1(clamp(point.x, pad,
|
|
1779
|
-
y: round1(clamp(point.y, pad,
|
|
92
|
+
x: round1(clamp(point.x, pad, bounds.width - pad)),
|
|
93
|
+
y: round1(clamp(point.y, pad, bounds.height - pad))
|
|
1780
94
|
};
|
|
1781
95
|
}
|
|
1782
96
|
function routeLength(points) {
|
|
@@ -1798,23 +112,14 @@ function routeBends(points) {
|
|
|
1798
112
|
}
|
|
1799
113
|
return bends;
|
|
1800
114
|
}
|
|
1801
|
-
function
|
|
1802
|
-
const sourceType = ast.actors[sourceName]?.type ?? "box";
|
|
1803
|
-
const targetType = ast.actors[targetName]?.type ?? "box";
|
|
1804
|
-
const sourceRect = actorRect(sourceState, sourceType);
|
|
1805
|
-
const targetRect = actorRect(targetState, targetType);
|
|
115
|
+
function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
|
|
1806
116
|
const laneShift = lane * 18;
|
|
1807
|
-
const sourceCenter =
|
|
1808
|
-
const targetCenter =
|
|
117
|
+
const sourceCenter = rectCenter(sourceRect);
|
|
118
|
+
const targetCenter = rectCenter(targetRect);
|
|
1809
119
|
const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
|
|
1810
120
|
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 };
|
|
1811
121
|
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 };
|
|
1812
|
-
const
|
|
1813
|
-
for (const [name, state] of states.entries()) {
|
|
1814
|
-
if (name === sourceName || name === targetName) continue;
|
|
1815
|
-
const type = ast.actors[name]?.type ?? "box";
|
|
1816
|
-
obstacles.push(inflateRect(actorRect(state, type), 8));
|
|
1817
|
-
}
|
|
122
|
+
const infl = obstacles.map((o) => inflateRect(o, 8));
|
|
1818
123
|
const candidates = [];
|
|
1819
124
|
if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
|
|
1820
125
|
candidates.push([source, target]);
|
|
@@ -1825,18 +130,18 @@ function routeFlowPath(sourceName, targetName, sourceState, targetState, states,
|
|
|
1825
130
|
[source, { x: midX, y: source.y }, { x: midX, y: target.y }, target],
|
|
1826
131
|
[source, { x: source.x, y: midY }, { x: target.x, y: midY }, target]
|
|
1827
132
|
);
|
|
1828
|
-
const minObstacleY =
|
|
1829
|
-
const maxObstacleY =
|
|
133
|
+
const minObstacleY = infl.length ? Math.min(...infl.map((o) => o.y1)) : Math.min(source.y, target.y);
|
|
134
|
+
const maxObstacleY = infl.length ? Math.max(...infl.map((o) => o.y2)) : Math.max(source.y, target.y);
|
|
1830
135
|
const topLane = Math.max(16, minObstacleY - 24 - lane * 12);
|
|
1831
|
-
const bottomLane = Math.min(
|
|
136
|
+
const bottomLane = Math.min(bounds.height - 16, maxObstacleY + 24 + lane * 12);
|
|
1832
137
|
candidates.push(
|
|
1833
138
|
[source, { x: source.x, y: topLane }, { x: target.x, y: topLane }, target],
|
|
1834
139
|
[source, { x: source.x, y: bottomLane }, { x: target.x, y: bottomLane }, target]
|
|
1835
140
|
);
|
|
1836
|
-
const minObstacleX =
|
|
1837
|
-
const maxObstacleX =
|
|
141
|
+
const minObstacleX = infl.length ? Math.min(...infl.map((o) => o.x1)) : Math.min(source.x, target.x);
|
|
142
|
+
const maxObstacleX = infl.length ? Math.max(...infl.map((o) => o.x2)) : Math.max(source.x, target.x);
|
|
1838
143
|
const leftLane = Math.max(16, minObstacleX - 24 - lane * 12);
|
|
1839
|
-
const rightLane = Math.min(
|
|
144
|
+
const rightLane = Math.min(bounds.width - 16, maxObstacleX + 24 + lane * 12);
|
|
1840
145
|
candidates.push(
|
|
1841
146
|
[source, { x: leftLane, y: source.y }, { x: leftLane, y: target.y }, target],
|
|
1842
147
|
[source, { x: rightLane, y: source.y }, { x: rightLane, y: target.y }, target]
|
|
@@ -1844,26 +149,32 @@ function routeFlowPath(sourceName, targetName, sourceState, targetState, states,
|
|
|
1844
149
|
let best = candidates[0];
|
|
1845
150
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
1846
151
|
for (const candidate of candidates) {
|
|
1847
|
-
const hits = countPathIntersections(candidate,
|
|
152
|
+
const hits = countPathIntersections(candidate, infl);
|
|
1848
153
|
const score = hits * 1e5 + routeBends(candidate) * 800 + routeLength(candidate);
|
|
1849
154
|
if (score < bestScore) {
|
|
1850
155
|
best = candidate;
|
|
1851
156
|
bestScore = score;
|
|
1852
157
|
}
|
|
1853
158
|
}
|
|
1854
|
-
return best.map((point) => clampPointToScene(point,
|
|
159
|
+
return best.map((point) => clampPointToScene(point, bounds));
|
|
1855
160
|
}
|
|
1856
161
|
|
|
1857
|
-
// src/
|
|
162
|
+
// src/edges.ts
|
|
1858
163
|
var EDGE_LAYER_ATTR = "data-markdy-edge-layer";
|
|
1859
|
-
var
|
|
1860
|
-
request: "
|
|
1861
|
-
response: "
|
|
1862
|
-
|
|
164
|
+
var EDGE_STYLES = {
|
|
165
|
+
request: { dash: "", marker: "arrow" },
|
|
166
|
+
response: { dash: "6 4", marker: "arrow-open" },
|
|
167
|
+
event: { dash: "2 6", marker: "dot" },
|
|
168
|
+
dependency: { dash: "4 4", marker: "none" }
|
|
1863
169
|
};
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
170
|
+
function dedupePoints(points) {
|
|
171
|
+
const out = [];
|
|
172
|
+
for (const p of points) {
|
|
173
|
+
const last = out[out.length - 1];
|
|
174
|
+
if (!last || Math.abs(last.x - p.x) > 0.01 || Math.abs(last.y - p.y) > 0.01) out.push(p);
|
|
175
|
+
}
|
|
176
|
+
return out.length >= 2 ? out : points;
|
|
177
|
+
}
|
|
1867
178
|
function ensureEdgeLayer(scene) {
|
|
1868
179
|
const existing = scene.querySelector(`svg[${EDGE_LAYER_ATTR}='1']`);
|
|
1869
180
|
if (existing) return existing;
|
|
@@ -1881,388 +192,322 @@ function ensureEdgeLayer(scene) {
|
|
|
1881
192
|
scene.appendChild(svg);
|
|
1882
193
|
return svg;
|
|
1883
194
|
}
|
|
1884
|
-
function
|
|
1885
|
-
|
|
195
|
+
function ensureDefs(svg, theme, id) {
|
|
196
|
+
const key = `data-markdy-defs-${id}`;
|
|
197
|
+
if (svg.querySelector(`defs[${key}='1']`)) return;
|
|
1886
198
|
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
1887
|
-
defs.setAttribute(
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
glow2.append(blur2, merge);
|
|
1904
|
-
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
1905
|
-
arrow.setAttribute("id", "markdy-flow-arrow");
|
|
1906
|
-
arrow.setAttribute("viewBox", "0 0 10 10");
|
|
1907
|
-
arrow.setAttribute("refX", "8");
|
|
1908
|
-
arrow.setAttribute("refY", "5");
|
|
1909
|
-
arrow.setAttribute("markerWidth", "6");
|
|
1910
|
-
arrow.setAttribute("markerHeight", "6");
|
|
1911
|
-
arrow.setAttribute("orient", "auto-start-reverse");
|
|
1912
|
-
const arrowPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1913
|
-
arrowPath.setAttribute("d", "M 1 1 L 9 5 L 1 9 z");
|
|
1914
|
-
arrowPath.setAttribute("fill", "context-stroke");
|
|
1915
|
-
arrow.appendChild(arrowPath);
|
|
1916
|
-
defs.append(glow2, arrow);
|
|
199
|
+
defs.setAttribute(key, "1");
|
|
200
|
+
for (const [kind, color] of Object.entries(theme.edges)) {
|
|
201
|
+
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
202
|
+
arrow.setAttribute("id", `${id}-arrow-${kind}`);
|
|
203
|
+
arrow.setAttribute("viewBox", "0 0 10 10");
|
|
204
|
+
arrow.setAttribute("refX", "8");
|
|
205
|
+
arrow.setAttribute("refY", "5");
|
|
206
|
+
arrow.setAttribute("markerWidth", "6");
|
|
207
|
+
arrow.setAttribute("markerHeight", "6");
|
|
208
|
+
arrow.setAttribute("orient", "auto-start-reverse");
|
|
209
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
210
|
+
path.setAttribute("d", "M 1 1 L 9 5 L 1 9 z");
|
|
211
|
+
path.setAttribute("fill", color);
|
|
212
|
+
arrow.appendChild(path);
|
|
213
|
+
defs.appendChild(arrow);
|
|
214
|
+
}
|
|
1917
215
|
svg.prepend(defs);
|
|
1918
216
|
}
|
|
1919
|
-
function
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
const
|
|
1925
|
-
const
|
|
1926
|
-
if (!targetState) return;
|
|
1927
|
-
const lane = ev.line % 5 - 2;
|
|
1928
|
-
const points = routeFlowPath(ev.actor, targetName, state, targetState, states, ast, lane);
|
|
1929
|
-
const length = polylineLength(points);
|
|
1930
|
-
const pathD = toPathD(points);
|
|
1931
|
-
const stroke = STROKE_BY_ACTION[ev.action] ?? DEFAULT_STROKE;
|
|
1932
|
-
const svg = ensureEdgeLayer(scene);
|
|
1933
|
-
ensureEdgeDefs(svg);
|
|
217
|
+
function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, obstacles, bounds) {
|
|
218
|
+
ensureDefs(svg, theme, sceneId);
|
|
219
|
+
const color = theme.edges[kind];
|
|
220
|
+
const style = EDGE_STYLES[kind];
|
|
221
|
+
const points = dedupePoints(routeOrthogonal(boxRect(from), boxRect(to), obstacles, bounds));
|
|
222
|
+
const d = toPathD(points);
|
|
223
|
+
const len = polylineLength(points);
|
|
1934
224
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1935
|
-
group.setAttribute("data-
|
|
225
|
+
group.setAttribute("data-edge-kind", kind);
|
|
226
|
+
group.style.opacity = "0";
|
|
1936
227
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1937
|
-
path.setAttribute("d",
|
|
228
|
+
path.setAttribute("d", d);
|
|
1938
229
|
path.setAttribute("fill", "none");
|
|
1939
|
-
path.setAttribute("stroke",
|
|
1940
|
-
path.setAttribute("stroke-width", "
|
|
1941
|
-
path.setAttribute("stroke-linecap", "round");
|
|
230
|
+
path.setAttribute("stroke", color);
|
|
231
|
+
path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2.5");
|
|
1942
232
|
path.setAttribute("stroke-linejoin", "round");
|
|
1943
|
-
path.setAttribute("
|
|
1944
|
-
path.setAttribute("
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
label.setAttribute("stroke-width", "5");
|
|
1969
|
-
label.setAttribute("stroke-linejoin", "round");
|
|
1970
|
-
label.setAttribute("fill", "#cbd5e1");
|
|
1971
|
-
label.setAttribute("filter", "url(#markdy-flow-glow)");
|
|
1972
|
-
label.setAttribute("data-full-label", labelText);
|
|
1973
|
-
label.textContent = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
|
|
1974
|
-
group.appendChild(label);
|
|
233
|
+
path.setAttribute("stroke-linecap", "round");
|
|
234
|
+
if (style.dash) path.setAttribute("stroke-dasharray", style.dash);
|
|
235
|
+
if (style.marker !== "none") {
|
|
236
|
+
path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
|
|
237
|
+
}
|
|
238
|
+
path.style.filter = `drop-shadow(0 0 4px ${color}55)`;
|
|
239
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
240
|
+
dot.setAttribute("r", "4");
|
|
241
|
+
dot.setAttribute("fill", color);
|
|
242
|
+
dot.style.opacity = "0";
|
|
243
|
+
dot.style.filter = `drop-shadow(0 0 6px ${color})`;
|
|
244
|
+
group.append(path, dot);
|
|
245
|
+
let labelEl;
|
|
246
|
+
if (label) {
|
|
247
|
+
const mid = labelPointForPath(points, 0);
|
|
248
|
+
labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
249
|
+
labelEl.setAttribute("x", String(mid.x));
|
|
250
|
+
labelEl.setAttribute("y", String(mid.y));
|
|
251
|
+
labelEl.setAttribute("text-anchor", "middle");
|
|
252
|
+
labelEl.setAttribute("font-size", "11");
|
|
253
|
+
labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, monospace");
|
|
254
|
+
labelEl.setAttribute("fill", theme.textMuted);
|
|
255
|
+
labelEl.textContent = label;
|
|
256
|
+
labelEl.style.opacity = "0";
|
|
257
|
+
group.appendChild(labelEl);
|
|
1975
258
|
}
|
|
1976
259
|
svg.appendChild(group);
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
),
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const targetName = String(ctx.ev.params.to ?? "");
|
|
1995
|
-
if (targetName) buildEdge(ctx, targetName);
|
|
1996
|
-
};
|
|
1997
|
-
|
|
1998
|
-
// src/theme.ts
|
|
1999
|
-
var DARK_LUMINANCE_THRESHOLD = 140;
|
|
2000
|
-
var NAMED_DARK = {
|
|
2001
|
-
black: true,
|
|
2002
|
-
"#000": true,
|
|
2003
|
-
"#000000": true
|
|
2004
|
-
};
|
|
2005
|
-
function isSceneDark(scene) {
|
|
2006
|
-
const bg = scene.style.background || "white";
|
|
2007
|
-
let hex = bg.trim().replace(/^#/, "");
|
|
2008
|
-
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
2009
|
-
if (hex.length === 6) {
|
|
2010
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
2011
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
2012
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
2013
|
-
return 0.299 * r + 0.587 * g + 0.114 * b <= DARK_LUMINANCE_THRESHOLD;
|
|
2014
|
-
}
|
|
2015
|
-
return NAMED_DARK[bg.toLowerCase()] ?? false;
|
|
2016
|
-
}
|
|
2017
|
-
function speechBubbleTheme(dark) {
|
|
2018
|
-
return dark ? {
|
|
2019
|
-
background: "#1e2530",
|
|
2020
|
-
border: "#475569",
|
|
2021
|
-
text: "#e2e8f0",
|
|
2022
|
-
shadow: "0 2px 8px rgba(0,0,0,0.35)"
|
|
2023
|
-
} : {
|
|
2024
|
-
background: "white",
|
|
2025
|
-
border: "#222",
|
|
2026
|
-
text: "#222",
|
|
2027
|
-
shadow: "0 2px 8px rgba(0,0,0,0.12)"
|
|
2028
|
-
};
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
// src/actions/speech.ts
|
|
2032
|
-
var MAX_FADE_MS = 200;
|
|
2033
|
-
var FADE_FRACTION = 0.15;
|
|
2034
|
-
var say = ({ ev, el, state, delayMs, durMs, scene, anims }) => {
|
|
2035
|
-
const text = String(ev.params.text ?? "");
|
|
2036
|
-
const theme = speechBubbleTheme(isSceneDark(scene));
|
|
2037
|
-
const inverseScale = 1 / (state.scale || 1);
|
|
2038
|
-
const bubble = document.createElement("div");
|
|
2039
|
-
bubble.textContent = text;
|
|
2040
|
-
Object.assign(bubble.style, {
|
|
2041
|
-
position: "absolute",
|
|
2042
|
-
bottom: "calc(100% + 10px)",
|
|
2043
|
-
left: "50%",
|
|
2044
|
-
transform: `translateX(-50%) scale(${inverseScale})`,
|
|
2045
|
-
transformOrigin: "center bottom",
|
|
2046
|
-
background: theme.background,
|
|
2047
|
-
border: `2px solid ${theme.border}`,
|
|
2048
|
-
color: theme.text,
|
|
2049
|
-
borderRadius: "12px",
|
|
2050
|
-
padding: "6px 14px",
|
|
2051
|
-
fontFamily: "system-ui, sans-serif",
|
|
2052
|
-
fontSize: "15px",
|
|
2053
|
-
lineHeight: "1.3",
|
|
2054
|
-
whiteSpace: "nowrap",
|
|
2055
|
-
maxWidth: "220px",
|
|
2056
|
-
overflow: "hidden",
|
|
2057
|
-
textOverflow: "ellipsis",
|
|
2058
|
-
pointerEvents: "none",
|
|
2059
|
-
zIndex: "10",
|
|
2060
|
-
boxShadow: theme.shadow,
|
|
2061
|
-
opacity: "0"
|
|
2062
|
-
});
|
|
2063
|
-
const tail = document.createElement("span");
|
|
2064
|
-
Object.assign(tail.style, {
|
|
2065
|
-
position: "absolute",
|
|
2066
|
-
bottom: "-10px",
|
|
2067
|
-
left: "50%",
|
|
2068
|
-
transform: "translateX(-50%)",
|
|
2069
|
-
width: "0",
|
|
2070
|
-
height: "0",
|
|
2071
|
-
borderLeft: "7px solid transparent",
|
|
2072
|
-
borderRight: "7px solid transparent",
|
|
2073
|
-
borderTop: `10px solid ${theme.border}`
|
|
260
|
+
return { group, path, label: labelEl, dot, pathLen: len, points };
|
|
261
|
+
}
|
|
262
|
+
function dotTravelKeyframes(points) {
|
|
263
|
+
const total = polylineLength(points);
|
|
264
|
+
const frames = [];
|
|
265
|
+
let acc = 0;
|
|
266
|
+
let lastOffset = -1;
|
|
267
|
+
points.forEach((p, i) => {
|
|
268
|
+
if (i > 0) acc += Math.hypot(p.x - points[i - 1].x, p.y - points[i - 1].y);
|
|
269
|
+
let offset = total > 0 ? acc / total : i === points.length - 1 ? 1 : 0;
|
|
270
|
+
if (offset <= lastOffset) offset = Math.min(1, lastOffset + 1e-4);
|
|
271
|
+
lastOffset = offset;
|
|
272
|
+
frames.push({
|
|
273
|
+
offset,
|
|
274
|
+
transform: `translate(${p.x}px, ${p.y}px)`,
|
|
275
|
+
opacity: i === 0 || i === points.length - 1 ? 0 : 1
|
|
276
|
+
});
|
|
2074
277
|
});
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
const
|
|
278
|
+
return frames;
|
|
279
|
+
}
|
|
280
|
+
function animateEdgeReveal(runtime, startMs, durMs) {
|
|
281
|
+
const anims = [];
|
|
282
|
+
const { path, dot, label, group, pathLen, points } = runtime;
|
|
283
|
+
path.style.strokeDasharray = String(pathLen);
|
|
284
|
+
path.style.strokeDashoffset = String(pathLen);
|
|
285
|
+
const drawMs = Math.min(220, durMs * 0.5);
|
|
2079
286
|
anims.push(
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
fill: "forwards"
|
|
2084
|
-
|
|
2085
|
-
bubble.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
2086
|
-
delay: delayMs + durMs - fadeMs,
|
|
2087
|
-
duration: fadeMs,
|
|
2088
|
-
fill: "forwards"
|
|
2089
|
-
})
|
|
287
|
+
group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 120, delay: startMs, fill: "forwards" }),
|
|
288
|
+
path.animate(
|
|
289
|
+
[{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
|
|
290
|
+
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "ease-out" }
|
|
291
|
+
)
|
|
2090
292
|
);
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
return img;
|
|
293
|
+
if (label) {
|
|
294
|
+
anims.push(label.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
295
|
+
}
|
|
296
|
+
if (points.length >= 2) {
|
|
297
|
+
anims.push(
|
|
298
|
+
dot.animate(dotTravelKeyframes(points), {
|
|
299
|
+
duration: Math.max(drawMs, durMs),
|
|
300
|
+
delay: startMs,
|
|
301
|
+
fill: "forwards",
|
|
302
|
+
easing: "ease-in-out"
|
|
303
|
+
})
|
|
304
|
+
);
|
|
2104
305
|
}
|
|
2105
|
-
|
|
2106
|
-
span.className = "iconify";
|
|
2107
|
-
span.dataset.icon = assetDef.value;
|
|
2108
|
-
span.style.fontSize = `${PROJECTILE_SIZE_PX}px`;
|
|
2109
|
-
span.style.lineHeight = "1";
|
|
2110
|
-
span.style.display = "inline-block";
|
|
2111
|
-
return span;
|
|
2112
|
-
}
|
|
2113
|
-
var throwAsset = ({
|
|
2114
|
-
ev,
|
|
2115
|
-
state,
|
|
2116
|
-
baseOpts,
|
|
2117
|
-
ast,
|
|
2118
|
-
states,
|
|
2119
|
-
scene,
|
|
2120
|
-
assetOverrides,
|
|
2121
|
-
anims
|
|
2122
|
-
}) => {
|
|
2123
|
-
const assetName = String(ev.params.asset ?? "");
|
|
2124
|
-
const targetState = states.get(String(ev.params.to ?? ""));
|
|
2125
|
-
const assetDef = ast.assets[assetName];
|
|
2126
|
-
if (!assetDef || !targetState) return;
|
|
2127
|
-
const projectile = createProjectile(assetName, assetDef, assetOverrides);
|
|
2128
|
-
Object.assign(projectile.style, {
|
|
2129
|
-
position: "absolute",
|
|
2130
|
-
left: "0",
|
|
2131
|
-
top: "0",
|
|
2132
|
-
pointerEvents: "none",
|
|
2133
|
-
zIndex: "9",
|
|
2134
|
-
opacity: "0"
|
|
2135
|
-
});
|
|
2136
|
-
scene.appendChild(projectile);
|
|
2137
|
-
const anim = projectile.animate(
|
|
2138
|
-
[
|
|
2139
|
-
{ transform: tx(state), opacity: 1 },
|
|
2140
|
-
{ transform: tx(targetState), opacity: 0 }
|
|
2141
|
-
],
|
|
2142
|
-
{ ...baseOpts, easing: "ease-in" }
|
|
2143
|
-
);
|
|
2144
|
-
anim.addEventListener("finish", () => {
|
|
2145
|
-
if (projectile.parentNode === scene) scene.removeChild(projectile);
|
|
2146
|
-
});
|
|
2147
|
-
anims.push(anim);
|
|
2148
|
-
};
|
|
2149
|
-
|
|
2150
|
-
// src/actions/registry.ts
|
|
2151
|
-
var ACTION_HANDLERS = Object.freeze({
|
|
2152
|
-
// Universal — every actor type
|
|
2153
|
-
move,
|
|
2154
|
-
enter,
|
|
2155
|
-
exit,
|
|
2156
|
-
fade_in: fadeIn,
|
|
2157
|
-
fade_out: fadeOut,
|
|
2158
|
-
spring,
|
|
2159
|
-
follow_path: followPath,
|
|
2160
|
-
scale,
|
|
2161
|
-
rotate,
|
|
2162
|
-
shake,
|
|
2163
|
-
pulse,
|
|
2164
|
-
glow,
|
|
2165
|
-
ripple,
|
|
2166
|
-
blur,
|
|
2167
|
-
line_reveal: lineReveal,
|
|
2168
|
-
mask,
|
|
2169
|
-
parallax,
|
|
2170
|
-
jump,
|
|
2171
|
-
bounce,
|
|
2172
|
-
say,
|
|
2173
|
-
throw: throwAsset,
|
|
2174
|
-
// Figure-only — the parser rejects these on other actor types
|
|
2175
|
-
punch,
|
|
2176
|
-
kick,
|
|
2177
|
-
wave,
|
|
2178
|
-
nod,
|
|
2179
|
-
rotate_part: rotatePart,
|
|
2180
|
-
pose,
|
|
2181
|
-
face,
|
|
2182
|
-
// System-diagram flow edges (@markdy/stdlib-systems)
|
|
2183
|
-
request: flowEdge,
|
|
2184
|
-
response: flowEdge,
|
|
2185
|
-
emit: flowEdge
|
|
2186
|
-
});
|
|
2187
|
-
function handlerFor(action) {
|
|
2188
|
-
return ACTION_HANDLERS[action];
|
|
306
|
+
return anims;
|
|
2189
307
|
}
|
|
2190
|
-
|
|
2191
|
-
// src/animations.ts
|
|
2192
|
-
var DEFAULT_DURATION_S = 0.5;
|
|
2193
|
-
var DEFAULT_EASE_BY_ACTION = {
|
|
2194
|
-
enter: "out",
|
|
2195
|
-
fade_in: "out",
|
|
2196
|
-
exit: "in",
|
|
2197
|
-
fade_out: "in"
|
|
2198
|
-
};
|
|
2199
|
-
function buildAnimations(ast, actorEls, actorLayer, cameraLayer, assetOverrides, faceSwaps) {
|
|
308
|
+
function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds) {
|
|
2200
309
|
const anims = [];
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
|
|
310
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
311
|
+
const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
|
|
312
|
+
const svg = ensureEdgeLayer(scene);
|
|
313
|
+
const sceneId = `md-${Math.random().toString(36).slice(2, 8)}`;
|
|
314
|
+
anims.push(
|
|
315
|
+
titleEl.animate(
|
|
316
|
+
[{ opacity: 0, transform: "translateY(-6px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
317
|
+
{ duration: 420, delay: 0, fill: "forwards", easing: "ease-out" }
|
|
318
|
+
)
|
|
2207
319
|
);
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
320
|
+
for (const cue of cues) {
|
|
321
|
+
const startMs = cue.start * 1e3;
|
|
322
|
+
const durMs = cue.duration * 1e3;
|
|
323
|
+
if (cue.kind === "show") {
|
|
324
|
+
cue.targets.forEach((id, idx) => {
|
|
325
|
+
const el = nodeEls.get(id);
|
|
326
|
+
if (!el) return;
|
|
327
|
+
const delay = startMs + (typeof cue.params.stagger === "number" ? cue.params.stagger * 1e3 * idx : 0);
|
|
328
|
+
anims.push(
|
|
329
|
+
el.animate(
|
|
330
|
+
[{ opacity: 0, transform: "translateY(8px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
331
|
+
{ duration: durMs, delay, fill: "forwards", easing: "ease-out" }
|
|
332
|
+
)
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (cue.kind === "hide") {
|
|
338
|
+
for (const id of cue.targets) {
|
|
339
|
+
const el = nodeEls.get(id);
|
|
340
|
+
if (!el) continue;
|
|
341
|
+
anims.push(el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (cue.kind === "glow") {
|
|
346
|
+
for (const id of cue.targets) {
|
|
347
|
+
const el = nodeEls.get(id);
|
|
348
|
+
if (!el) continue;
|
|
349
|
+
if (cue.params.color) el.style.setProperty("--md-glow-color", String(cue.params.color));
|
|
350
|
+
el.dataset.glow = "1";
|
|
351
|
+
anims.push(
|
|
352
|
+
el.animate(
|
|
353
|
+
[{ filter: "brightness(1)" }, { filter: "brightness(1.15)" }, { filter: "brightness(1)" }],
|
|
354
|
+
{ duration: durMs, delay: startMs, fill: "forwards" }
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (cue.kind === "focus") {
|
|
361
|
+
for (const id of cue.targets) {
|
|
362
|
+
const el = nodeEls.get(id);
|
|
363
|
+
if (!el) continue;
|
|
364
|
+
el.dataset.focused = "1";
|
|
365
|
+
anims.push(
|
|
366
|
+
el.animate(
|
|
367
|
+
[{ transform: "scale(1)" }, { transform: "scale(1.03)" }, { transform: "scale(1)" }],
|
|
368
|
+
{ duration: durMs, delay: startMs, fill: "forwards" }
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
}
|
|
2226
372
|
continue;
|
|
2227
373
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
durMs
|
|
2239
|
-
|
|
2240
|
-
states,
|
|
2241
|
-
actorEls,
|
|
2242
|
-
scene: actorLayer,
|
|
2243
|
-
assetOverrides,
|
|
2244
|
-
faceSwaps,
|
|
2245
|
-
anims,
|
|
2246
|
-
tx: txFor(ev.actor)
|
|
2247
|
-
};
|
|
2248
|
-
handler(ctx);
|
|
374
|
+
if (cue.kind === "flow" && cue.segments?.[0]) {
|
|
375
|
+
const seg = cue.segments[0];
|
|
376
|
+
const from = nodeById.get(seg.from);
|
|
377
|
+
const to = nodeById.get(seg.to);
|
|
378
|
+
if (!from || !to) continue;
|
|
379
|
+
const obstacles = [];
|
|
380
|
+
for (const [id, rect] of rectById) {
|
|
381
|
+
if (id !== seg.from && id !== seg.to) obstacles.push(rect);
|
|
382
|
+
}
|
|
383
|
+
const runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, obstacles, bounds);
|
|
384
|
+
anims.push(...animateEdgeReveal(runtime, startMs, durMs));
|
|
385
|
+
}
|
|
2249
386
|
}
|
|
387
|
+
for (const a of anims) a.pause();
|
|
2250
388
|
return anims;
|
|
2251
389
|
}
|
|
2252
390
|
|
|
2253
|
-
// src/
|
|
391
|
+
// src/nodes.ts
|
|
392
|
+
var STYLE_ID = "markdy-diagram-node-styles";
|
|
393
|
+
function ensureNodeStyles(doc) {
|
|
394
|
+
if (doc.getElementById(STYLE_ID)) return;
|
|
395
|
+
const style = doc.createElement("style");
|
|
396
|
+
style.id = STYLE_ID;
|
|
397
|
+
style.textContent = `
|
|
398
|
+
.markdy-node {
|
|
399
|
+
position: absolute;
|
|
400
|
+
box-sizing: border-box;
|
|
401
|
+
width: var(--md-node-w, 184px);
|
|
402
|
+
height: var(--md-node-h, 88px);
|
|
403
|
+
border-radius: 16px;
|
|
404
|
+
border: 1px solid var(--md-border);
|
|
405
|
+
background: linear-gradient(145deg, var(--md-surface-raised), var(--md-surface));
|
|
406
|
+
color: var(--md-text);
|
|
407
|
+
box-shadow: 0 12px 32px rgba(2, 6, 23, 0.28), 0 0 0 1px rgba(255,255,255,0.04) inset;
|
|
408
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
409
|
+
overflow: hidden;
|
|
410
|
+
opacity: 0;
|
|
411
|
+
transform: translateY(8px);
|
|
412
|
+
transition: box-shadow 0.3s ease;
|
|
413
|
+
}
|
|
414
|
+
.markdy-node[data-visible="1"] {
|
|
415
|
+
opacity: 1;
|
|
416
|
+
transform: translateY(0);
|
|
417
|
+
}
|
|
418
|
+
.markdy-node[data-focused="1"] {
|
|
419
|
+
box-shadow: 0 0 0 2px var(--md-accent), 0 16px 40px rgba(2, 6, 23, 0.35);
|
|
420
|
+
}
|
|
421
|
+
.markdy-node[data-glow="1"] {
|
|
422
|
+
box-shadow: 0 0 24px var(--md-glow-color, var(--md-accent)), 0 0 0 1px var(--md-glow-color, var(--md-accent)) inset;
|
|
423
|
+
}
|
|
424
|
+
.markdy-node__rail {
|
|
425
|
+
position: absolute;
|
|
426
|
+
left: 0;
|
|
427
|
+
top: 0;
|
|
428
|
+
bottom: 0;
|
|
429
|
+
width: 4px;
|
|
430
|
+
background: var(--md-role-color, var(--md-accent));
|
|
431
|
+
border-radius: 16px 0 0 16px;
|
|
432
|
+
}
|
|
433
|
+
.markdy-node__type {
|
|
434
|
+
padding: 10px 14px 0 18px;
|
|
435
|
+
font-size: 10px;
|
|
436
|
+
font-weight: 700;
|
|
437
|
+
letter-spacing: 0.08em;
|
|
438
|
+
text-transform: uppercase;
|
|
439
|
+
color: var(--md-text-muted);
|
|
440
|
+
}
|
|
441
|
+
.markdy-node__label {
|
|
442
|
+
padding: 2px 14px 0 18px;
|
|
443
|
+
font-size: 15px;
|
|
444
|
+
font-weight: 650;
|
|
445
|
+
line-height: 1.25;
|
|
446
|
+
white-space: nowrap;
|
|
447
|
+
overflow: hidden;
|
|
448
|
+
text-overflow: ellipsis;
|
|
449
|
+
}
|
|
450
|
+
.markdy-node[data-role="client"] { border-radius: 20px 20px 8px 8px; }
|
|
451
|
+
.markdy-node[data-role="data"] { border-radius: 16px 16px 28px 28px; }
|
|
452
|
+
.markdy-node[data-role="flow"] { transform: rotate(0deg); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
|
|
453
|
+
.markdy-node[data-role="network"] { clip-path: polygon(8% 0%, 92% 0%, 100% 50%, 92% 100%, 8% 100%, 0% 50%); }
|
|
454
|
+
.markdy-scene-title {
|
|
455
|
+
position: absolute;
|
|
456
|
+
left: 64px;
|
|
457
|
+
top: 28px;
|
|
458
|
+
right: 64px;
|
|
459
|
+
font-size: 32px;
|
|
460
|
+
font-weight: 700;
|
|
461
|
+
line-height: 1.2;
|
|
462
|
+
color: var(--md-text);
|
|
463
|
+
opacity: 0;
|
|
464
|
+
transform: translateY(-6px);
|
|
465
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
466
|
+
}
|
|
467
|
+
.markdy-scene-title[data-visible="1"] {
|
|
468
|
+
opacity: 1;
|
|
469
|
+
transform: translateY(0);
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
doc.head.appendChild(style);
|
|
473
|
+
}
|
|
474
|
+
function createNodeEl(node, theme) {
|
|
475
|
+
const el = document.createElement("div");
|
|
476
|
+
el.className = "markdy-node markdy-scene-actor";
|
|
477
|
+
el.dataset.actor = node.id;
|
|
478
|
+
el.dataset.role = node.role;
|
|
479
|
+
el.style.left = `${node.x}px`;
|
|
480
|
+
el.style.top = `${node.y}px`;
|
|
481
|
+
el.style.setProperty("--md-node-w", `${node.width}px`);
|
|
482
|
+
el.style.setProperty("--md-node-h", `${node.height}px`);
|
|
483
|
+
const roleColor = theme.roles[node.role] ?? theme.accent;
|
|
484
|
+
el.style.setProperty("--md-role-color", roleColor);
|
|
485
|
+
const rail = document.createElement("div");
|
|
486
|
+
rail.className = "markdy-node__rail";
|
|
487
|
+
const type = document.createElement("div");
|
|
488
|
+
type.className = "markdy-node__type";
|
|
489
|
+
type.textContent = node.kind.replace(/_/g, " ");
|
|
490
|
+
const label = document.createElement("div");
|
|
491
|
+
label.className = "markdy-node__label";
|
|
492
|
+
label.textContent = node.label;
|
|
493
|
+
el.append(rail, type, label);
|
|
494
|
+
return el;
|
|
495
|
+
}
|
|
496
|
+
function createTitleEl(title) {
|
|
497
|
+
const el = document.createElement("div");
|
|
498
|
+
el.className = "markdy-scene-title";
|
|
499
|
+
el.textContent = title;
|
|
500
|
+
return el;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/theme.ts
|
|
2254
504
|
var SCENE_STYLE_ID = "markdy-scene-ambience-styles";
|
|
2255
|
-
var SAFE_FRAME_PADDING = 28;
|
|
2256
505
|
function ensureSceneStyles(doc) {
|
|
2257
506
|
if (doc.getElementById(SCENE_STYLE_ID)) return;
|
|
2258
507
|
const style = doc.createElement("style");
|
|
2259
508
|
style.id = SCENE_STYLE_ID;
|
|
2260
509
|
style.textContent = `
|
|
2261
510
|
.markdy-scene-root {
|
|
2262
|
-
--markdy-scene-grid: rgba(148, 163, 184, 0.13);
|
|
2263
|
-
--markdy-scene-grid-strong: rgba(148, 163, 184, 0.2);
|
|
2264
|
-
--markdy-scene-vignette: rgba(2, 6, 23, 0.72);
|
|
2265
|
-
--markdy-scene-accent: rgba(56, 189, 248, 0.16);
|
|
2266
511
|
isolation: isolate;
|
|
2267
512
|
}
|
|
2268
513
|
.markdy-scene-root::before,
|
|
@@ -2275,149 +520,69 @@ function ensureSceneStyles(doc) {
|
|
|
2275
520
|
.markdy-scene-root::before {
|
|
2276
521
|
z-index: 0;
|
|
2277
522
|
background:
|
|
2278
|
-
linear-gradient(var(--
|
|
2279
|
-
linear-gradient(90deg, var(--
|
|
2280
|
-
linear-gradient(var(--
|
|
2281
|
-
linear-gradient(90deg, var(--
|
|
523
|
+
linear-gradient(var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
|
|
524
|
+
linear-gradient(90deg, var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
|
|
525
|
+
linear-gradient(var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px,
|
|
526
|
+
linear-gradient(90deg, var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px;
|
|
2282
527
|
mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
|
|
2283
528
|
opacity: 0.82;
|
|
2284
529
|
}
|
|
2285
530
|
.markdy-scene-root::after {
|
|
2286
531
|
z-index: 1;
|
|
2287
532
|
background:
|
|
2288
|
-
radial-gradient(ellipse at 50% 0%, var(--
|
|
2289
|
-
linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.08) 58%, var(--
|
|
2290
|
-
linear-gradient(90deg, rgba(255, 255, 255, 0.035), transparent 18%, transparent 82%, rgba(255, 255, 255, 0.035));
|
|
2291
|
-
mix-blend-mode: screen;
|
|
533
|
+
radial-gradient(ellipse at 50% 0%, color-mix(in srgb, var(--md-accent) 16%, transparent), transparent 42%),
|
|
534
|
+
linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.08) 58%, var(--md-vignette) 100%);
|
|
2292
535
|
opacity: 0.8;
|
|
2293
536
|
}
|
|
2294
|
-
.markdy-scene-
|
|
2295
|
-
--markdy-scene-grid: rgba(15, 23, 42, 0.09);
|
|
2296
|
-
--markdy-scene-grid-strong: rgba(15, 23, 42, 0.14);
|
|
2297
|
-
--markdy-scene-vignette: rgba(241, 245, 249, 0.74);
|
|
2298
|
-
--markdy-scene-accent: rgba(14, 165, 233, 0.12);
|
|
2299
|
-
}
|
|
2300
|
-
.markdy-scene-content {
|
|
2301
|
-
z-index: 2;
|
|
2302
|
-
}
|
|
537
|
+
.markdy-scene-content { z-index: 2; }
|
|
2303
538
|
.markdy-scene-actor-layer {
|
|
2304
539
|
position: absolute;
|
|
2305
540
|
inset: 0;
|
|
2306
541
|
overflow: visible;
|
|
2307
|
-
transform-origin: 0 0;
|
|
2308
|
-
will-change: transform;
|
|
2309
542
|
}
|
|
2310
543
|
`;
|
|
2311
544
|
doc.head.appendChild(style);
|
|
2312
545
|
}
|
|
2313
|
-
function
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
function computeContentBounds(ast) {
|
|
2328
|
-
let bounds = null;
|
|
2329
|
-
const maxScaleByActor = /* @__PURE__ */ new Map();
|
|
2330
|
-
const positionsByActor = /* @__PURE__ */ new Map();
|
|
2331
|
-
for (const [name, def] of Object.entries(ast.actors)) {
|
|
2332
|
-
maxScaleByActor.set(name, Math.max(1e-3, def.scale ?? 1));
|
|
2333
|
-
positionsByActor.set(name, [[def.x, def.y]]);
|
|
2334
|
-
}
|
|
2335
|
-
for (const ev of ast.events) {
|
|
2336
|
-
if (ev.actor === "camera") continue;
|
|
2337
|
-
if (ev.action === "scale" && typeof ev.params.to === "number") {
|
|
2338
|
-
maxScaleByActor.set(ev.actor, Math.max(maxScaleByActor.get(ev.actor) ?? 1, ev.params.to));
|
|
2339
|
-
}
|
|
2340
|
-
if (ev.action === "move" || ev.action === "spring") {
|
|
2341
|
-
const to = numericPair(ev.params.to);
|
|
2342
|
-
if (to) positionsByActor.get(ev.actor)?.push(to);
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
for (const [name, def] of Object.entries(ast.actors)) {
|
|
2346
|
-
const positions = positionsByActor.get(name) ?? [[def.x, def.y]];
|
|
2347
|
-
const scale2 = maxScaleByActor.get(name) ?? def.scale ?? 1;
|
|
2348
|
-
for (const [x, y] of positions) {
|
|
2349
|
-
bounds = unionRect(bounds, actorRect({ ...stateFrom(def), x, y, scale: scale2 }, def.type));
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
return bounds;
|
|
2353
|
-
}
|
|
2354
|
-
function computeSafeFrame(ast) {
|
|
2355
|
-
const bounds = computeContentBounds(ast);
|
|
2356
|
-
if (!bounds) return { x: 0, y: 0, scale: 1 };
|
|
2357
|
-
const boundsWidth = Math.max(1, bounds.x2 - bounds.x1);
|
|
2358
|
-
const boundsHeight = Math.max(1, bounds.y2 - bounds.y1);
|
|
2359
|
-
const availableWidth = Math.max(1, ast.meta.width - SAFE_FRAME_PADDING * 2);
|
|
2360
|
-
const availableHeight = Math.max(1, ast.meta.height - SAFE_FRAME_PADDING * 2);
|
|
2361
|
-
const scale2 = Math.min(1, availableWidth / boundsWidth, availableHeight / boundsHeight);
|
|
2362
|
-
const scaledWidth = boundsWidth * scale2;
|
|
2363
|
-
const scaledHeight = boundsHeight * scale2;
|
|
2364
|
-
function axisOffset(min, max, sceneSize, scaledSize) {
|
|
2365
|
-
const paddedMin = SAFE_FRAME_PADDING;
|
|
2366
|
-
const paddedMax = sceneSize - SAFE_FRAME_PADDING;
|
|
2367
|
-
const scaledMin = min * scale2;
|
|
2368
|
-
const scaledMax = max * scale2;
|
|
2369
|
-
if (scaledSize > paddedMax - paddedMin) return (sceneSize - scaledSize) / 2 - scaledMin;
|
|
2370
|
-
if (scaledMin < paddedMin) return paddedMin - scaledMin;
|
|
2371
|
-
if (scaledMax > paddedMax) return paddedMax - scaledMax;
|
|
2372
|
-
return 0;
|
|
2373
|
-
}
|
|
2374
|
-
const x = axisOffset(bounds.x1, bounds.x2, ast.meta.width, scaledWidth);
|
|
2375
|
-
const y = axisOffset(bounds.y1, bounds.y2, ast.meta.height, scaledHeight);
|
|
2376
|
-
return {
|
|
2377
|
-
x: Math.round(x * 1e3) / 1e3,
|
|
2378
|
-
y: Math.round(y * 1e3) / 1e3,
|
|
2379
|
-
scale: Math.round(scale2 * 1e3) / 1e3
|
|
2380
|
-
};
|
|
2381
|
-
}
|
|
2382
|
-
function bgToTextColor(bg) {
|
|
2383
|
-
let hex = bg.trim().replace(/^#/, "");
|
|
2384
|
-
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
2385
|
-
if (hex.length === 6) {
|
|
2386
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
2387
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
2388
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
2389
|
-
return 0.299 * r + 0.587 * g + 0.114 * b > 140 ? "#1a1a1a" : "#f0f0f0";
|
|
2390
|
-
}
|
|
2391
|
-
const named = {
|
|
2392
|
-
white: "#1a1a1a",
|
|
2393
|
-
black: "#f0f0f0",
|
|
2394
|
-
transparent: "#1a1a1a"
|
|
2395
|
-
};
|
|
2396
|
-
return named[bg.toLowerCase()] ?? "#1a1a1a";
|
|
546
|
+
function applyThemeToScene(scene, theme) {
|
|
547
|
+
scene.style.background = theme.canvas;
|
|
548
|
+
scene.style.color = theme.text;
|
|
549
|
+
scene.style.setProperty("--md-canvas", theme.canvas);
|
|
550
|
+
scene.style.setProperty("--md-surface", theme.surface);
|
|
551
|
+
scene.style.setProperty("--md-surface-raised", theme.surfaceRaised);
|
|
552
|
+
scene.style.setProperty("--md-border", theme.border);
|
|
553
|
+
scene.style.setProperty("--md-text", theme.text);
|
|
554
|
+
scene.style.setProperty("--md-text-muted", theme.textMuted);
|
|
555
|
+
scene.style.setProperty("--md-grid-minor", theme.gridMinor);
|
|
556
|
+
scene.style.setProperty("--md-grid-major", theme.gridMajor);
|
|
557
|
+
scene.style.setProperty("--md-vignette", theme.vignette);
|
|
558
|
+
scene.style.setProperty("--md-accent", theme.accent);
|
|
559
|
+
scene.dataset.markdyTheme = theme.name;
|
|
2397
560
|
}
|
|
561
|
+
|
|
562
|
+
// src/player.ts
|
|
2398
563
|
function createPlayer(opts) {
|
|
2399
564
|
const {
|
|
2400
565
|
container,
|
|
2401
566
|
code,
|
|
2402
|
-
assets: assetOverrides = {},
|
|
2403
|
-
imports,
|
|
2404
567
|
autoplay = true,
|
|
2405
568
|
loop = true,
|
|
2406
569
|
copyright = true,
|
|
2407
570
|
progressBar = true,
|
|
2408
|
-
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}
|
|
571
|
+
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}`),
|
|
2409
572
|
onTimeUpdate,
|
|
2410
573
|
onPlayStateChange
|
|
2411
574
|
} = opts;
|
|
2412
|
-
const ast
|
|
2413
|
-
const
|
|
2414
|
-
|
|
2415
|
-
|
|
575
|
+
const { ast, plan } = parseAndCompile(code);
|
|
576
|
+
for (const w of ast.diagnostics) {
|
|
577
|
+
if (w.severity === "warning") onWarning(w);
|
|
578
|
+
}
|
|
579
|
+
const totalDurationMs = plan.duration * 1e3;
|
|
580
|
+
const durationSeconds = plan.duration;
|
|
2416
581
|
const viewport = document.createElement("div");
|
|
2417
582
|
Object.assign(viewport.style, {
|
|
2418
583
|
position: "relative",
|
|
2419
584
|
width: "100%",
|
|
2420
|
-
aspectRatio: `${
|
|
585
|
+
aspectRatio: `${plan.meta.width} / ${plan.meta.height}`,
|
|
2421
586
|
overflow: "hidden"
|
|
2422
587
|
});
|
|
2423
588
|
container.appendChild(viewport);
|
|
@@ -2433,13 +598,14 @@ function createPlayer(opts) {
|
|
|
2433
598
|
});
|
|
2434
599
|
viewport.appendChild(progressEl);
|
|
2435
600
|
}
|
|
2436
|
-
const tlAngle =
|
|
601
|
+
const tlAngle = Math.atan2(-plan.meta.width / 2, plan.meta.height / 2) * 180 / Math.PI;
|
|
602
|
+
const tlAngleNorm = (tlAngle % 360 + 360) % 360;
|
|
2437
603
|
function updateProgressBar(pct) {
|
|
2438
604
|
if (!progressEl) return;
|
|
2439
605
|
const deg = pct * 360;
|
|
2440
606
|
const rainbow = "hsl(0,90%,60%), hsl(45,90%,55%), hsl(90,80%,50%), hsl(180,80%,50%), hsl(270,80%,55%), hsl(330,90%,60%)";
|
|
2441
|
-
progressEl.style.background = `conic-gradient(from ${
|
|
2442
|
-
progressEl.style.mask =
|
|
607
|
+
progressEl.style.background = `conic-gradient(from ${tlAngleNorm}deg, ${rainbow} ${deg}deg, transparent ${deg}deg)`;
|
|
608
|
+
progressEl.style.mask = "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)";
|
|
2443
609
|
progressEl.style.webkitMask = progressEl.style.mask;
|
|
2444
610
|
progressEl.style.maskComposite = "exclude";
|
|
2445
611
|
progressEl.style.webkitMaskComposite = "xor";
|
|
@@ -2456,78 +622,57 @@ function createPlayer(opts) {
|
|
|
2456
622
|
display: "block",
|
|
2457
623
|
textAlign: "right",
|
|
2458
624
|
fontSize: "10px",
|
|
2459
|
-
fontFamily: "system-ui,
|
|
625
|
+
fontFamily: "system-ui, sans-serif",
|
|
2460
626
|
color: "#999",
|
|
2461
627
|
textDecoration: "none",
|
|
2462
628
|
padding: "3px 6px 0",
|
|
2463
|
-
opacity: "0.7"
|
|
2464
|
-
transition: "opacity 0.2s",
|
|
2465
|
-
maxWidth: container.style.maxWidth || "100%",
|
|
2466
|
-
width: "100%"
|
|
2467
|
-
});
|
|
2468
|
-
badge.addEventListener("mouseenter", () => {
|
|
2469
|
-
badge.style.opacity = "1";
|
|
629
|
+
opacity: "0.7"
|
|
2470
630
|
});
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
});
|
|
2474
|
-
if (container.parentNode) {
|
|
2475
|
-
container.parentNode.insertBefore(badge, container.nextSibling);
|
|
2476
|
-
} else {
|
|
2477
|
-
container.appendChild(badge);
|
|
2478
|
-
}
|
|
631
|
+
if (container.parentNode) container.parentNode.insertBefore(badge, container.nextSibling);
|
|
632
|
+
else container.appendChild(badge);
|
|
2479
633
|
}
|
|
2480
|
-
const scene = document.createElement("div");
|
|
2481
634
|
ensureSceneStyles(document);
|
|
635
|
+
ensureNodeStyles(document);
|
|
636
|
+
const scene = document.createElement("div");
|
|
2482
637
|
scene.className = "markdy-scene-root";
|
|
2483
|
-
scene.dataset.markdySceneTone = bgToTextColor(ast.meta.bg) === "#1a1a1a" ? "light" : "dark";
|
|
2484
638
|
Object.assign(scene.style, {
|
|
2485
639
|
position: "absolute",
|
|
2486
640
|
top: "0",
|
|
2487
641
|
left: "0",
|
|
2488
|
-
width: `${
|
|
2489
|
-
height: `${
|
|
2490
|
-
background: ast.meta.bg,
|
|
2491
|
-
color: bgToTextColor(ast.meta.bg),
|
|
642
|
+
width: `${plan.meta.width}px`,
|
|
643
|
+
height: `${plan.meta.height}px`,
|
|
2492
644
|
overflow: "hidden",
|
|
2493
645
|
userSelect: "none",
|
|
2494
646
|
transformOrigin: "0 0"
|
|
2495
647
|
});
|
|
648
|
+
applyThemeToScene(scene, plan.theme);
|
|
2496
649
|
viewport.appendChild(scene);
|
|
2497
650
|
const sceneContent = document.createElement("div");
|
|
2498
651
|
sceneContent.className = "markdy-scene-content";
|
|
2499
|
-
Object.assign(sceneContent.style, {
|
|
2500
|
-
position: "absolute",
|
|
2501
|
-
top: "0",
|
|
2502
|
-
left: "0",
|
|
2503
|
-
width: "100%",
|
|
2504
|
-
height: "100%",
|
|
2505
|
-
transformOrigin: "50% 50%",
|
|
2506
|
-
willChange: "transform"
|
|
2507
|
-
});
|
|
652
|
+
Object.assign(sceneContent.style, { position: "absolute", inset: "0" });
|
|
2508
653
|
scene.appendChild(sceneContent);
|
|
2509
654
|
const actorLayer = document.createElement("div");
|
|
2510
655
|
actorLayer.className = "markdy-scene-actor-layer";
|
|
2511
|
-
const safeFrame = computeSafeFrame(ast);
|
|
2512
|
-
actorLayer.dataset.markdySafeFrame = `${safeFrame.x},${safeFrame.y},${safeFrame.scale}`;
|
|
2513
|
-
actorLayer.style.transform = `translate(${safeFrame.x}px, ${safeFrame.y}px) scale(${safeFrame.scale})`;
|
|
2514
656
|
sceneContent.appendChild(actorLayer);
|
|
657
|
+
const titleEl = createTitleEl(plan.title);
|
|
658
|
+
actorLayer.appendChild(titleEl);
|
|
659
|
+
const nodeEls = /* @__PURE__ */ new Map();
|
|
660
|
+
for (const node of plan.nodes) {
|
|
661
|
+
const el = createNodeEl(node, plan.theme);
|
|
662
|
+
actorLayer.appendChild(el);
|
|
663
|
+
nodeEls.set(node.id, el);
|
|
664
|
+
}
|
|
2515
665
|
function scaleScene() {
|
|
2516
|
-
const s = viewport.clientWidth /
|
|
666
|
+
const s = viewport.clientWidth / plan.meta.width;
|
|
2517
667
|
scene.style.transform = `scale(${s})`;
|
|
2518
668
|
}
|
|
2519
669
|
scaleScene();
|
|
2520
670
|
const resizeObserver = new ResizeObserver(scaleScene);
|
|
2521
671
|
resizeObserver.observe(viewport);
|
|
2522
|
-
const
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
actorEls.set(name, el);
|
|
2527
|
-
}
|
|
2528
|
-
const faceSwaps = [];
|
|
2529
|
-
const allAnims = buildAnimations(ast, actorEls, actorLayer, sceneContent, assetOverrides, faceSwaps);
|
|
2530
|
-
faceSwaps.sort((a, b) => a.timeMs - b.timeMs);
|
|
672
|
+
const allAnims = buildCueAnimations(plan.cues, nodeEls, plan.nodes, plan.theme, sceneContent, titleEl, {
|
|
673
|
+
width: plan.meta.width,
|
|
674
|
+
height: plan.meta.height
|
|
675
|
+
});
|
|
2531
676
|
for (const anim of allAnims) {
|
|
2532
677
|
anim.pause();
|
|
2533
678
|
anim.currentTime = 0;
|
|
@@ -2536,47 +681,16 @@ function createPlayer(opts) {
|
|
|
2536
681
|
let lastRafTs = null;
|
|
2537
682
|
let isPlaying = false;
|
|
2538
683
|
let rafId = null;
|
|
2539
|
-
for (const { el } of faceSwaps) {
|
|
2540
|
-
if (!el.dataset["figFaceInitial"]) {
|
|
2541
|
-
el.dataset["figFaceInitial"] = el.textContent ?? "";
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
684
|
function applyCurrentTime() {
|
|
2545
|
-
for (const anim of allAnims)
|
|
2546
|
-
anim.currentTime = sceneMs;
|
|
2547
|
-
}
|
|
2548
|
-
applyFaceSwaps();
|
|
685
|
+
for (const anim of allAnims) anim.currentTime = sceneMs;
|
|
2549
686
|
onTimeUpdate?.(sceneMs / 1e3, durationSeconds);
|
|
2550
687
|
}
|
|
2551
|
-
function applyFaceSwaps() {
|
|
2552
|
-
if (faceSwaps.length === 0) return;
|
|
2553
|
-
const elEmoji = /* @__PURE__ */ new Map();
|
|
2554
|
-
for (const { timeMs, el, emoji } of faceSwaps) {
|
|
2555
|
-
if (timeMs <= sceneMs) elEmoji.set(el, emoji);
|
|
2556
|
-
}
|
|
2557
|
-
for (const [el, emoji] of elEmoji) {
|
|
2558
|
-
if (el.textContent !== emoji) el.textContent = emoji;
|
|
2559
|
-
}
|
|
2560
|
-
const elFirst = /* @__PURE__ */ new Map();
|
|
2561
|
-
for (const { el, emoji } of faceSwaps) {
|
|
2562
|
-
if (!elFirst.has(el)) elFirst.set(el, emoji);
|
|
2563
|
-
}
|
|
2564
|
-
for (const [el, firstEmoji] of elFirst) {
|
|
2565
|
-
if (!elEmoji.has(el)) {
|
|
2566
|
-
const initial = el.dataset["figFaceInitial"] ?? firstEmoji;
|
|
2567
|
-
if (el.textContent !== initial) el.textContent = initial;
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
688
|
function rafTick(timestamp) {
|
|
2572
|
-
if (lastRafTs !== null)
|
|
2573
|
-
sceneMs += timestamp - lastRafTs;
|
|
2574
|
-
}
|
|
689
|
+
if (lastRafTs !== null) sceneMs += timestamp - lastRafTs;
|
|
2575
690
|
lastRafTs = timestamp;
|
|
2576
691
|
if (totalDurationMs > 0 && sceneMs >= totalDurationMs) {
|
|
2577
|
-
if (loop)
|
|
2578
|
-
|
|
2579
|
-
} else {
|
|
692
|
+
if (loop) sceneMs = sceneMs % totalDurationMs;
|
|
693
|
+
else {
|
|
2580
694
|
sceneMs = totalDurationMs;
|
|
2581
695
|
applyCurrentTime();
|
|
2582
696
|
isPlaying = false;
|
|
@@ -2602,15 +716,12 @@ function createPlayer(opts) {
|
|
|
2602
716
|
if (!isPlaying) return;
|
|
2603
717
|
isPlaying = false;
|
|
2604
718
|
onPlayStateChange?.(false);
|
|
2605
|
-
if (rafId !== null)
|
|
2606
|
-
|
|
2607
|
-
rafId = null;
|
|
2608
|
-
}
|
|
719
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
720
|
+
rafId = null;
|
|
2609
721
|
lastRafTs = null;
|
|
2610
722
|
},
|
|
2611
723
|
seek(seconds) {
|
|
2612
|
-
|
|
2613
|
-
sceneMs = totalDurationMs > 0 ? Math.min(Math.max(nextMs, 0), totalDurationMs) : Math.max(nextMs, 0);
|
|
724
|
+
sceneMs = totalDurationMs > 0 ? Math.min(Math.max(seconds * 1e3, 0), totalDurationMs) : Math.max(seconds * 1e3, 0);
|
|
2614
725
|
applyCurrentTime();
|
|
2615
726
|
if (totalDurationMs > 0) updateProgressBar(sceneMs / totalDurationMs);
|
|
2616
727
|
},
|
|
@@ -2623,13 +734,12 @@ function createPlayer(opts) {
|
|
|
2623
734
|
isPlaying() {
|
|
2624
735
|
return isPlaying;
|
|
2625
736
|
},
|
|
2626
|
-
|
|
2627
|
-
return
|
|
737
|
+
beats() {
|
|
738
|
+
return plan.beats;
|
|
2628
739
|
},
|
|
2629
|
-
|
|
2630
|
-
const
|
|
2631
|
-
if (
|
|
2632
|
-
player.seek(chapter.startTime);
|
|
740
|
+
seekToBeat(name) {
|
|
741
|
+
const beat = plan.beats.find((b) => b.name === name);
|
|
742
|
+
if (beat) player.seek(beat.start);
|
|
2633
743
|
},
|
|
2634
744
|
destroy() {
|
|
2635
745
|
player.pause();
|
|
@@ -2642,12 +752,9 @@ function createPlayer(opts) {
|
|
|
2642
752
|
};
|
|
2643
753
|
viewport.style.cursor = "pointer";
|
|
2644
754
|
viewport.addEventListener("click", () => {
|
|
2645
|
-
if (isPlaying)
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
if (!loop && sceneMs >= totalDurationMs) {
|
|
2649
|
-
sceneMs = 0;
|
|
2650
|
-
}
|
|
755
|
+
if (isPlaying) player.pause();
|
|
756
|
+
else {
|
|
757
|
+
if (!loop && sceneMs >= totalDurationMs) sceneMs = 0;
|
|
2651
758
|
player.play();
|
|
2652
759
|
}
|
|
2653
760
|
});
|