reframe-video 0.1.1 → 0.1.3
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/dist/analyze.js +44 -1
- package/dist/bin.js +743 -88
- package/dist/browserEntry.js +259 -3
- package/dist/cli.js +222 -5
- package/dist/index.js +510 -16
- package/dist/renderer-canvas.js +36 -0
- package/dist/trace-cli.js +677 -0
- package/dist/types/compile.d.ts +15 -1
- package/dist/types/compose.d.ts +12 -2
- package/dist/types/dsl.d.ts +27 -1
- package/dist/types/evaluate.d.ts +9 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/ir.d.ts +67 -1
- package/dist/types/motion.d.ts +54 -0
- package/dist/types/path.d.ts +15 -0
- package/dist/types/presets.d.ts +41 -0
- package/guides/edsl-guide.md +19 -0
- package/package.json +1 -1
- package/preview/src/main.ts +62 -0
- package/preview/src/panel.ts +37 -3
- package/preview/src/store.ts +28 -2
package/dist/index.js
CHANGED
|
@@ -1,10 +1,89 @@
|
|
|
1
1
|
// ../core/src/ir.ts
|
|
2
2
|
var DEFAULT_TO_DURATION = 0.5;
|
|
3
3
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
4
|
+
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
4
5
|
var DEFAULT_FPS = 30;
|
|
5
6
|
|
|
7
|
+
// ../core/src/path.ts
|
|
8
|
+
function locate(segCount, u) {
|
|
9
|
+
if (segCount <= 0) return { i: 0, t: 0 };
|
|
10
|
+
const clamped = Math.max(0, Math.min(1, u));
|
|
11
|
+
const scaled = clamped * segCount;
|
|
12
|
+
let i = Math.floor(scaled);
|
|
13
|
+
if (i >= segCount) i = segCount - 1;
|
|
14
|
+
return { i, t: scaled - i };
|
|
15
|
+
}
|
|
16
|
+
function controls(points, closed, i) {
|
|
17
|
+
const n = points.length;
|
|
18
|
+
const at = (k) => {
|
|
19
|
+
if (closed) return points[(k % n + n) % n];
|
|
20
|
+
return points[Math.max(0, Math.min(n - 1, k))];
|
|
21
|
+
};
|
|
22
|
+
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
23
|
+
}
|
|
24
|
+
function segCountOf(points, closed) {
|
|
25
|
+
const n = points.length;
|
|
26
|
+
if (n < 2) return 0;
|
|
27
|
+
return closed ? n : n - 1;
|
|
28
|
+
}
|
|
29
|
+
function pathPoint(points, closed, u) {
|
|
30
|
+
const n = points.length;
|
|
31
|
+
if (n === 0) return [0, 0];
|
|
32
|
+
if (n === 1) return [points[0][0], points[0][1]];
|
|
33
|
+
const segs = segCountOf(points, closed);
|
|
34
|
+
const { i, t } = locate(segs, u);
|
|
35
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
36
|
+
const t2 = t * t;
|
|
37
|
+
const t3 = t2 * t;
|
|
38
|
+
const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
|
|
39
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
40
|
+
}
|
|
41
|
+
function pathTangentAngle(points, closed, u) {
|
|
42
|
+
const n = points.length;
|
|
43
|
+
if (n < 2) return 0;
|
|
44
|
+
const segs = segCountOf(points, closed);
|
|
45
|
+
const { i, t } = locate(segs, u);
|
|
46
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
47
|
+
const t2 = t * t;
|
|
48
|
+
const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
|
|
49
|
+
const dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
50
|
+
const dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
51
|
+
if (dx === 0 && dy === 0) return 0;
|
|
52
|
+
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
53
|
+
}
|
|
54
|
+
|
|
6
55
|
// ../core/src/compile.ts
|
|
7
56
|
var key = (target, prop) => `${target}.${prop}`;
|
|
57
|
+
function scaleTimeline(tl, k) {
|
|
58
|
+
switch (tl.kind) {
|
|
59
|
+
case "seq":
|
|
60
|
+
case "par":
|
|
61
|
+
return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
62
|
+
case "stagger":
|
|
63
|
+
return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
64
|
+
case "wait":
|
|
65
|
+
return { ...tl, duration: tl.duration * k };
|
|
66
|
+
case "tween":
|
|
67
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
|
|
68
|
+
case "motionPath":
|
|
69
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
|
|
70
|
+
case "to":
|
|
71
|
+
return {
|
|
72
|
+
...tl,
|
|
73
|
+
duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
|
|
74
|
+
...tl.stagger !== void 0 && { stagger: tl.stagger * k }
|
|
75
|
+
};
|
|
76
|
+
case "beat":
|
|
77
|
+
return {
|
|
78
|
+
...tl,
|
|
79
|
+
children: tl.children.map((c) => scaleTimeline(c, k)),
|
|
80
|
+
...tl.gap !== void 0 && { gap: tl.gap * k }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function orderBeats(children) {
|
|
85
|
+
return children.map((c, i) => ({ c, i, key: c.kind === "beat" && c.order !== void 0 ? c.order : i })).sort((a, b) => a.key - b.key || a.i - b.i).map((x) => x.c);
|
|
86
|
+
}
|
|
8
87
|
function compileScene(ir) {
|
|
9
88
|
const nodeById = /* @__PURE__ */ new Map();
|
|
10
89
|
const nodeOrder = [];
|
|
@@ -33,6 +112,7 @@ function compileScene(ir) {
|
|
|
33
112
|
}
|
|
34
113
|
}
|
|
35
114
|
const segments = /* @__PURE__ */ new Map();
|
|
115
|
+
const motionPaths = /* @__PURE__ */ new Map();
|
|
36
116
|
const current = new Map(initialValues);
|
|
37
117
|
const pushSegment = (seg) => {
|
|
38
118
|
const k = key(seg.target, seg.prop);
|
|
@@ -49,6 +129,50 @@ function compileScene(ir) {
|
|
|
49
129
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
50
130
|
};
|
|
51
131
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
132
|
+
const beatTimes = /* @__PURE__ */ new Map();
|
|
133
|
+
const durationOf = (tl, start) => {
|
|
134
|
+
switch (tl.kind) {
|
|
135
|
+
case "seq": {
|
|
136
|
+
let t = start;
|
|
137
|
+
for (const child of orderBeats(tl.children)) t = durationOf(child, t);
|
|
138
|
+
return t;
|
|
139
|
+
}
|
|
140
|
+
case "par": {
|
|
141
|
+
let end = start;
|
|
142
|
+
for (const child of tl.children) end = Math.max(end, durationOf(child, start));
|
|
143
|
+
return end;
|
|
144
|
+
}
|
|
145
|
+
case "stagger": {
|
|
146
|
+
let end = start;
|
|
147
|
+
tl.children.forEach((child, i) => {
|
|
148
|
+
end = Math.max(end, durationOf(child, start + i * tl.interval));
|
|
149
|
+
});
|
|
150
|
+
return end;
|
|
151
|
+
}
|
|
152
|
+
case "wait":
|
|
153
|
+
return start + tl.duration;
|
|
154
|
+
case "tween":
|
|
155
|
+
return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
156
|
+
case "motionPath":
|
|
157
|
+
return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
158
|
+
case "to": {
|
|
159
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
160
|
+
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
161
|
+
const si = tl.stagger ?? 0;
|
|
162
|
+
const targets = nodeOrder.filter(
|
|
163
|
+
(id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
|
|
164
|
+
);
|
|
165
|
+
return start + duration + Math.max(0, targets.length - 1) * si;
|
|
166
|
+
}
|
|
167
|
+
case "beat": {
|
|
168
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
169
|
+
const natural = durationOf(grouping, 0);
|
|
170
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
171
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
172
|
+
return beatStart + k * natural;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
52
176
|
const walk = (tl, start) => {
|
|
53
177
|
const end = walkInner(tl, start);
|
|
54
178
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -58,9 +182,19 @@ function compileScene(ir) {
|
|
|
58
182
|
switch (tl.kind) {
|
|
59
183
|
case "seq": {
|
|
60
184
|
let t = start;
|
|
61
|
-
for (const child of tl.children) t = walk(child, t);
|
|
185
|
+
for (const child of orderBeats(tl.children)) t = walk(child, t);
|
|
62
186
|
return t;
|
|
63
187
|
}
|
|
188
|
+
case "beat": {
|
|
189
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
190
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
191
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
192
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
193
|
+
const end = walk(inner, beatStart);
|
|
194
|
+
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
195
|
+
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
196
|
+
return end;
|
|
197
|
+
}
|
|
64
198
|
case "par": {
|
|
65
199
|
let end = start;
|
|
66
200
|
for (const child of tl.children) end = Math.max(end, walk(child, start));
|
|
@@ -90,6 +224,23 @@ function compileScene(ir) {
|
|
|
90
224
|
}
|
|
91
225
|
return start + duration;
|
|
92
226
|
}
|
|
227
|
+
case "motionPath": {
|
|
228
|
+
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
229
|
+
const points = tl.points;
|
|
230
|
+
const closed = tl.closed ?? false;
|
|
231
|
+
const autoRotate = tl.autoRotate ?? false;
|
|
232
|
+
const rotateOffset = tl.rotateOffset ?? 0;
|
|
233
|
+
let list = motionPaths.get(tl.target);
|
|
234
|
+
if (!list) motionPaths.set(tl.target, list = []);
|
|
235
|
+
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
236
|
+
if (points.length > 0) {
|
|
237
|
+
const [ex, ey] = pathPoint(points, closed, 1);
|
|
238
|
+
current.set(key(tl.target, "x"), ex);
|
|
239
|
+
current.set(key(tl.target, "y"), ey);
|
|
240
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
241
|
+
}
|
|
242
|
+
return start + duration;
|
|
243
|
+
}
|
|
93
244
|
case "to": {
|
|
94
245
|
const override = ir.states?.[tl.state] ?? {};
|
|
95
246
|
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
@@ -118,14 +269,17 @@ function compileScene(ir) {
|
|
|
118
269
|
};
|
|
119
270
|
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
120
271
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
272
|
+
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
121
273
|
return {
|
|
122
274
|
ir,
|
|
123
275
|
duration: ir.duration ?? inferredEnd,
|
|
124
276
|
segments,
|
|
277
|
+
motionPaths,
|
|
125
278
|
initialValues,
|
|
126
279
|
nodeById,
|
|
127
280
|
nodeOrder,
|
|
128
|
-
labelTimes
|
|
281
|
+
labelTimes,
|
|
282
|
+
beatTimes
|
|
129
283
|
};
|
|
130
284
|
}
|
|
131
285
|
|
|
@@ -137,6 +291,7 @@ var PROPS_BY_TYPE = {
|
|
|
137
291
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
138
292
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
139
293
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
294
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
140
295
|
group: COMMON_PROPS
|
|
141
296
|
};
|
|
142
297
|
var SceneValidationError = class extends Error {
|
|
@@ -190,11 +345,11 @@ function validateScene(ir) {
|
|
|
190
345
|
);
|
|
191
346
|
}
|
|
192
347
|
const labels = /* @__PURE__ */ new Set();
|
|
193
|
-
const checkTimeline = (tl,
|
|
348
|
+
const checkTimeline = (tl, path2) => {
|
|
194
349
|
if ("label" in tl && tl.label !== void 0) {
|
|
195
350
|
if (labels.has(tl.label)) {
|
|
196
351
|
problems.push(
|
|
197
|
-
`${
|
|
352
|
+
`${path2}: duplicate timeline label "${tl.label}" \u2014 labels are overlay addresses and must be unique`
|
|
198
353
|
);
|
|
199
354
|
}
|
|
200
355
|
labels.add(tl.label);
|
|
@@ -202,33 +357,63 @@ function validateScene(ir) {
|
|
|
202
357
|
switch (tl.kind) {
|
|
203
358
|
case "seq":
|
|
204
359
|
case "par":
|
|
205
|
-
tl.children.forEach((c, i) => checkTimeline(c, `${
|
|
360
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.${tl.kind}[${i}]`));
|
|
206
361
|
break;
|
|
207
362
|
case "stagger":
|
|
208
|
-
if (tl.interval < 0) problems.push(`${
|
|
209
|
-
tl.children.forEach((c, i) => checkTimeline(c, `${
|
|
363
|
+
if (tl.interval < 0) problems.push(`${path2}: stagger interval must be >= 0`);
|
|
364
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.stagger[${i}]`));
|
|
210
365
|
break;
|
|
211
366
|
case "to":
|
|
212
367
|
if (!(tl.state in states)) {
|
|
213
368
|
problems.push(
|
|
214
|
-
`${
|
|
369
|
+
`${path2}: to("${tl.state}") references an undefined state \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
|
|
215
370
|
);
|
|
216
371
|
}
|
|
217
372
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
218
|
-
problems.push(`${
|
|
373
|
+
problems.push(`${path2}: to("${tl.state}") duration must be > 0`);
|
|
219
374
|
}
|
|
220
375
|
for (const id of tl.filter ?? []) {
|
|
221
|
-
if (!nodeById.has(id)) problems.push(`${
|
|
376
|
+
if (!nodeById.has(id)) problems.push(`${path2}: filter contains unknown node "${id}"`);
|
|
222
377
|
}
|
|
223
378
|
break;
|
|
224
379
|
case "tween":
|
|
225
|
-
checkProps(
|
|
380
|
+
checkProps(path2, tl.target, tl.props);
|
|
226
381
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
227
|
-
problems.push(`${
|
|
382
|
+
problems.push(`${path2}: tween duration must be > 0`);
|
|
228
383
|
}
|
|
229
384
|
break;
|
|
385
|
+
case "motionPath": {
|
|
386
|
+
const node = nodeById.get(tl.target);
|
|
387
|
+
if (!node) {
|
|
388
|
+
problems.push(
|
|
389
|
+
`${path2}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
390
|
+
);
|
|
391
|
+
} else if (node.type === "line") {
|
|
392
|
+
problems.push(`${path2}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
|
|
393
|
+
}
|
|
394
|
+
if (tl.points.length < 1) problems.push(`${path2}: motionPath "${tl.target}" needs at least 1 point`);
|
|
395
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
396
|
+
problems.push(`${path2}: motionPath "${tl.target}" duration must be > 0`);
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
230
400
|
case "wait":
|
|
231
|
-
if (tl.duration < 0) problems.push(`${
|
|
401
|
+
if (tl.duration < 0) problems.push(`${path2}: wait duration must be >= 0`);
|
|
402
|
+
break;
|
|
403
|
+
case "beat":
|
|
404
|
+
if (labels.has(tl.name)) {
|
|
405
|
+
problems.push(
|
|
406
|
+
`${path2}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
labels.add(tl.name);
|
|
410
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
411
|
+
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
412
|
+
}
|
|
413
|
+
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
414
|
+
problems.push(`${path2}: beat "${tl.name}" scale must be > 0`);
|
|
415
|
+
}
|
|
416
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.beat(${tl.name})[${i}]`));
|
|
232
417
|
break;
|
|
233
418
|
}
|
|
234
419
|
};
|
|
@@ -298,6 +483,10 @@ function image(props) {
|
|
|
298
483
|
const { id, ...rest } = props;
|
|
299
484
|
return { type: "image", id, props: rest };
|
|
300
485
|
}
|
|
486
|
+
function path(props) {
|
|
487
|
+
const { id, ...rest } = props;
|
|
488
|
+
return { type: "path", id, props: rest };
|
|
489
|
+
}
|
|
301
490
|
function group(props, children) {
|
|
302
491
|
const { id, ...rest } = props;
|
|
303
492
|
return { type: "group", id, props: rest, children };
|
|
@@ -311,6 +500,9 @@ function par(...children) {
|
|
|
311
500
|
function stagger(interval, ...children) {
|
|
312
501
|
return { kind: "stagger", interval, children };
|
|
313
502
|
}
|
|
503
|
+
function beat(name, opts, children) {
|
|
504
|
+
return { kind: "beat", name, children, ...opts };
|
|
505
|
+
}
|
|
314
506
|
function to(state, opts = {}) {
|
|
315
507
|
return { kind: "to", state, ...opts };
|
|
316
508
|
}
|
|
@@ -320,6 +512,9 @@ function tween(target, props, opts = {}) {
|
|
|
320
512
|
function wait(duration, label) {
|
|
321
513
|
return { kind: "wait", duration, ...label !== void 0 && { label } };
|
|
322
514
|
}
|
|
515
|
+
function motionPath(target, points, opts = {}) {
|
|
516
|
+
return { kind: "motionPath", target, points, ...opts };
|
|
517
|
+
}
|
|
323
518
|
function oscillate(target, prop, params, window = {}) {
|
|
324
519
|
return { target, prop, ...window, behavior: { kind: "named", name: "oscillate", params } };
|
|
325
520
|
}
|
|
@@ -451,13 +646,16 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
451
646
|
const byLabel = /* @__PURE__ */ new Map();
|
|
452
647
|
const walkTimeline = (tl) => {
|
|
453
648
|
if ("label" in tl && tl.label !== void 0) byLabel.set(tl.label, tl);
|
|
649
|
+
if (tl.kind === "beat") byLabel.set(tl.name, tl);
|
|
454
650
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
455
651
|
};
|
|
456
652
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
457
653
|
const PATCHABLE = {
|
|
458
654
|
to: ["duration", "ease", "stagger"],
|
|
459
655
|
tween: ["duration", "ease"],
|
|
460
|
-
wait: ["duration"]
|
|
656
|
+
wait: ["duration"],
|
|
657
|
+
motionPath: ["points", "duration", "ease"],
|
|
658
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
461
659
|
};
|
|
462
660
|
let timingPatched = false;
|
|
463
661
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
@@ -481,7 +679,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
481
679
|
}
|
|
482
680
|
step[key2] = value;
|
|
483
681
|
applied(`timeline.${label}.${key2}`, "set");
|
|
484
|
-
if (
|
|
682
|
+
if (["duration", "stagger", "at", "gap", "scale", "order"].includes(key2)) timingPatched = true;
|
|
485
683
|
}
|
|
486
684
|
}
|
|
487
685
|
if (timingPatched && overlay.scene?.duration === void 0) {
|
|
@@ -506,6 +704,186 @@ function formatComposeReport(report) {
|
|
|
506
704
|
return lines.join("\n");
|
|
507
705
|
}
|
|
508
706
|
|
|
707
|
+
// ../core/src/presets.ts
|
|
708
|
+
var PRESET_NAMES = [
|
|
709
|
+
"draw-bloom",
|
|
710
|
+
"punch-in",
|
|
711
|
+
"rise-settle",
|
|
712
|
+
"slide-bank",
|
|
713
|
+
"reveal-orbit",
|
|
714
|
+
"spin-forge"
|
|
715
|
+
];
|
|
716
|
+
function makeRng(seed) {
|
|
717
|
+
let a = seed >>> 0 || 2654435769;
|
|
718
|
+
return () => {
|
|
719
|
+
a = a + 1831565813 | 0;
|
|
720
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
721
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
722
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
var clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
726
|
+
var SET = 1 / 120;
|
|
727
|
+
function ctx(o) {
|
|
728
|
+
const rand = makeRng((o.seed ?? 0) + 1);
|
|
729
|
+
return {
|
|
730
|
+
e: clamp01(o.energy ?? 0.5),
|
|
731
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
732
|
+
it: clamp01(o.intensity ?? 0.5),
|
|
733
|
+
from: o.from,
|
|
734
|
+
rand,
|
|
735
|
+
jit: (amp) => (rand() - 0.5) * 2 * amp,
|
|
736
|
+
g: o.target.group,
|
|
737
|
+
cx: o.target.center[0],
|
|
738
|
+
cy: o.target.center[1],
|
|
739
|
+
s: o.target.baseScale,
|
|
740
|
+
fills: o.target.fills,
|
|
741
|
+
inks: o.target.inks
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
var dur = (base, sp) => base / sp;
|
|
745
|
+
function settleEase(e) {
|
|
746
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
747
|
+
}
|
|
748
|
+
function fromVec(from, dist) {
|
|
749
|
+
switch (from) {
|
|
750
|
+
case "left":
|
|
751
|
+
return [-dist, 0];
|
|
752
|
+
case "right":
|
|
753
|
+
return [dist, 0];
|
|
754
|
+
case "top":
|
|
755
|
+
return [0, -dist];
|
|
756
|
+
default:
|
|
757
|
+
return [0, dist];
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
761
|
+
return stagger(
|
|
762
|
+
gap / c.sp,
|
|
763
|
+
...c.fills.map(
|
|
764
|
+
(id, i) => tween(id, { opacity: 1 }, { duration: dur(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
765
|
+
)
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
function drawInks(c) {
|
|
769
|
+
return stagger(
|
|
770
|
+
0.15 / c.sp,
|
|
771
|
+
...c.inks.map(
|
|
772
|
+
(id, i) => tween(id, { progress: 1 }, { duration: dur(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
773
|
+
)
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
function motionPreset(name, opts) {
|
|
777
|
+
const c = ctx(opts);
|
|
778
|
+
switch (name) {
|
|
779
|
+
case "draw-bloom":
|
|
780
|
+
return beat("draw-bloom", {}, [
|
|
781
|
+
drawInks(c),
|
|
782
|
+
fadeFills(c, 0.45),
|
|
783
|
+
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
784
|
+
]);
|
|
785
|
+
case "punch-in": {
|
|
786
|
+
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
787
|
+
return beat("punch-in", {}, [
|
|
788
|
+
par(
|
|
789
|
+
fadeFills(c, 0.25),
|
|
790
|
+
seq(
|
|
791
|
+
tween(c.g, { scale: peak }, { duration: dur(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
792
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.5, c.sp), ease: settleEase(c.e) })
|
|
793
|
+
)
|
|
794
|
+
)
|
|
795
|
+
]);
|
|
796
|
+
}
|
|
797
|
+
case "rise-settle": {
|
|
798
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
799
|
+
const dist = (220 + 260 * c.it) * es;
|
|
800
|
+
const [dx, dy] = fromVec(c.from ?? "bottom", dist);
|
|
801
|
+
const jx = c.jit(110);
|
|
802
|
+
return beat("rise-settle", {}, [
|
|
803
|
+
par(
|
|
804
|
+
motionPath(
|
|
805
|
+
c.g,
|
|
806
|
+
[
|
|
807
|
+
[c.cx + dx + jx, c.cy + dy],
|
|
808
|
+
[c.cx + dx * 0.4 - jx * 0.6, c.cy + dy * 0.4],
|
|
809
|
+
[c.cx, c.cy]
|
|
810
|
+
],
|
|
811
|
+
{ duration: dur(1.1, c.sp), ease: settleEase(c.e), label: "rise" }
|
|
812
|
+
),
|
|
813
|
+
fadeFills(c, 0.4)
|
|
814
|
+
)
|
|
815
|
+
]);
|
|
816
|
+
}
|
|
817
|
+
case "slide-bank": {
|
|
818
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
819
|
+
const dist = (420 + 240 * c.it) * es;
|
|
820
|
+
const [dx, dy] = fromVec(c.from ?? "left", dist);
|
|
821
|
+
const arc = c.jit(140);
|
|
822
|
+
const midx = c.jit(120);
|
|
823
|
+
const move = dur(1.2, c.sp);
|
|
824
|
+
return beat("slide-bank", {}, [
|
|
825
|
+
par(
|
|
826
|
+
motionPath(
|
|
827
|
+
c.g,
|
|
828
|
+
[
|
|
829
|
+
[c.cx + dx, c.cy + dy],
|
|
830
|
+
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
831
|
+
[c.cx, c.cy]
|
|
832
|
+
],
|
|
833
|
+
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
834
|
+
),
|
|
835
|
+
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
836
|
+
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur(0.5, c.sp), ease: "easeOutCubic" })),
|
|
837
|
+
fadeFills(c, 0.4)
|
|
838
|
+
)
|
|
839
|
+
]);
|
|
840
|
+
}
|
|
841
|
+
case "reveal-orbit": {
|
|
842
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
843
|
+
const orbit = (180 + 160 * c.it) * es;
|
|
844
|
+
const jx = c.jit(0.4);
|
|
845
|
+
const jy = c.jit(0.4);
|
|
846
|
+
return beat("reveal-orbit", {}, [
|
|
847
|
+
drawInks(c),
|
|
848
|
+
fadeFills(c, 0.45),
|
|
849
|
+
par(
|
|
850
|
+
motionPath(
|
|
851
|
+
c.g,
|
|
852
|
+
[
|
|
853
|
+
[c.cx, c.cy],
|
|
854
|
+
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
855
|
+
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
856
|
+
[c.cx, c.cy]
|
|
857
|
+
],
|
|
858
|
+
{ duration: dur(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
859
|
+
),
|
|
860
|
+
seq(
|
|
861
|
+
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur(0.85, c.sp), ease: "easeOutBack" }),
|
|
862
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.85, c.sp), ease: "easeInOutQuad" })
|
|
863
|
+
)
|
|
864
|
+
)
|
|
865
|
+
]);
|
|
866
|
+
}
|
|
867
|
+
case "spin-forge": {
|
|
868
|
+
const turns = 1 + Math.round(c.it);
|
|
869
|
+
const dir = c.rand() < 0.5 ? -1 : 1;
|
|
870
|
+
const startRot = dir * 360 * turns;
|
|
871
|
+
const peak = c.s * (1 + 0.05 + 0.2 * c.e);
|
|
872
|
+
return beat("spin-forge", {}, [
|
|
873
|
+
par(
|
|
874
|
+
seq(
|
|
875
|
+
tween(c.g, { scale: c.s * 0.2, rotation: startRot }, { duration: SET }),
|
|
876
|
+
// establish (invisible)
|
|
877
|
+
tween(c.g, { scale: peak, rotation: 0 }, { duration: dur(0.9, c.sp), ease: "easeOutBack", label: "spin" }),
|
|
878
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.3, c.sp), ease: "easeInOutQuad" })
|
|
879
|
+
),
|
|
880
|
+
seq(wait(SET), fadeFills(c, 0.3))
|
|
881
|
+
)
|
|
882
|
+
]);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
509
887
|
// ../core/src/audio.ts
|
|
510
888
|
var SFX_DURATION = {
|
|
511
889
|
whoosh: 0.35,
|
|
@@ -607,6 +985,19 @@ function hash01(n, seed) {
|
|
|
607
985
|
}
|
|
608
986
|
|
|
609
987
|
// ../core/src/interpolate.ts
|
|
988
|
+
var BACK_C1 = 1.70158;
|
|
989
|
+
var BACK_C2 = BACK_C1 * 1.525;
|
|
990
|
+
var BACK_C3 = BACK_C1 + 1;
|
|
991
|
+
var ELASTIC_C4 = 2 * Math.PI / 3;
|
|
992
|
+
var ELASTIC_C5 = 2 * Math.PI / 4.5;
|
|
993
|
+
function easeOutBounce(u) {
|
|
994
|
+
const n1 = 7.5625;
|
|
995
|
+
const d1 = 2.75;
|
|
996
|
+
if (u < 1 / d1) return n1 * u * u;
|
|
997
|
+
if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
|
|
998
|
+
if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
|
|
999
|
+
return n1 * (u -= 2.625 / d1) * u + 0.984375;
|
|
1000
|
+
}
|
|
610
1001
|
var EASE_TABLE = {
|
|
611
1002
|
linear: (u) => u,
|
|
612
1003
|
easeInQuad: (u) => u * u,
|
|
@@ -620,7 +1011,20 @@ var EASE_TABLE = {
|
|
|
620
1011
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
621
1012
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
622
1013
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
623
|
-
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
|
|
1014
|
+
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
|
|
1015
|
+
// --- expressive eases (GSAP's signature feel) — standard Penner equations ---
|
|
1016
|
+
// back: overshoots past the target then settles (pop / snap)
|
|
1017
|
+
easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
|
|
1018
|
+
easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
|
|
1019
|
+
easeInOutBack: (u) => u < 0.5 ? (2 * u) ** 2 * ((BACK_C2 + 1) * 2 * u - BACK_C2) / 2 : ((2 * u - 2) ** 2 * ((BACK_C2 + 1) * (2 * u - 2) + BACK_C2) + 2) / 2,
|
|
1020
|
+
// elastic: rings around the target before settling (playful spring)
|
|
1021
|
+
easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
|
|
1022
|
+
easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
|
|
1023
|
+
easeInOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? -(2 ** (20 * u - 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5)) / 2 : 2 ** (-20 * u + 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5) / 2 + 1,
|
|
1024
|
+
// bounce: drops and bounces to rest (lands without overshoot)
|
|
1025
|
+
easeInBounce: (u) => 1 - easeOutBounce(1 - u),
|
|
1026
|
+
easeOutBounce,
|
|
1027
|
+
easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
|
|
624
1028
|
};
|
|
625
1029
|
var EASE_NAMES = Object.keys(EASE_TABLE);
|
|
626
1030
|
function resolveEase(ease) {
|
|
@@ -735,6 +1139,7 @@ function evaluate(compiled, t) {
|
|
|
735
1139
|
const ops = [];
|
|
736
1140
|
const valueAt = (target, prop, fallback) => {
|
|
737
1141
|
let value = compiled.initialValues.get(`${target}.${prop}`) ?? fallback;
|
|
1142
|
+
let segStart = Number.NEGATIVE_INFINITY;
|
|
738
1143
|
const segs = compiled.segments.get(`${target}.${prop}`);
|
|
739
1144
|
if (segs) {
|
|
740
1145
|
let active;
|
|
@@ -743,6 +1148,7 @@ function evaluate(compiled, t) {
|
|
|
743
1148
|
else break;
|
|
744
1149
|
}
|
|
745
1150
|
if (active) {
|
|
1151
|
+
segStart = active.t0;
|
|
746
1152
|
if (t >= active.t1) {
|
|
747
1153
|
value = active.to;
|
|
748
1154
|
} else {
|
|
@@ -751,6 +1157,23 @@ function evaluate(compiled, t) {
|
|
|
751
1157
|
}
|
|
752
1158
|
}
|
|
753
1159
|
}
|
|
1160
|
+
if (prop === "x" || prop === "y" || prop === "rotation") {
|
|
1161
|
+
const drivers = compiled.motionPaths.get(target);
|
|
1162
|
+
if (drivers) {
|
|
1163
|
+
let active;
|
|
1164
|
+
for (const d of drivers) {
|
|
1165
|
+
if (d.t0 <= t) active = d;
|
|
1166
|
+
else break;
|
|
1167
|
+
}
|
|
1168
|
+
if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
|
|
1169
|
+
const span = active.t1 - active.t0;
|
|
1170
|
+
const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
|
|
1171
|
+
if (prop === "x") value = pathPoint(active.points, active.closed, u)[0];
|
|
1172
|
+
else if (prop === "y") value = pathPoint(active.points, active.closed, u)[1];
|
|
1173
|
+
else value = pathTangentAngle(active.points, active.closed, u) + active.rotateOffset;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
754
1177
|
for (const b of compiled.ir.behaviors ?? []) {
|
|
755
1178
|
if (b.target === target && b.prop === prop && typeof value === "number") {
|
|
756
1179
|
const envelope = behaviorEnvelope(b, t);
|
|
@@ -848,6 +1271,23 @@ function evaluate(compiled, t) {
|
|
|
848
1271
|
});
|
|
849
1272
|
return;
|
|
850
1273
|
}
|
|
1274
|
+
case "path": {
|
|
1275
|
+
const ox = num(id, "originX", node.props.originX ?? 0);
|
|
1276
|
+
const oy = num(id, "originY", node.props.originY ?? 0);
|
|
1277
|
+
const fill = opt(id, "fill", node.props.fill);
|
|
1278
|
+
const stroke = opt(id, "stroke", node.props.stroke);
|
|
1279
|
+
ops.push({
|
|
1280
|
+
type: "path",
|
|
1281
|
+
id,
|
|
1282
|
+
transform: ox === 0 && oy === 0 ? matrix : multiply(matrix, [1, 0, 0, 1, -ox, -oy]),
|
|
1283
|
+
opacity,
|
|
1284
|
+
d: str(id, "d", node.props.d),
|
|
1285
|
+
progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
|
|
1286
|
+
...fill !== void 0 && { fill },
|
|
1287
|
+
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) }
|
|
1288
|
+
});
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
851
1291
|
case "text": {
|
|
852
1292
|
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
853
1293
|
const raw = valueAt(id, "content", node.props.content);
|
|
@@ -908,14 +1348,62 @@ function collectImageSrcs(ir) {
|
|
|
908
1348
|
walkTimeline(ir.timeline);
|
|
909
1349
|
return [...srcs];
|
|
910
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
// ../core/src/motion.ts
|
|
1353
|
+
var EASE_BY_CLASS = {
|
|
1354
|
+
accelerating: "easeInCubic",
|
|
1355
|
+
decelerating: "easeOutCubic",
|
|
1356
|
+
linear: "linear"
|
|
1357
|
+
};
|
|
1358
|
+
function easeFor(easing) {
|
|
1359
|
+
return EASE_BY_CLASS[easing.class] ?? "easeOutCubic";
|
|
1360
|
+
}
|
|
1361
|
+
function sketchToTimeline(sketch, nodeIds) {
|
|
1362
|
+
if (nodeIds.length === 0) return seq();
|
|
1363
|
+
const events = [...sketch.events].sort((a, b) => a.t0 - b.t0);
|
|
1364
|
+
const steps = [];
|
|
1365
|
+
events.forEach((ev, i) => {
|
|
1366
|
+
const node = nodeIds[i % nodeIds.length];
|
|
1367
|
+
const dur2 = Math.max(0.05, ev.t1 - ev.t0);
|
|
1368
|
+
const ease = easeFor(ev.easing);
|
|
1369
|
+
let motion;
|
|
1370
|
+
switch (ev.kind) {
|
|
1371
|
+
case "enter":
|
|
1372
|
+
motion = tween(node, { opacity: 1 }, { duration: dur2, ease });
|
|
1373
|
+
break;
|
|
1374
|
+
case "exit":
|
|
1375
|
+
motion = tween(node, { opacity: 0 }, { duration: dur2, ease });
|
|
1376
|
+
break;
|
|
1377
|
+
case "emphasis": {
|
|
1378
|
+
const peak = 1 + Math.max(0.08, Math.min(0.5, ev.magnitude));
|
|
1379
|
+
motion = seq(
|
|
1380
|
+
tween(node, { scale: peak }, { duration: dur2 / 2, ease: "easeOutCubic" }),
|
|
1381
|
+
tween(node, { scale: 1 }, { duration: dur2 / 2, ease: "easeInOutQuad" })
|
|
1382
|
+
);
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
case "scale":
|
|
1386
|
+
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration: dur2, ease });
|
|
1387
|
+
break;
|
|
1388
|
+
case "move":
|
|
1389
|
+
motion = tween(node, { opacity: 1 }, { duration: dur2, ease });
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
steps.push(ev.t0 > 0 ? seq(wait(ev.t0), motion) : motion);
|
|
1393
|
+
});
|
|
1394
|
+
return par(...steps);
|
|
1395
|
+
}
|
|
911
1396
|
export {
|
|
912
1397
|
DEFAULT_FPS,
|
|
1398
|
+
DEFAULT_MOTIONPATH_DURATION,
|
|
913
1399
|
DEFAULT_TO_DURATION,
|
|
914
1400
|
DEFAULT_TWEEN_DURATION,
|
|
915
1401
|
EASE_NAMES,
|
|
1402
|
+
PRESET_NAMES,
|
|
916
1403
|
PROPS_BY_TYPE,
|
|
917
1404
|
SFX_DURATION,
|
|
918
1405
|
SceneValidationError,
|
|
1406
|
+
beat,
|
|
919
1407
|
collectImageSrcs,
|
|
920
1408
|
compileScene,
|
|
921
1409
|
composeScene,
|
|
@@ -927,14 +1415,20 @@ export {
|
|
|
927
1415
|
isColor,
|
|
928
1416
|
lerpValue,
|
|
929
1417
|
line,
|
|
1418
|
+
motionPath,
|
|
1419
|
+
motionPreset,
|
|
930
1420
|
oscillate,
|
|
931
1421
|
par,
|
|
1422
|
+
path,
|
|
1423
|
+
pathPoint,
|
|
1424
|
+
pathTangentAngle,
|
|
932
1425
|
rect,
|
|
933
1426
|
resolveAudioPlan,
|
|
934
1427
|
resolveEase,
|
|
935
1428
|
sampleBehavior,
|
|
936
1429
|
scene,
|
|
937
1430
|
seq,
|
|
1431
|
+
sketchToTimeline,
|
|
938
1432
|
stagger,
|
|
939
1433
|
text,
|
|
940
1434
|
to,
|