@markdy/renderer-dom 0.7.29 → 0.8.1
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 +416 -2321
- package/package.json +10 -10
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;
|
|
5
|
+
function boxRect(box) {
|
|
6
|
+
return { x1: box.x, y1: box.y, x2: box.x + box.width, y2: box.y + box.height };
|
|
1678
7
|
}
|
|
1679
|
-
function
|
|
1680
|
-
|
|
1681
|
-
return { x: rect.x1 + (rect.x2 - rect.x1) / 2, y: rect.y1 + (rect.y2 - rect.y1) / 2 };
|
|
1682
|
-
}
|
|
1683
|
-
function actorRect(state, actorType) {
|
|
1684
|
-
const { width, height } = actorSizeByType(actorType);
|
|
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 {
|
|
@@ -1748,6 +62,7 @@ function polylineLength(points) {
|
|
|
1748
62
|
function segmentLength(a, b) {
|
|
1749
63
|
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
1750
64
|
}
|
|
65
|
+
var LABEL_BOX_HEIGHT = 16;
|
|
1751
66
|
function rectsOverlap(a, b) {
|
|
1752
67
|
return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
|
|
1753
68
|
}
|
|
@@ -1756,8 +71,7 @@ function overlapCount(rect, obstacles) {
|
|
|
1756
71
|
for (const o of obstacles) if (rectsOverlap(rect, o)) hits++;
|
|
1757
72
|
return hits;
|
|
1758
73
|
}
|
|
1759
|
-
|
|
1760
|
-
function placeFlowLabel(points, textWidth, obstacles, ast) {
|
|
74
|
+
function placeFlowLabel(points, textWidth, obstacles, bounds) {
|
|
1761
75
|
let bestIndex = 0;
|
|
1762
76
|
let bestLength = -1;
|
|
1763
77
|
for (let i = 0; i < points.length - 1; i++) {
|
|
@@ -1774,20 +88,18 @@ function placeFlowLabel(points, textWidth, obstacles, ast) {
|
|
|
1774
88
|
const half = textWidth / 2;
|
|
1775
89
|
const halfH = LABEL_BOX_HEIGHT / 2;
|
|
1776
90
|
const pad = 8;
|
|
1777
|
-
const base = horizontal ?
|
|
1778
|
-
const step = horizontal ? LABEL_BOX_HEIGHT +
|
|
91
|
+
const base = horizontal ? 14 : half + 12;
|
|
92
|
+
const step = horizontal ? LABEL_BOX_HEIGHT + 4 : textWidth + 12;
|
|
1779
93
|
const order = horizontal ? [-1, 1] : [1, -1];
|
|
1780
94
|
const offsets = [];
|
|
1781
|
-
for (let k = 0; k <
|
|
95
|
+
for (let k = 0; k < 8; k++) {
|
|
1782
96
|
for (const sign of order) offsets.push(sign * (base + k * step));
|
|
1783
97
|
}
|
|
1784
98
|
let fallback = null;
|
|
1785
99
|
let fallbackHits = Number.POSITIVE_INFINITY;
|
|
1786
100
|
for (const off of offsets) {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
cx = clamp(cx, pad + half, ast.meta.width - pad - half);
|
|
1790
|
-
cy = clamp(cy, pad + halfH, ast.meta.height - pad - halfH);
|
|
101
|
+
const cx = clamp(horizontal ? mid.x : mid.x + off, pad + half, bounds.width - pad - half);
|
|
102
|
+
const cy = clamp(horizontal ? mid.y + off : mid.y, pad + halfH, bounds.height - pad - halfH);
|
|
1791
103
|
const rect = { x1: cx - half, y1: cy - halfH, x2: cx + half, y2: cy + halfH };
|
|
1792
104
|
const hits = overlapCount(rect, obstacles);
|
|
1793
105
|
if (hits === 0) return { x: round1(cx), y: round1(cy), rect };
|
|
@@ -1806,11 +118,11 @@ function clamp(n, min, max) {
|
|
|
1806
118
|
if (max < min) return n;
|
|
1807
119
|
return Math.min(max, Math.max(min, n));
|
|
1808
120
|
}
|
|
1809
|
-
function clampPointToScene(point,
|
|
121
|
+
function clampPointToScene(point, bounds) {
|
|
1810
122
|
const pad = 14;
|
|
1811
123
|
return {
|
|
1812
|
-
x: round1(clamp(point.x, pad,
|
|
1813
|
-
y: round1(clamp(point.y, pad,
|
|
124
|
+
x: round1(clamp(point.x, pad, bounds.width - pad)),
|
|
125
|
+
y: round1(clamp(point.y, pad, bounds.height - pad))
|
|
1814
126
|
};
|
|
1815
127
|
}
|
|
1816
128
|
function routeLength(points) {
|
|
@@ -1832,23 +144,14 @@ function routeBends(points) {
|
|
|
1832
144
|
}
|
|
1833
145
|
return bends;
|
|
1834
146
|
}
|
|
1835
|
-
function
|
|
1836
|
-
const sourceType = ast.actors[sourceName]?.type ?? "box";
|
|
1837
|
-
const targetType = ast.actors[targetName]?.type ?? "box";
|
|
1838
|
-
const sourceRect = actorRect(sourceState, sourceType);
|
|
1839
|
-
const targetRect = actorRect(targetState, targetType);
|
|
147
|
+
function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
|
|
1840
148
|
const laneShift = lane * 18;
|
|
1841
|
-
const sourceCenter =
|
|
1842
|
-
const targetCenter =
|
|
149
|
+
const sourceCenter = rectCenter(sourceRect);
|
|
150
|
+
const targetCenter = rectCenter(targetRect);
|
|
1843
151
|
const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
|
|
1844
152
|
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 };
|
|
1845
153
|
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 };
|
|
1846
|
-
const
|
|
1847
|
-
for (const [name, state] of states.entries()) {
|
|
1848
|
-
if (name === sourceName || name === targetName) continue;
|
|
1849
|
-
const type = ast.actors[name]?.type ?? "box";
|
|
1850
|
-
obstacles.push(inflateRect(actorRect(state, type), 8));
|
|
1851
|
-
}
|
|
154
|
+
const infl = obstacles.map((o) => inflateRect(o, 8));
|
|
1852
155
|
const candidates = [];
|
|
1853
156
|
if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
|
|
1854
157
|
candidates.push([source, target]);
|
|
@@ -1859,18 +162,18 @@ function routeFlowPath(sourceName, targetName, sourceState, targetState, states,
|
|
|
1859
162
|
[source, { x: midX, y: source.y }, { x: midX, y: target.y }, target],
|
|
1860
163
|
[source, { x: source.x, y: midY }, { x: target.x, y: midY }, target]
|
|
1861
164
|
);
|
|
1862
|
-
const minObstacleY =
|
|
1863
|
-
const maxObstacleY =
|
|
165
|
+
const minObstacleY = infl.length ? Math.min(...infl.map((o) => o.y1)) : Math.min(source.y, target.y);
|
|
166
|
+
const maxObstacleY = infl.length ? Math.max(...infl.map((o) => o.y2)) : Math.max(source.y, target.y);
|
|
1864
167
|
const topLane = Math.max(16, minObstacleY - 24 - lane * 12);
|
|
1865
|
-
const bottomLane = Math.min(
|
|
168
|
+
const bottomLane = Math.min(bounds.height - 16, maxObstacleY + 24 + lane * 12);
|
|
1866
169
|
candidates.push(
|
|
1867
170
|
[source, { x: source.x, y: topLane }, { x: target.x, y: topLane }, target],
|
|
1868
171
|
[source, { x: source.x, y: bottomLane }, { x: target.x, y: bottomLane }, target]
|
|
1869
172
|
);
|
|
1870
|
-
const minObstacleX =
|
|
1871
|
-
const maxObstacleX =
|
|
173
|
+
const minObstacleX = infl.length ? Math.min(...infl.map((o) => o.x1)) : Math.min(source.x, target.x);
|
|
174
|
+
const maxObstacleX = infl.length ? Math.max(...infl.map((o) => o.x2)) : Math.max(source.x, target.x);
|
|
1872
175
|
const leftLane = Math.max(16, minObstacleX - 24 - lane * 12);
|
|
1873
|
-
const rightLane = Math.min(
|
|
176
|
+
const rightLane = Math.min(bounds.width - 16, maxObstacleX + 24 + lane * 12);
|
|
1874
177
|
candidates.push(
|
|
1875
178
|
[source, { x: leftLane, y: source.y }, { x: leftLane, y: target.y }, target],
|
|
1876
179
|
[source, { x: rightLane, y: source.y }, { x: rightLane, y: target.y }, target]
|
|
@@ -1878,28 +181,32 @@ function routeFlowPath(sourceName, targetName, sourceState, targetState, states,
|
|
|
1878
181
|
let best = candidates[0];
|
|
1879
182
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
1880
183
|
for (const candidate of candidates) {
|
|
1881
|
-
const hits = countPathIntersections(candidate,
|
|
184
|
+
const hits = countPathIntersections(candidate, infl);
|
|
1882
185
|
const score = hits * 1e5 + routeBends(candidate) * 800 + routeLength(candidate);
|
|
1883
186
|
if (score < bestScore) {
|
|
1884
187
|
best = candidate;
|
|
1885
188
|
bestScore = score;
|
|
1886
189
|
}
|
|
1887
190
|
}
|
|
1888
|
-
return best.map((point) => clampPointToScene(point,
|
|
191
|
+
return best.map((point) => clampPointToScene(point, bounds));
|
|
1889
192
|
}
|
|
1890
193
|
|
|
1891
|
-
// src/
|
|
194
|
+
// src/edges.ts
|
|
1892
195
|
var EDGE_LAYER_ATTR = "data-markdy-edge-layer";
|
|
1893
|
-
var
|
|
1894
|
-
request: "
|
|
1895
|
-
response: "
|
|
1896
|
-
|
|
196
|
+
var EDGE_STYLES = {
|
|
197
|
+
request: { dash: "", marker: "arrow" },
|
|
198
|
+
response: { dash: "6 4", marker: "arrow-open" },
|
|
199
|
+
event: { dash: "2 6", marker: "dot" },
|
|
200
|
+
dependency: { dash: "4 4", marker: "none" }
|
|
1897
201
|
};
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
202
|
+
function dedupePoints(points) {
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const p of points) {
|
|
205
|
+
const last = out[out.length - 1];
|
|
206
|
+
if (!last || Math.abs(last.x - p.x) > 0.01 || Math.abs(last.y - p.y) > 0.01) out.push(p);
|
|
207
|
+
}
|
|
208
|
+
return out.length >= 2 ? out : points;
|
|
209
|
+
}
|
|
1903
210
|
function ensureEdgeLayer(scene) {
|
|
1904
211
|
const existing = scene.querySelector(`svg[${EDGE_LAYER_ATTR}='1']`);
|
|
1905
212
|
if (existing) return existing;
|
|
@@ -1917,412 +224,338 @@ function ensureEdgeLayer(scene) {
|
|
|
1917
224
|
scene.appendChild(svg);
|
|
1918
225
|
return svg;
|
|
1919
226
|
}
|
|
1920
|
-
function
|
|
1921
|
-
|
|
227
|
+
function ensureDefs(svg, theme, id) {
|
|
228
|
+
const key = `data-markdy-defs-${id}`;
|
|
229
|
+
if (svg.querySelector(`defs[${key}='1']`)) return;
|
|
1922
230
|
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
1923
|
-
defs.setAttribute(
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
glow2.append(blur2, merge);
|
|
1940
|
-
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
1941
|
-
arrow.setAttribute("id", "markdy-flow-arrow");
|
|
1942
|
-
arrow.setAttribute("viewBox", "0 0 10 10");
|
|
1943
|
-
arrow.setAttribute("refX", "8");
|
|
1944
|
-
arrow.setAttribute("refY", "5");
|
|
1945
|
-
arrow.setAttribute("markerWidth", "6");
|
|
1946
|
-
arrow.setAttribute("markerHeight", "6");
|
|
1947
|
-
arrow.setAttribute("orient", "auto-start-reverse");
|
|
1948
|
-
const arrowPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1949
|
-
arrowPath.setAttribute("d", "M 1 1 L 9 5 L 1 9 z");
|
|
1950
|
-
arrowPath.setAttribute("fill", "context-stroke");
|
|
1951
|
-
arrow.appendChild(arrowPath);
|
|
1952
|
-
defs.append(glow2, arrow);
|
|
231
|
+
defs.setAttribute(key, "1");
|
|
232
|
+
for (const [kind, color] of Object.entries(theme.edges)) {
|
|
233
|
+
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
234
|
+
arrow.setAttribute("id", `${id}-arrow-${kind}`);
|
|
235
|
+
arrow.setAttribute("viewBox", "0 0 10 10");
|
|
236
|
+
arrow.setAttribute("refX", "8");
|
|
237
|
+
arrow.setAttribute("refY", "5");
|
|
238
|
+
arrow.setAttribute("markerWidth", "6");
|
|
239
|
+
arrow.setAttribute("markerHeight", "6");
|
|
240
|
+
arrow.setAttribute("orient", "auto-start-reverse");
|
|
241
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
242
|
+
path.setAttribute("d", "M 1 1 L 9 5 L 1 9 z");
|
|
243
|
+
path.setAttribute("fill", color);
|
|
244
|
+
arrow.appendChild(path);
|
|
245
|
+
defs.appendChild(arrow);
|
|
246
|
+
}
|
|
1953
247
|
svg.prepend(defs);
|
|
1954
248
|
}
|
|
1955
|
-
function
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
const halfH = (rect.y2 - rect.y1) / 2 + 4;
|
|
1963
|
-
return { x1: 0, y1: state.y - halfH, x2: sceneWidth, y2: state.y + halfH };
|
|
1964
|
-
}
|
|
1965
|
-
function buildEdge(ctx, targetName) {
|
|
1966
|
-
const { ev, state, states, ast, scene, baseOpts, anims } = ctx;
|
|
1967
|
-
const targetState = states.get(targetName);
|
|
1968
|
-
if (!targetState) return;
|
|
1969
|
-
const lane = ev.line % 5 - 2;
|
|
1970
|
-
const points = routeFlowPath(ev.actor, targetName, state, targetState, states, ast, lane);
|
|
1971
|
-
const length = polylineLength(points);
|
|
1972
|
-
const pathD = toPathD(points);
|
|
1973
|
-
const stroke = STROKE_BY_ACTION[ev.action] ?? DEFAULT_STROKE;
|
|
1974
|
-
const svg = ensureEdgeLayer(scene);
|
|
1975
|
-
ensureEdgeDefs(svg);
|
|
1976
|
-
const startMs = Number(baseOpts.delay ?? 0);
|
|
1977
|
-
const durationMs = Math.max(1, Number(baseOpts.duration ?? 500));
|
|
1978
|
-
const revealMs = Math.min(EDGE_REVEAL_MS, durationMs);
|
|
249
|
+
function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane) {
|
|
250
|
+
ensureDefs(svg, theme, sceneId);
|
|
251
|
+
const color = theme.edges[kind];
|
|
252
|
+
const style = EDGE_STYLES[kind];
|
|
253
|
+
const points = dedupePoints(routeOrthogonal(boxRect(from), boxRect(to), routeObstacles, bounds, lane));
|
|
254
|
+
const d = toPathD(points);
|
|
255
|
+
const len = polylineLength(points);
|
|
1979
256
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1980
|
-
group.setAttribute("data-
|
|
257
|
+
group.setAttribute("data-edge-kind", kind);
|
|
1981
258
|
group.style.opacity = "0";
|
|
1982
259
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1983
|
-
path.setAttribute("d",
|
|
260
|
+
path.setAttribute("d", d);
|
|
1984
261
|
path.setAttribute("fill", "none");
|
|
1985
|
-
path.setAttribute("stroke",
|
|
1986
|
-
path.setAttribute("stroke-width", "
|
|
1987
|
-
path.setAttribute("stroke-linecap", "round");
|
|
262
|
+
path.setAttribute("stroke", color);
|
|
263
|
+
path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2.5");
|
|
1988
264
|
path.setAttribute("stroke-linejoin", "round");
|
|
1989
|
-
path.setAttribute("
|
|
1990
|
-
path.setAttribute("
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
const
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
label.setAttribute("paint-order", "stroke");
|
|
2023
|
-
label.setAttribute("stroke", "rgba(2, 6, 23, 0.9)");
|
|
2024
|
-
label.setAttribute("stroke-width", "5");
|
|
2025
|
-
label.setAttribute("stroke-linejoin", "round");
|
|
2026
|
-
label.setAttribute("fill", "#dbe4f0");
|
|
2027
|
-
label.setAttribute("data-full-label", labelText);
|
|
2028
|
-
label.textContent = shown;
|
|
2029
|
-
group.appendChild(label);
|
|
265
|
+
path.setAttribute("stroke-linecap", "round");
|
|
266
|
+
if (style.dash) path.setAttribute("stroke-dasharray", style.dash);
|
|
267
|
+
if (style.marker !== "none") {
|
|
268
|
+
path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
|
|
269
|
+
}
|
|
270
|
+
path.style.filter = `drop-shadow(0 0 4px ${color}55)`;
|
|
271
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
272
|
+
dot.setAttribute("r", "4");
|
|
273
|
+
dot.setAttribute("fill", color);
|
|
274
|
+
dot.style.opacity = "0";
|
|
275
|
+
dot.style.filter = `drop-shadow(0 0 6px ${color})`;
|
|
276
|
+
group.append(path, dot);
|
|
277
|
+
let labelEl;
|
|
278
|
+
let labelRect;
|
|
279
|
+
if (label) {
|
|
280
|
+
const textWidth = label.length * 6.6 + 10;
|
|
281
|
+
const placement = placeFlowLabel(points, textWidth, labelObstacles, bounds);
|
|
282
|
+
labelRect = placement.rect;
|
|
283
|
+
labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
284
|
+
labelEl.setAttribute("x", String(placement.x));
|
|
285
|
+
labelEl.setAttribute("y", String(placement.y));
|
|
286
|
+
labelEl.setAttribute("text-anchor", "middle");
|
|
287
|
+
labelEl.setAttribute("dominant-baseline", "middle");
|
|
288
|
+
labelEl.setAttribute("font-size", "11");
|
|
289
|
+
labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, monospace");
|
|
290
|
+
labelEl.setAttribute("fill", theme.textMuted);
|
|
291
|
+
labelEl.setAttribute("stroke", theme.canvas);
|
|
292
|
+
labelEl.setAttribute("stroke-width", "3");
|
|
293
|
+
labelEl.setAttribute("paint-order", "stroke");
|
|
294
|
+
labelEl.setAttribute("stroke-linejoin", "round");
|
|
295
|
+
labelEl.textContent = label;
|
|
296
|
+
labelEl.style.opacity = "0";
|
|
297
|
+
group.appendChild(labelEl);
|
|
2030
298
|
}
|
|
2031
299
|
svg.appendChild(group);
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
fill: "both"
|
|
2050
|
-
})
|
|
2051
|
-
);
|
|
2052
|
-
}
|
|
2053
|
-
var flowEdge = (ctx) => {
|
|
2054
|
-
const targetName = String(ctx.ev.params.to ?? "");
|
|
2055
|
-
if (targetName) buildEdge(ctx, targetName);
|
|
2056
|
-
};
|
|
2057
|
-
|
|
2058
|
-
// src/theme.ts
|
|
2059
|
-
var DARK_LUMINANCE_THRESHOLD = 140;
|
|
2060
|
-
var NAMED_DARK = {
|
|
2061
|
-
black: true,
|
|
2062
|
-
"#000": true,
|
|
2063
|
-
"#000000": true
|
|
2064
|
-
};
|
|
2065
|
-
function isSceneDark(scene) {
|
|
2066
|
-
const bg = scene.style.background || "white";
|
|
2067
|
-
let hex = bg.trim().replace(/^#/, "");
|
|
2068
|
-
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
2069
|
-
if (hex.length === 6) {
|
|
2070
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
2071
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
2072
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
2073
|
-
return 0.299 * r + 0.587 * g + 0.114 * b <= DARK_LUMINANCE_THRESHOLD;
|
|
2074
|
-
}
|
|
2075
|
-
return NAMED_DARK[bg.toLowerCase()] ?? false;
|
|
2076
|
-
}
|
|
2077
|
-
function speechBubbleTheme(dark) {
|
|
2078
|
-
return dark ? {
|
|
2079
|
-
background: "#1e2530",
|
|
2080
|
-
border: "#475569",
|
|
2081
|
-
text: "#e2e8f0",
|
|
2082
|
-
shadow: "0 2px 8px rgba(0,0,0,0.35)"
|
|
2083
|
-
} : {
|
|
2084
|
-
background: "white",
|
|
2085
|
-
border: "#222",
|
|
2086
|
-
text: "#222",
|
|
2087
|
-
shadow: "0 2px 8px rgba(0,0,0,0.12)"
|
|
2088
|
-
};
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
// src/actions/speech.ts
|
|
2092
|
-
var MAX_FADE_MS = 200;
|
|
2093
|
-
var FADE_FRACTION = 0.15;
|
|
2094
|
-
var say = ({ ev, el, state, delayMs, durMs, scene, anims }) => {
|
|
2095
|
-
const text = String(ev.params.text ?? "");
|
|
2096
|
-
const theme = speechBubbleTheme(isSceneDark(scene));
|
|
2097
|
-
const inverseScale = 1 / (state.scale || 1);
|
|
2098
|
-
const bubble = document.createElement("div");
|
|
2099
|
-
bubble.textContent = text;
|
|
2100
|
-
Object.assign(bubble.style, {
|
|
2101
|
-
position: "absolute",
|
|
2102
|
-
bottom: "calc(100% + 10px)",
|
|
2103
|
-
left: "50%",
|
|
2104
|
-
transform: `translateX(-50%) scale(${inverseScale})`,
|
|
2105
|
-
transformOrigin: "center bottom",
|
|
2106
|
-
background: theme.background,
|
|
2107
|
-
border: `2px solid ${theme.border}`,
|
|
2108
|
-
color: theme.text,
|
|
2109
|
-
borderRadius: "12px",
|
|
2110
|
-
padding: "6px 14px",
|
|
2111
|
-
fontFamily: "system-ui, sans-serif",
|
|
2112
|
-
fontSize: "15px",
|
|
2113
|
-
lineHeight: "1.3",
|
|
2114
|
-
whiteSpace: "nowrap",
|
|
2115
|
-
maxWidth: "220px",
|
|
2116
|
-
overflow: "hidden",
|
|
2117
|
-
textOverflow: "ellipsis",
|
|
2118
|
-
pointerEvents: "none",
|
|
2119
|
-
zIndex: "10",
|
|
2120
|
-
boxShadow: theme.shadow,
|
|
2121
|
-
opacity: "0"
|
|
2122
|
-
});
|
|
2123
|
-
const tail = document.createElement("span");
|
|
2124
|
-
Object.assign(tail.style, {
|
|
2125
|
-
position: "absolute",
|
|
2126
|
-
bottom: "-10px",
|
|
2127
|
-
left: "50%",
|
|
2128
|
-
transform: "translateX(-50%)",
|
|
2129
|
-
width: "0",
|
|
2130
|
-
height: "0",
|
|
2131
|
-
borderLeft: "7px solid transparent",
|
|
2132
|
-
borderRight: "7px solid transparent",
|
|
2133
|
-
borderTop: `10px solid ${theme.border}`
|
|
300
|
+
return { group, path, label: labelEl, dot, pathLen: len, points, labelRect };
|
|
301
|
+
}
|
|
302
|
+
function dotTravelKeyframes(points) {
|
|
303
|
+
const total = polylineLength(points);
|
|
304
|
+
const frames = [];
|
|
305
|
+
let acc = 0;
|
|
306
|
+
let lastOffset = -1;
|
|
307
|
+
points.forEach((p, i) => {
|
|
308
|
+
if (i > 0) acc += Math.hypot(p.x - points[i - 1].x, p.y - points[i - 1].y);
|
|
309
|
+
let offset = total > 0 ? acc / total : i === points.length - 1 ? 1 : 0;
|
|
310
|
+
if (offset <= lastOffset) offset = Math.min(1, lastOffset + 1e-4);
|
|
311
|
+
lastOffset = offset;
|
|
312
|
+
frames.push({
|
|
313
|
+
offset,
|
|
314
|
+
transform: `translate(${p.x}px, ${p.y}px)`,
|
|
315
|
+
opacity: i === 0 || i === points.length - 1 ? 0 : 1
|
|
316
|
+
});
|
|
2134
317
|
});
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
const
|
|
318
|
+
return frames;
|
|
319
|
+
}
|
|
320
|
+
function animateEdgeReveal(runtime, startMs, durMs) {
|
|
321
|
+
const anims = [];
|
|
322
|
+
const { path, dot, label, group, pathLen, points } = runtime;
|
|
323
|
+
path.style.strokeDasharray = String(pathLen);
|
|
324
|
+
path.style.strokeDashoffset = String(pathLen);
|
|
325
|
+
const drawMs = Math.min(220, durMs * 0.5);
|
|
2139
326
|
anims.push(
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
fill: "forwards"
|
|
2144
|
-
|
|
2145
|
-
bubble.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
2146
|
-
delay: delayMs + durMs - fadeMs,
|
|
2147
|
-
duration: fadeMs,
|
|
2148
|
-
fill: "forwards"
|
|
2149
|
-
})
|
|
327
|
+
group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 120, delay: startMs, fill: "forwards" }),
|
|
328
|
+
path.animate(
|
|
329
|
+
[{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
|
|
330
|
+
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "ease-out" }
|
|
331
|
+
)
|
|
2150
332
|
);
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
return img;
|
|
333
|
+
if (label) {
|
|
334
|
+
anims.push(label.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
335
|
+
}
|
|
336
|
+
if (points.length >= 2) {
|
|
337
|
+
anims.push(
|
|
338
|
+
dot.animate(dotTravelKeyframes(points), {
|
|
339
|
+
duration: Math.max(drawMs, durMs),
|
|
340
|
+
delay: startMs,
|
|
341
|
+
fill: "forwards",
|
|
342
|
+
easing: "ease-in-out"
|
|
343
|
+
})
|
|
344
|
+
);
|
|
2164
345
|
}
|
|
2165
|
-
|
|
2166
|
-
span.className = "iconify";
|
|
2167
|
-
span.dataset.icon = assetDef.value;
|
|
2168
|
-
span.style.fontSize = `${PROJECTILE_SIZE_PX}px`;
|
|
2169
|
-
span.style.lineHeight = "1";
|
|
2170
|
-
span.style.display = "inline-block";
|
|
2171
|
-
return span;
|
|
2172
|
-
}
|
|
2173
|
-
var throwAsset = ({
|
|
2174
|
-
ev,
|
|
2175
|
-
state,
|
|
2176
|
-
baseOpts,
|
|
2177
|
-
ast,
|
|
2178
|
-
states,
|
|
2179
|
-
scene,
|
|
2180
|
-
assetOverrides,
|
|
2181
|
-
anims
|
|
2182
|
-
}) => {
|
|
2183
|
-
const assetName = String(ev.params.asset ?? "");
|
|
2184
|
-
const targetState = states.get(String(ev.params.to ?? ""));
|
|
2185
|
-
const assetDef = ast.assets[assetName];
|
|
2186
|
-
if (!assetDef || !targetState) return;
|
|
2187
|
-
const projectile = createProjectile(assetName, assetDef, assetOverrides);
|
|
2188
|
-
Object.assign(projectile.style, {
|
|
2189
|
-
position: "absolute",
|
|
2190
|
-
left: "0",
|
|
2191
|
-
top: "0",
|
|
2192
|
-
pointerEvents: "none",
|
|
2193
|
-
zIndex: "9",
|
|
2194
|
-
opacity: "0"
|
|
2195
|
-
});
|
|
2196
|
-
scene.appendChild(projectile);
|
|
2197
|
-
const anim = projectile.animate(
|
|
2198
|
-
[
|
|
2199
|
-
{ transform: tx(state), opacity: 1 },
|
|
2200
|
-
{ transform: tx(targetState), opacity: 0 }
|
|
2201
|
-
],
|
|
2202
|
-
{ ...baseOpts, easing: "ease-in" }
|
|
2203
|
-
);
|
|
2204
|
-
anim.addEventListener("finish", () => {
|
|
2205
|
-
if (projectile.parentNode === scene) scene.removeChild(projectile);
|
|
2206
|
-
});
|
|
2207
|
-
anims.push(anim);
|
|
2208
|
-
};
|
|
2209
|
-
|
|
2210
|
-
// src/actions/registry.ts
|
|
2211
|
-
var ACTION_HANDLERS = Object.freeze({
|
|
2212
|
-
// Universal — every actor type
|
|
2213
|
-
move,
|
|
2214
|
-
enter,
|
|
2215
|
-
exit,
|
|
2216
|
-
fade_in: fadeIn,
|
|
2217
|
-
fade_out: fadeOut,
|
|
2218
|
-
spring,
|
|
2219
|
-
follow_path: followPath,
|
|
2220
|
-
scale,
|
|
2221
|
-
rotate,
|
|
2222
|
-
shake,
|
|
2223
|
-
pulse,
|
|
2224
|
-
glow,
|
|
2225
|
-
ripple,
|
|
2226
|
-
blur,
|
|
2227
|
-
line_reveal: lineReveal,
|
|
2228
|
-
mask,
|
|
2229
|
-
parallax,
|
|
2230
|
-
jump,
|
|
2231
|
-
bounce,
|
|
2232
|
-
say,
|
|
2233
|
-
throw: throwAsset,
|
|
2234
|
-
// Figure-only — the parser rejects these on other actor types
|
|
2235
|
-
punch,
|
|
2236
|
-
kick,
|
|
2237
|
-
wave,
|
|
2238
|
-
nod,
|
|
2239
|
-
rotate_part: rotatePart,
|
|
2240
|
-
pose,
|
|
2241
|
-
face,
|
|
2242
|
-
// System-diagram flow edges (@markdy/stdlib-systems)
|
|
2243
|
-
request: flowEdge,
|
|
2244
|
-
response: flowEdge,
|
|
2245
|
-
emit: flowEdge
|
|
2246
|
-
});
|
|
2247
|
-
function handlerFor(action) {
|
|
2248
|
-
return ACTION_HANDLERS[action];
|
|
346
|
+
return anims;
|
|
2249
347
|
}
|
|
2250
|
-
|
|
2251
|
-
// src/animations.ts
|
|
2252
|
-
var DEFAULT_DURATION_S = 0.5;
|
|
2253
|
-
var DEFAULT_EASE_BY_ACTION = {
|
|
2254
|
-
enter: "out",
|
|
2255
|
-
fade_in: "out",
|
|
2256
|
-
exit: "in",
|
|
2257
|
-
fade_out: "in"
|
|
2258
|
-
};
|
|
2259
|
-
function buildAnimations(ast, actorEls, actorLayer, cameraLayer, assetOverrides, faceSwaps) {
|
|
348
|
+
function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds) {
|
|
2260
349
|
const anims = [];
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
const
|
|
2266
|
-
|
|
350
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
351
|
+
const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
|
|
352
|
+
const allNodeRects = [...rectById.values()];
|
|
353
|
+
const placedLabels = [];
|
|
354
|
+
const laneByPair = /* @__PURE__ */ new Map();
|
|
355
|
+
const svg = ensureEdgeLayer(scene);
|
|
356
|
+
const sceneId = `md-${Math.random().toString(36).slice(2, 8)}`;
|
|
357
|
+
anims.push(
|
|
358
|
+
titleEl.animate(
|
|
359
|
+
[{ opacity: 0, transform: "translateY(-6px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
360
|
+
{ duration: 420, delay: 0, fill: "forwards", easing: "ease-out" }
|
|
361
|
+
)
|
|
2267
362
|
);
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
};
|
|
2284
|
-
if (ev.actor === "camera") {
|
|
2285
|
-
buildCameraAction(ev, cameraLayer, ast, baseOpts, anims, cameraState);
|
|
363
|
+
for (const cue of cues) {
|
|
364
|
+
const startMs = cue.start * 1e3;
|
|
365
|
+
const durMs = cue.duration * 1e3;
|
|
366
|
+
if (cue.kind === "show") {
|
|
367
|
+
cue.targets.forEach((id, idx) => {
|
|
368
|
+
const el = nodeEls.get(id);
|
|
369
|
+
if (!el) return;
|
|
370
|
+
const delay = startMs + (typeof cue.params.stagger === "number" ? cue.params.stagger * 1e3 * idx : 0);
|
|
371
|
+
anims.push(
|
|
372
|
+
el.animate(
|
|
373
|
+
[{ opacity: 0, transform: "translateY(8px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
374
|
+
{ duration: durMs, delay, fill: "forwards", easing: "ease-out" }
|
|
375
|
+
)
|
|
376
|
+
);
|
|
377
|
+
});
|
|
2286
378
|
continue;
|
|
2287
379
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
380
|
+
if (cue.kind === "hide") {
|
|
381
|
+
for (const id of cue.targets) {
|
|
382
|
+
const el = nodeEls.get(id);
|
|
383
|
+
if (!el) continue;
|
|
384
|
+
anims.push(el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
|
|
385
|
+
}
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (cue.kind === "glow") {
|
|
389
|
+
for (const id of cue.targets) {
|
|
390
|
+
const el = nodeEls.get(id);
|
|
391
|
+
if (!el) continue;
|
|
392
|
+
if (cue.params.color) el.style.setProperty("--md-glow-color", String(cue.params.color));
|
|
393
|
+
el.dataset.glow = "1";
|
|
394
|
+
anims.push(
|
|
395
|
+
el.animate(
|
|
396
|
+
[{ filter: "brightness(1)" }, { filter: "brightness(1.15)" }, { filter: "brightness(1)" }],
|
|
397
|
+
{ duration: durMs, delay: startMs, fill: "forwards" }
|
|
398
|
+
)
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (cue.kind === "focus") {
|
|
404
|
+
for (const id of cue.targets) {
|
|
405
|
+
const el = nodeEls.get(id);
|
|
406
|
+
if (!el) continue;
|
|
407
|
+
el.dataset.focused = "1";
|
|
408
|
+
anims.push(
|
|
409
|
+
el.animate(
|
|
410
|
+
[{ transform: "scale(1)" }, { transform: "scale(1.03)" }, { transform: "scale(1)" }],
|
|
411
|
+
{ duration: durMs, delay: startMs, fill: "forwards" }
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (cue.kind === "flow" && cue.segments?.[0]) {
|
|
418
|
+
const seg = cue.segments[0];
|
|
419
|
+
const from = nodeById.get(seg.from);
|
|
420
|
+
const to = nodeById.get(seg.to);
|
|
421
|
+
if (!from || !to) continue;
|
|
422
|
+
const routeObstacles = [];
|
|
423
|
+
for (const [id, rect] of rectById) {
|
|
424
|
+
if (id !== seg.from && id !== seg.to) routeObstacles.push(rect);
|
|
425
|
+
}
|
|
426
|
+
const pairKey = [seg.from, seg.to].sort().join("|");
|
|
427
|
+
const lane = laneByPair.get(pairKey) ?? 0;
|
|
428
|
+
laneByPair.set(pairKey, lane + 1);
|
|
429
|
+
const labelObstacles = [...allNodeRects, ...placedLabels];
|
|
430
|
+
const runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane);
|
|
431
|
+
if (runtime.labelRect) placedLabels.push(runtime.labelRect);
|
|
432
|
+
anims.push(...animateEdgeReveal(runtime, startMs, durMs));
|
|
433
|
+
}
|
|
2309
434
|
}
|
|
435
|
+
for (const a of anims) a.pause();
|
|
2310
436
|
return anims;
|
|
2311
437
|
}
|
|
2312
438
|
|
|
2313
|
-
// src/
|
|
439
|
+
// src/nodes.ts
|
|
440
|
+
var STYLE_ID = "markdy-diagram-node-styles";
|
|
441
|
+
function ensureNodeStyles(doc) {
|
|
442
|
+
if (doc.getElementById(STYLE_ID)) return;
|
|
443
|
+
const style = doc.createElement("style");
|
|
444
|
+
style.id = STYLE_ID;
|
|
445
|
+
style.textContent = `
|
|
446
|
+
.markdy-node {
|
|
447
|
+
position: absolute;
|
|
448
|
+
box-sizing: border-box;
|
|
449
|
+
width: var(--md-node-w, 184px);
|
|
450
|
+
height: var(--md-node-h, 88px);
|
|
451
|
+
border-radius: 16px;
|
|
452
|
+
border: 1px solid var(--md-border);
|
|
453
|
+
background: linear-gradient(145deg, var(--md-surface-raised), var(--md-surface));
|
|
454
|
+
color: var(--md-text);
|
|
455
|
+
box-shadow: 0 12px 32px rgba(2, 6, 23, 0.28), 0 0 0 1px rgba(255,255,255,0.04) inset;
|
|
456
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
opacity: 0;
|
|
459
|
+
transform: translateY(8px);
|
|
460
|
+
transition: box-shadow 0.3s ease;
|
|
461
|
+
}
|
|
462
|
+
.markdy-node[data-visible="1"] {
|
|
463
|
+
opacity: 1;
|
|
464
|
+
transform: translateY(0);
|
|
465
|
+
}
|
|
466
|
+
.markdy-node[data-focused="1"] {
|
|
467
|
+
box-shadow: 0 0 0 2px var(--md-accent), 0 16px 40px rgba(2, 6, 23, 0.35);
|
|
468
|
+
}
|
|
469
|
+
.markdy-node[data-glow="1"] {
|
|
470
|
+
box-shadow: 0 0 24px var(--md-glow-color, var(--md-accent)), 0 0 0 1px var(--md-glow-color, var(--md-accent)) inset;
|
|
471
|
+
}
|
|
472
|
+
.markdy-node__rail {
|
|
473
|
+
position: absolute;
|
|
474
|
+
left: 0;
|
|
475
|
+
top: 0;
|
|
476
|
+
bottom: 0;
|
|
477
|
+
width: 4px;
|
|
478
|
+
background: var(--md-role-color, var(--md-accent));
|
|
479
|
+
border-radius: 16px 0 0 16px;
|
|
480
|
+
}
|
|
481
|
+
.markdy-node__type {
|
|
482
|
+
padding: 10px 14px 0 18px;
|
|
483
|
+
font-size: 10px;
|
|
484
|
+
font-weight: 700;
|
|
485
|
+
letter-spacing: 0.08em;
|
|
486
|
+
text-transform: uppercase;
|
|
487
|
+
color: var(--md-text-muted);
|
|
488
|
+
}
|
|
489
|
+
.markdy-node__label {
|
|
490
|
+
padding: 2px 14px 0 18px;
|
|
491
|
+
font-size: 15px;
|
|
492
|
+
font-weight: 650;
|
|
493
|
+
line-height: 1.25;
|
|
494
|
+
white-space: nowrap;
|
|
495
|
+
overflow: hidden;
|
|
496
|
+
text-overflow: ellipsis;
|
|
497
|
+
}
|
|
498
|
+
.markdy-node[data-role="client"] { border-radius: 20px 20px 8px 8px; }
|
|
499
|
+
.markdy-node[data-role="data"] { border-radius: 16px 16px 28px 28px; }
|
|
500
|
+
.markdy-node[data-role="flow"] { transform: rotate(0deg); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
|
|
501
|
+
.markdy-node[data-role="network"] { clip-path: polygon(8% 0%, 92% 0%, 100% 50%, 92% 100%, 8% 100%, 0% 50%); }
|
|
502
|
+
.markdy-scene-title {
|
|
503
|
+
position: absolute;
|
|
504
|
+
left: 64px;
|
|
505
|
+
top: 28px;
|
|
506
|
+
right: 64px;
|
|
507
|
+
font-size: 32px;
|
|
508
|
+
font-weight: 700;
|
|
509
|
+
line-height: 1.2;
|
|
510
|
+
color: var(--md-text);
|
|
511
|
+
opacity: 0;
|
|
512
|
+
transform: translateY(-6px);
|
|
513
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
514
|
+
}
|
|
515
|
+
.markdy-scene-title[data-visible="1"] {
|
|
516
|
+
opacity: 1;
|
|
517
|
+
transform: translateY(0);
|
|
518
|
+
}
|
|
519
|
+
`;
|
|
520
|
+
doc.head.appendChild(style);
|
|
521
|
+
}
|
|
522
|
+
function createNodeEl(node, theme) {
|
|
523
|
+
const el = document.createElement("div");
|
|
524
|
+
el.className = "markdy-node markdy-scene-actor";
|
|
525
|
+
el.dataset.actor = node.id;
|
|
526
|
+
el.dataset.role = node.role;
|
|
527
|
+
el.style.left = `${node.x}px`;
|
|
528
|
+
el.style.top = `${node.y}px`;
|
|
529
|
+
el.style.setProperty("--md-node-w", `${node.width}px`);
|
|
530
|
+
el.style.setProperty("--md-node-h", `${node.height}px`);
|
|
531
|
+
const roleColor = theme.roles[node.role] ?? theme.accent;
|
|
532
|
+
el.style.setProperty("--md-role-color", roleColor);
|
|
533
|
+
const rail = document.createElement("div");
|
|
534
|
+
rail.className = "markdy-node__rail";
|
|
535
|
+
const type = document.createElement("div");
|
|
536
|
+
type.className = "markdy-node__type";
|
|
537
|
+
type.textContent = node.kind.replace(/_/g, " ");
|
|
538
|
+
const label = document.createElement("div");
|
|
539
|
+
label.className = "markdy-node__label";
|
|
540
|
+
label.textContent = node.label;
|
|
541
|
+
el.append(rail, type, label);
|
|
542
|
+
return el;
|
|
543
|
+
}
|
|
544
|
+
function createTitleEl(title) {
|
|
545
|
+
const el = document.createElement("div");
|
|
546
|
+
el.className = "markdy-scene-title";
|
|
547
|
+
el.textContent = title;
|
|
548
|
+
return el;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/theme.ts
|
|
2314
552
|
var SCENE_STYLE_ID = "markdy-scene-ambience-styles";
|
|
2315
|
-
var SAFE_FRAME_PADDING = 28;
|
|
2316
553
|
function ensureSceneStyles(doc) {
|
|
2317
554
|
if (doc.getElementById(SCENE_STYLE_ID)) return;
|
|
2318
555
|
const style = doc.createElement("style");
|
|
2319
556
|
style.id = SCENE_STYLE_ID;
|
|
2320
557
|
style.textContent = `
|
|
2321
558
|
.markdy-scene-root {
|
|
2322
|
-
--markdy-scene-grid: rgba(148, 163, 184, 0.13);
|
|
2323
|
-
--markdy-scene-grid-strong: rgba(148, 163, 184, 0.2);
|
|
2324
|
-
--markdy-scene-vignette: rgba(2, 6, 23, 0.72);
|
|
2325
|
-
--markdy-scene-accent: rgba(56, 189, 248, 0.16);
|
|
2326
559
|
isolation: isolate;
|
|
2327
560
|
}
|
|
2328
561
|
.markdy-scene-root::before,
|
|
@@ -2335,149 +568,69 @@ function ensureSceneStyles(doc) {
|
|
|
2335
568
|
.markdy-scene-root::before {
|
|
2336
569
|
z-index: 0;
|
|
2337
570
|
background:
|
|
2338
|
-
linear-gradient(var(--
|
|
2339
|
-
linear-gradient(90deg, var(--
|
|
2340
|
-
linear-gradient(var(--
|
|
2341
|
-
linear-gradient(90deg, var(--
|
|
571
|
+
linear-gradient(var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
|
|
572
|
+
linear-gradient(90deg, var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
|
|
573
|
+
linear-gradient(var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px,
|
|
574
|
+
linear-gradient(90deg, var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px;
|
|
2342
575
|
mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
|
|
2343
576
|
opacity: 0.82;
|
|
2344
577
|
}
|
|
2345
578
|
.markdy-scene-root::after {
|
|
2346
579
|
z-index: 1;
|
|
2347
580
|
background:
|
|
2348
|
-
radial-gradient(ellipse at 50% 0%, var(--
|
|
2349
|
-
linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.08) 58%, var(--
|
|
2350
|
-
linear-gradient(90deg, rgba(255, 255, 255, 0.035), transparent 18%, transparent 82%, rgba(255, 255, 255, 0.035));
|
|
2351
|
-
mix-blend-mode: screen;
|
|
581
|
+
radial-gradient(ellipse at 50% 0%, color-mix(in srgb, var(--md-accent) 16%, transparent), transparent 42%),
|
|
582
|
+
linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.08) 58%, var(--md-vignette) 100%);
|
|
2352
583
|
opacity: 0.8;
|
|
2353
584
|
}
|
|
2354
|
-
.markdy-scene-
|
|
2355
|
-
--markdy-scene-grid: rgba(15, 23, 42, 0.09);
|
|
2356
|
-
--markdy-scene-grid-strong: rgba(15, 23, 42, 0.14);
|
|
2357
|
-
--markdy-scene-vignette: rgba(241, 245, 249, 0.74);
|
|
2358
|
-
--markdy-scene-accent: rgba(14, 165, 233, 0.12);
|
|
2359
|
-
}
|
|
2360
|
-
.markdy-scene-content {
|
|
2361
|
-
z-index: 2;
|
|
2362
|
-
}
|
|
585
|
+
.markdy-scene-content { z-index: 2; }
|
|
2363
586
|
.markdy-scene-actor-layer {
|
|
2364
587
|
position: absolute;
|
|
2365
588
|
inset: 0;
|
|
2366
589
|
overflow: visible;
|
|
2367
|
-
transform-origin: 0 0;
|
|
2368
|
-
will-change: transform;
|
|
2369
590
|
}
|
|
2370
591
|
`;
|
|
2371
592
|
doc.head.appendChild(style);
|
|
2372
593
|
}
|
|
2373
|
-
function
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
function computeContentBounds(ast) {
|
|
2388
|
-
let bounds = null;
|
|
2389
|
-
const maxScaleByActor = /* @__PURE__ */ new Map();
|
|
2390
|
-
const positionsByActor = /* @__PURE__ */ new Map();
|
|
2391
|
-
for (const [name, def] of Object.entries(ast.actors)) {
|
|
2392
|
-
maxScaleByActor.set(name, Math.max(1e-3, def.scale ?? 1));
|
|
2393
|
-
positionsByActor.set(name, [[def.x, def.y]]);
|
|
2394
|
-
}
|
|
2395
|
-
for (const ev of ast.events) {
|
|
2396
|
-
if (ev.actor === "camera") continue;
|
|
2397
|
-
if (ev.action === "scale" && typeof ev.params.to === "number") {
|
|
2398
|
-
maxScaleByActor.set(ev.actor, Math.max(maxScaleByActor.get(ev.actor) ?? 1, ev.params.to));
|
|
2399
|
-
}
|
|
2400
|
-
if (ev.action === "move" || ev.action === "spring") {
|
|
2401
|
-
const to = numericPair(ev.params.to);
|
|
2402
|
-
if (to) positionsByActor.get(ev.actor)?.push(to);
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
for (const [name, def] of Object.entries(ast.actors)) {
|
|
2406
|
-
const positions = positionsByActor.get(name) ?? [[def.x, def.y]];
|
|
2407
|
-
const scale2 = maxScaleByActor.get(name) ?? def.scale ?? 1;
|
|
2408
|
-
for (const [x, y] of positions) {
|
|
2409
|
-
bounds = unionRect(bounds, actorRect({ ...stateFrom(def), x, y, scale: scale2 }, def.type));
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
return bounds;
|
|
2413
|
-
}
|
|
2414
|
-
function computeSafeFrame(ast) {
|
|
2415
|
-
const bounds = computeContentBounds(ast);
|
|
2416
|
-
if (!bounds) return { x: 0, y: 0, scale: 1 };
|
|
2417
|
-
const boundsWidth = Math.max(1, bounds.x2 - bounds.x1);
|
|
2418
|
-
const boundsHeight = Math.max(1, bounds.y2 - bounds.y1);
|
|
2419
|
-
const availableWidth = Math.max(1, ast.meta.width - SAFE_FRAME_PADDING * 2);
|
|
2420
|
-
const availableHeight = Math.max(1, ast.meta.height - SAFE_FRAME_PADDING * 2);
|
|
2421
|
-
const scale2 = Math.min(1, availableWidth / boundsWidth, availableHeight / boundsHeight);
|
|
2422
|
-
const scaledWidth = boundsWidth * scale2;
|
|
2423
|
-
const scaledHeight = boundsHeight * scale2;
|
|
2424
|
-
function axisOffset(min, max, sceneSize, scaledSize) {
|
|
2425
|
-
const paddedMin = SAFE_FRAME_PADDING;
|
|
2426
|
-
const paddedMax = sceneSize - SAFE_FRAME_PADDING;
|
|
2427
|
-
const scaledMin = min * scale2;
|
|
2428
|
-
const scaledMax = max * scale2;
|
|
2429
|
-
if (scaledSize > paddedMax - paddedMin) return (sceneSize - scaledSize) / 2 - scaledMin;
|
|
2430
|
-
if (scaledMin < paddedMin) return paddedMin - scaledMin;
|
|
2431
|
-
if (scaledMax > paddedMax) return paddedMax - scaledMax;
|
|
2432
|
-
return 0;
|
|
2433
|
-
}
|
|
2434
|
-
const x = axisOffset(bounds.x1, bounds.x2, ast.meta.width, scaledWidth);
|
|
2435
|
-
const y = axisOffset(bounds.y1, bounds.y2, ast.meta.height, scaledHeight);
|
|
2436
|
-
return {
|
|
2437
|
-
x: Math.round(x * 1e3) / 1e3,
|
|
2438
|
-
y: Math.round(y * 1e3) / 1e3,
|
|
2439
|
-
scale: Math.round(scale2 * 1e3) / 1e3
|
|
2440
|
-
};
|
|
2441
|
-
}
|
|
2442
|
-
function bgToTextColor(bg) {
|
|
2443
|
-
let hex = bg.trim().replace(/^#/, "");
|
|
2444
|
-
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
2445
|
-
if (hex.length === 6) {
|
|
2446
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
2447
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
2448
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
2449
|
-
return 0.299 * r + 0.587 * g + 0.114 * b > 140 ? "#1a1a1a" : "#f0f0f0";
|
|
2450
|
-
}
|
|
2451
|
-
const named = {
|
|
2452
|
-
white: "#1a1a1a",
|
|
2453
|
-
black: "#f0f0f0",
|
|
2454
|
-
transparent: "#1a1a1a"
|
|
2455
|
-
};
|
|
2456
|
-
return named[bg.toLowerCase()] ?? "#1a1a1a";
|
|
594
|
+
function applyThemeToScene(scene, theme) {
|
|
595
|
+
scene.style.background = theme.canvas;
|
|
596
|
+
scene.style.color = theme.text;
|
|
597
|
+
scene.style.setProperty("--md-canvas", theme.canvas);
|
|
598
|
+
scene.style.setProperty("--md-surface", theme.surface);
|
|
599
|
+
scene.style.setProperty("--md-surface-raised", theme.surfaceRaised);
|
|
600
|
+
scene.style.setProperty("--md-border", theme.border);
|
|
601
|
+
scene.style.setProperty("--md-text", theme.text);
|
|
602
|
+
scene.style.setProperty("--md-text-muted", theme.textMuted);
|
|
603
|
+
scene.style.setProperty("--md-grid-minor", theme.gridMinor);
|
|
604
|
+
scene.style.setProperty("--md-grid-major", theme.gridMajor);
|
|
605
|
+
scene.style.setProperty("--md-vignette", theme.vignette);
|
|
606
|
+
scene.style.setProperty("--md-accent", theme.accent);
|
|
607
|
+
scene.dataset.markdyTheme = theme.name;
|
|
2457
608
|
}
|
|
609
|
+
|
|
610
|
+
// src/player.ts
|
|
2458
611
|
function createPlayer(opts) {
|
|
2459
612
|
const {
|
|
2460
613
|
container,
|
|
2461
614
|
code,
|
|
2462
|
-
assets: assetOverrides = {},
|
|
2463
|
-
imports,
|
|
2464
615
|
autoplay = true,
|
|
2465
616
|
loop = true,
|
|
2466
617
|
copyright = true,
|
|
2467
618
|
progressBar = true,
|
|
2468
|
-
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}
|
|
619
|
+
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}`),
|
|
2469
620
|
onTimeUpdate,
|
|
2470
621
|
onPlayStateChange
|
|
2471
622
|
} = opts;
|
|
2472
|
-
const ast
|
|
2473
|
-
const
|
|
2474
|
-
|
|
2475
|
-
|
|
623
|
+
const { ast, plan } = parseAndCompile(code);
|
|
624
|
+
for (const w of ast.diagnostics) {
|
|
625
|
+
if (w.severity === "warning") onWarning(w);
|
|
626
|
+
}
|
|
627
|
+
const totalDurationMs = plan.duration * 1e3;
|
|
628
|
+
const durationSeconds = plan.duration;
|
|
2476
629
|
const viewport = document.createElement("div");
|
|
2477
630
|
Object.assign(viewport.style, {
|
|
2478
631
|
position: "relative",
|
|
2479
632
|
width: "100%",
|
|
2480
|
-
aspectRatio: `${
|
|
633
|
+
aspectRatio: `${plan.meta.width} / ${plan.meta.height}`,
|
|
2481
634
|
overflow: "hidden"
|
|
2482
635
|
});
|
|
2483
636
|
container.appendChild(viewport);
|
|
@@ -2493,13 +646,14 @@ function createPlayer(opts) {
|
|
|
2493
646
|
});
|
|
2494
647
|
viewport.appendChild(progressEl);
|
|
2495
648
|
}
|
|
2496
|
-
const tlAngle =
|
|
649
|
+
const tlAngle = Math.atan2(-plan.meta.width / 2, plan.meta.height / 2) * 180 / Math.PI;
|
|
650
|
+
const tlAngleNorm = (tlAngle % 360 + 360) % 360;
|
|
2497
651
|
function updateProgressBar(pct) {
|
|
2498
652
|
if (!progressEl) return;
|
|
2499
653
|
const deg = pct * 360;
|
|
2500
654
|
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%)";
|
|
2501
|
-
progressEl.style.background = `conic-gradient(from ${
|
|
2502
|
-
progressEl.style.mask =
|
|
655
|
+
progressEl.style.background = `conic-gradient(from ${tlAngleNorm}deg, ${rainbow} ${deg}deg, transparent ${deg}deg)`;
|
|
656
|
+
progressEl.style.mask = "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)";
|
|
2503
657
|
progressEl.style.webkitMask = progressEl.style.mask;
|
|
2504
658
|
progressEl.style.maskComposite = "exclude";
|
|
2505
659
|
progressEl.style.webkitMaskComposite = "xor";
|
|
@@ -2516,78 +670,57 @@ function createPlayer(opts) {
|
|
|
2516
670
|
display: "block",
|
|
2517
671
|
textAlign: "right",
|
|
2518
672
|
fontSize: "10px",
|
|
2519
|
-
fontFamily: "system-ui,
|
|
673
|
+
fontFamily: "system-ui, sans-serif",
|
|
2520
674
|
color: "#999",
|
|
2521
675
|
textDecoration: "none",
|
|
2522
676
|
padding: "3px 6px 0",
|
|
2523
|
-
opacity: "0.7"
|
|
2524
|
-
transition: "opacity 0.2s",
|
|
2525
|
-
maxWidth: container.style.maxWidth || "100%",
|
|
2526
|
-
width: "100%"
|
|
2527
|
-
});
|
|
2528
|
-
badge.addEventListener("mouseenter", () => {
|
|
2529
|
-
badge.style.opacity = "1";
|
|
677
|
+
opacity: "0.7"
|
|
2530
678
|
});
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
});
|
|
2534
|
-
if (container.parentNode) {
|
|
2535
|
-
container.parentNode.insertBefore(badge, container.nextSibling);
|
|
2536
|
-
} else {
|
|
2537
|
-
container.appendChild(badge);
|
|
2538
|
-
}
|
|
679
|
+
if (container.parentNode) container.parentNode.insertBefore(badge, container.nextSibling);
|
|
680
|
+
else container.appendChild(badge);
|
|
2539
681
|
}
|
|
2540
|
-
const scene = document.createElement("div");
|
|
2541
682
|
ensureSceneStyles(document);
|
|
683
|
+
ensureNodeStyles(document);
|
|
684
|
+
const scene = document.createElement("div");
|
|
2542
685
|
scene.className = "markdy-scene-root";
|
|
2543
|
-
scene.dataset.markdySceneTone = bgToTextColor(ast.meta.bg) === "#1a1a1a" ? "light" : "dark";
|
|
2544
686
|
Object.assign(scene.style, {
|
|
2545
687
|
position: "absolute",
|
|
2546
688
|
top: "0",
|
|
2547
689
|
left: "0",
|
|
2548
|
-
width: `${
|
|
2549
|
-
height: `${
|
|
2550
|
-
background: ast.meta.bg,
|
|
2551
|
-
color: bgToTextColor(ast.meta.bg),
|
|
690
|
+
width: `${plan.meta.width}px`,
|
|
691
|
+
height: `${plan.meta.height}px`,
|
|
2552
692
|
overflow: "hidden",
|
|
2553
693
|
userSelect: "none",
|
|
2554
694
|
transformOrigin: "0 0"
|
|
2555
695
|
});
|
|
696
|
+
applyThemeToScene(scene, plan.theme);
|
|
2556
697
|
viewport.appendChild(scene);
|
|
2557
698
|
const sceneContent = document.createElement("div");
|
|
2558
699
|
sceneContent.className = "markdy-scene-content";
|
|
2559
|
-
Object.assign(sceneContent.style, {
|
|
2560
|
-
position: "absolute",
|
|
2561
|
-
top: "0",
|
|
2562
|
-
left: "0",
|
|
2563
|
-
width: "100%",
|
|
2564
|
-
height: "100%",
|
|
2565
|
-
transformOrigin: "50% 50%",
|
|
2566
|
-
willChange: "transform"
|
|
2567
|
-
});
|
|
700
|
+
Object.assign(sceneContent.style, { position: "absolute", inset: "0" });
|
|
2568
701
|
scene.appendChild(sceneContent);
|
|
2569
702
|
const actorLayer = document.createElement("div");
|
|
2570
703
|
actorLayer.className = "markdy-scene-actor-layer";
|
|
2571
|
-
const safeFrame = computeSafeFrame(ast);
|
|
2572
|
-
actorLayer.dataset.markdySafeFrame = `${safeFrame.x},${safeFrame.y},${safeFrame.scale}`;
|
|
2573
|
-
actorLayer.style.transform = `translate(${safeFrame.x}px, ${safeFrame.y}px) scale(${safeFrame.scale})`;
|
|
2574
704
|
sceneContent.appendChild(actorLayer);
|
|
705
|
+
const titleEl = createTitleEl(plan.title);
|
|
706
|
+
actorLayer.appendChild(titleEl);
|
|
707
|
+
const nodeEls = /* @__PURE__ */ new Map();
|
|
708
|
+
for (const node of plan.nodes) {
|
|
709
|
+
const el = createNodeEl(node, plan.theme);
|
|
710
|
+
actorLayer.appendChild(el);
|
|
711
|
+
nodeEls.set(node.id, el);
|
|
712
|
+
}
|
|
2575
713
|
function scaleScene() {
|
|
2576
|
-
const s = viewport.clientWidth /
|
|
714
|
+
const s = viewport.clientWidth / plan.meta.width;
|
|
2577
715
|
scene.style.transform = `scale(${s})`;
|
|
2578
716
|
}
|
|
2579
717
|
scaleScene();
|
|
2580
718
|
const resizeObserver = new ResizeObserver(scaleScene);
|
|
2581
719
|
resizeObserver.observe(viewport);
|
|
2582
|
-
const
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
actorEls.set(name, el);
|
|
2587
|
-
}
|
|
2588
|
-
const faceSwaps = [];
|
|
2589
|
-
const allAnims = buildAnimations(ast, actorEls, actorLayer, sceneContent, assetOverrides, faceSwaps);
|
|
2590
|
-
faceSwaps.sort((a, b) => a.timeMs - b.timeMs);
|
|
720
|
+
const allAnims = buildCueAnimations(plan.cues, nodeEls, plan.nodes, plan.theme, sceneContent, titleEl, {
|
|
721
|
+
width: plan.meta.width,
|
|
722
|
+
height: plan.meta.height
|
|
723
|
+
});
|
|
2591
724
|
for (const anim of allAnims) {
|
|
2592
725
|
anim.pause();
|
|
2593
726
|
anim.currentTime = 0;
|
|
@@ -2596,47 +729,16 @@ function createPlayer(opts) {
|
|
|
2596
729
|
let lastRafTs = null;
|
|
2597
730
|
let isPlaying = false;
|
|
2598
731
|
let rafId = null;
|
|
2599
|
-
for (const { el } of faceSwaps) {
|
|
2600
|
-
if (!el.dataset["figFaceInitial"]) {
|
|
2601
|
-
el.dataset["figFaceInitial"] = el.textContent ?? "";
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
732
|
function applyCurrentTime() {
|
|
2605
|
-
for (const anim of allAnims)
|
|
2606
|
-
anim.currentTime = sceneMs;
|
|
2607
|
-
}
|
|
2608
|
-
applyFaceSwaps();
|
|
733
|
+
for (const anim of allAnims) anim.currentTime = sceneMs;
|
|
2609
734
|
onTimeUpdate?.(sceneMs / 1e3, durationSeconds);
|
|
2610
735
|
}
|
|
2611
|
-
function applyFaceSwaps() {
|
|
2612
|
-
if (faceSwaps.length === 0) return;
|
|
2613
|
-
const elEmoji = /* @__PURE__ */ new Map();
|
|
2614
|
-
for (const { timeMs, el, emoji } of faceSwaps) {
|
|
2615
|
-
if (timeMs <= sceneMs) elEmoji.set(el, emoji);
|
|
2616
|
-
}
|
|
2617
|
-
for (const [el, emoji] of elEmoji) {
|
|
2618
|
-
if (el.textContent !== emoji) el.textContent = emoji;
|
|
2619
|
-
}
|
|
2620
|
-
const elFirst = /* @__PURE__ */ new Map();
|
|
2621
|
-
for (const { el, emoji } of faceSwaps) {
|
|
2622
|
-
if (!elFirst.has(el)) elFirst.set(el, emoji);
|
|
2623
|
-
}
|
|
2624
|
-
for (const [el, firstEmoji] of elFirst) {
|
|
2625
|
-
if (!elEmoji.has(el)) {
|
|
2626
|
-
const initial = el.dataset["figFaceInitial"] ?? firstEmoji;
|
|
2627
|
-
if (el.textContent !== initial) el.textContent = initial;
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
736
|
function rafTick(timestamp) {
|
|
2632
|
-
if (lastRafTs !== null)
|
|
2633
|
-
sceneMs += timestamp - lastRafTs;
|
|
2634
|
-
}
|
|
737
|
+
if (lastRafTs !== null) sceneMs += timestamp - lastRafTs;
|
|
2635
738
|
lastRafTs = timestamp;
|
|
2636
739
|
if (totalDurationMs > 0 && sceneMs >= totalDurationMs) {
|
|
2637
|
-
if (loop)
|
|
2638
|
-
|
|
2639
|
-
} else {
|
|
740
|
+
if (loop) sceneMs = sceneMs % totalDurationMs;
|
|
741
|
+
else {
|
|
2640
742
|
sceneMs = totalDurationMs;
|
|
2641
743
|
applyCurrentTime();
|
|
2642
744
|
isPlaying = false;
|
|
@@ -2662,15 +764,12 @@ function createPlayer(opts) {
|
|
|
2662
764
|
if (!isPlaying) return;
|
|
2663
765
|
isPlaying = false;
|
|
2664
766
|
onPlayStateChange?.(false);
|
|
2665
|
-
if (rafId !== null)
|
|
2666
|
-
|
|
2667
|
-
rafId = null;
|
|
2668
|
-
}
|
|
767
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
768
|
+
rafId = null;
|
|
2669
769
|
lastRafTs = null;
|
|
2670
770
|
},
|
|
2671
771
|
seek(seconds) {
|
|
2672
|
-
|
|
2673
|
-
sceneMs = totalDurationMs > 0 ? Math.min(Math.max(nextMs, 0), totalDurationMs) : Math.max(nextMs, 0);
|
|
772
|
+
sceneMs = totalDurationMs > 0 ? Math.min(Math.max(seconds * 1e3, 0), totalDurationMs) : Math.max(seconds * 1e3, 0);
|
|
2674
773
|
applyCurrentTime();
|
|
2675
774
|
if (totalDurationMs > 0) updateProgressBar(sceneMs / totalDurationMs);
|
|
2676
775
|
},
|
|
@@ -2683,13 +782,12 @@ function createPlayer(opts) {
|
|
|
2683
782
|
isPlaying() {
|
|
2684
783
|
return isPlaying;
|
|
2685
784
|
},
|
|
2686
|
-
|
|
2687
|
-
return
|
|
785
|
+
beats() {
|
|
786
|
+
return plan.beats;
|
|
2688
787
|
},
|
|
2689
|
-
|
|
2690
|
-
const
|
|
2691
|
-
if (
|
|
2692
|
-
player.seek(chapter.startTime);
|
|
788
|
+
seekToBeat(name) {
|
|
789
|
+
const beat = plan.beats.find((b) => b.name === name);
|
|
790
|
+
if (beat) player.seek(beat.start);
|
|
2693
791
|
},
|
|
2694
792
|
destroy() {
|
|
2695
793
|
player.pause();
|
|
@@ -2702,12 +800,9 @@ function createPlayer(opts) {
|
|
|
2702
800
|
};
|
|
2703
801
|
viewport.style.cursor = "pointer";
|
|
2704
802
|
viewport.addEventListener("click", () => {
|
|
2705
|
-
if (isPlaying)
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
if (!loop && sceneMs >= totalDurationMs) {
|
|
2709
|
-
sceneMs = 0;
|
|
2710
|
-
}
|
|
803
|
+
if (isPlaying) player.pause();
|
|
804
|
+
else {
|
|
805
|
+
if (!loop && sceneMs >= totalDurationMs) sceneMs = 0;
|
|
2711
806
|
player.play();
|
|
2712
807
|
}
|
|
2713
808
|
});
|