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/bin.js
CHANGED
|
@@ -10,16 +10,100 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// ../core/src/ir.ts
|
|
13
|
-
var DEFAULT_TO_DURATION, DEFAULT_TWEEN_DURATION;
|
|
13
|
+
var DEFAULT_TO_DURATION, DEFAULT_TWEEN_DURATION, DEFAULT_MOTIONPATH_DURATION;
|
|
14
14
|
var init_ir = __esm({
|
|
15
15
|
"../core/src/ir.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
DEFAULT_TO_DURATION = 0.5;
|
|
18
18
|
DEFAULT_TWEEN_DURATION = 0.5;
|
|
19
|
+
DEFAULT_MOTIONPATH_DURATION = 1;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ../core/src/path.ts
|
|
24
|
+
function locate(segCount, u) {
|
|
25
|
+
if (segCount <= 0) return { i: 0, t: 0 };
|
|
26
|
+
const clamped = Math.max(0, Math.min(1, u));
|
|
27
|
+
const scaled = clamped * segCount;
|
|
28
|
+
let i = Math.floor(scaled);
|
|
29
|
+
if (i >= segCount) i = segCount - 1;
|
|
30
|
+
return { i, t: scaled - i };
|
|
31
|
+
}
|
|
32
|
+
function controls(points, closed, i) {
|
|
33
|
+
const n = points.length;
|
|
34
|
+
const at = (k) => {
|
|
35
|
+
if (closed) return points[(k % n + n) % n];
|
|
36
|
+
return points[Math.max(0, Math.min(n - 1, k))];
|
|
37
|
+
};
|
|
38
|
+
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
39
|
+
}
|
|
40
|
+
function segCountOf(points, closed) {
|
|
41
|
+
const n = points.length;
|
|
42
|
+
if (n < 2) return 0;
|
|
43
|
+
return closed ? n : n - 1;
|
|
44
|
+
}
|
|
45
|
+
function pathPoint(points, closed, u) {
|
|
46
|
+
const n = points.length;
|
|
47
|
+
if (n === 0) return [0, 0];
|
|
48
|
+
if (n === 1) return [points[0][0], points[0][1]];
|
|
49
|
+
const segs = segCountOf(points, closed);
|
|
50
|
+
const { i, t } = locate(segs, u);
|
|
51
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
52
|
+
const t2 = t * t;
|
|
53
|
+
const t3 = t2 * t;
|
|
54
|
+
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);
|
|
55
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
56
|
+
}
|
|
57
|
+
function pathTangentAngle(points, closed, u) {
|
|
58
|
+
const n = points.length;
|
|
59
|
+
if (n < 2) return 0;
|
|
60
|
+
const segs = segCountOf(points, closed);
|
|
61
|
+
const { i, t } = locate(segs, u);
|
|
62
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
63
|
+
const t2 = t * t;
|
|
64
|
+
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);
|
|
65
|
+
const dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
66
|
+
const dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
67
|
+
if (dx === 0 && dy === 0) return 0;
|
|
68
|
+
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
69
|
+
}
|
|
70
|
+
var init_path = __esm({
|
|
71
|
+
"../core/src/path.ts"() {
|
|
72
|
+
"use strict";
|
|
19
73
|
}
|
|
20
74
|
});
|
|
21
75
|
|
|
22
76
|
// ../core/src/compile.ts
|
|
77
|
+
function scaleTimeline(tl, k) {
|
|
78
|
+
switch (tl.kind) {
|
|
79
|
+
case "seq":
|
|
80
|
+
case "par":
|
|
81
|
+
return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
82
|
+
case "stagger":
|
|
83
|
+
return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
84
|
+
case "wait":
|
|
85
|
+
return { ...tl, duration: tl.duration * k };
|
|
86
|
+
case "tween":
|
|
87
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
|
|
88
|
+
case "motionPath":
|
|
89
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
|
|
90
|
+
case "to":
|
|
91
|
+
return {
|
|
92
|
+
...tl,
|
|
93
|
+
duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
|
|
94
|
+
...tl.stagger !== void 0 && { stagger: tl.stagger * k }
|
|
95
|
+
};
|
|
96
|
+
case "beat":
|
|
97
|
+
return {
|
|
98
|
+
...tl,
|
|
99
|
+
children: tl.children.map((c) => scaleTimeline(c, k)),
|
|
100
|
+
...tl.gap !== void 0 && { gap: tl.gap * k }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function orderBeats(children) {
|
|
105
|
+
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);
|
|
106
|
+
}
|
|
23
107
|
function compileScene(ir) {
|
|
24
108
|
const nodeById = /* @__PURE__ */ new Map();
|
|
25
109
|
const nodeOrder = [];
|
|
@@ -48,6 +132,7 @@ function compileScene(ir) {
|
|
|
48
132
|
}
|
|
49
133
|
}
|
|
50
134
|
const segments = /* @__PURE__ */ new Map();
|
|
135
|
+
const motionPaths = /* @__PURE__ */ new Map();
|
|
51
136
|
const current = new Map(initialValues);
|
|
52
137
|
const pushSegment = (seg) => {
|
|
53
138
|
const k = key(seg.target, seg.prop);
|
|
@@ -64,6 +149,50 @@ function compileScene(ir) {
|
|
|
64
149
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
65
150
|
};
|
|
66
151
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
152
|
+
const beatTimes = /* @__PURE__ */ new Map();
|
|
153
|
+
const durationOf = (tl, start) => {
|
|
154
|
+
switch (tl.kind) {
|
|
155
|
+
case "seq": {
|
|
156
|
+
let t = start;
|
|
157
|
+
for (const child of orderBeats(tl.children)) t = durationOf(child, t);
|
|
158
|
+
return t;
|
|
159
|
+
}
|
|
160
|
+
case "par": {
|
|
161
|
+
let end = start;
|
|
162
|
+
for (const child of tl.children) end = Math.max(end, durationOf(child, start));
|
|
163
|
+
return end;
|
|
164
|
+
}
|
|
165
|
+
case "stagger": {
|
|
166
|
+
let end = start;
|
|
167
|
+
tl.children.forEach((child, i) => {
|
|
168
|
+
end = Math.max(end, durationOf(child, start + i * tl.interval));
|
|
169
|
+
});
|
|
170
|
+
return end;
|
|
171
|
+
}
|
|
172
|
+
case "wait":
|
|
173
|
+
return start + tl.duration;
|
|
174
|
+
case "tween":
|
|
175
|
+
return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
176
|
+
case "motionPath":
|
|
177
|
+
return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
178
|
+
case "to": {
|
|
179
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
180
|
+
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
181
|
+
const si = tl.stagger ?? 0;
|
|
182
|
+
const targets = nodeOrder.filter(
|
|
183
|
+
(id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
|
|
184
|
+
);
|
|
185
|
+
return start + duration + Math.max(0, targets.length - 1) * si;
|
|
186
|
+
}
|
|
187
|
+
case "beat": {
|
|
188
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
189
|
+
const natural = durationOf(grouping, 0);
|
|
190
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
191
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
192
|
+
return beatStart + k * natural;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
67
196
|
const walk = (tl, start) => {
|
|
68
197
|
const end = walkInner(tl, start);
|
|
69
198
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -73,9 +202,19 @@ function compileScene(ir) {
|
|
|
73
202
|
switch (tl.kind) {
|
|
74
203
|
case "seq": {
|
|
75
204
|
let t = start;
|
|
76
|
-
for (const child of tl.children) t = walk(child, t);
|
|
205
|
+
for (const child of orderBeats(tl.children)) t = walk(child, t);
|
|
77
206
|
return t;
|
|
78
207
|
}
|
|
208
|
+
case "beat": {
|
|
209
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
210
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
211
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
212
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
213
|
+
const end = walk(inner, beatStart);
|
|
214
|
+
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
215
|
+
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
216
|
+
return end;
|
|
217
|
+
}
|
|
79
218
|
case "par": {
|
|
80
219
|
let end = start;
|
|
81
220
|
for (const child of tl.children) end = Math.max(end, walk(child, start));
|
|
@@ -105,6 +244,23 @@ function compileScene(ir) {
|
|
|
105
244
|
}
|
|
106
245
|
return start + duration;
|
|
107
246
|
}
|
|
247
|
+
case "motionPath": {
|
|
248
|
+
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
249
|
+
const points = tl.points;
|
|
250
|
+
const closed = tl.closed ?? false;
|
|
251
|
+
const autoRotate = tl.autoRotate ?? false;
|
|
252
|
+
const rotateOffset = tl.rotateOffset ?? 0;
|
|
253
|
+
let list = motionPaths.get(tl.target);
|
|
254
|
+
if (!list) motionPaths.set(tl.target, list = []);
|
|
255
|
+
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
256
|
+
if (points.length > 0) {
|
|
257
|
+
const [ex, ey] = pathPoint(points, closed, 1);
|
|
258
|
+
current.set(key(tl.target, "x"), ex);
|
|
259
|
+
current.set(key(tl.target, "y"), ey);
|
|
260
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
261
|
+
}
|
|
262
|
+
return start + duration;
|
|
263
|
+
}
|
|
108
264
|
case "to": {
|
|
109
265
|
const override = ir.states?.[tl.state] ?? {};
|
|
110
266
|
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
@@ -133,14 +289,17 @@ function compileScene(ir) {
|
|
|
133
289
|
};
|
|
134
290
|
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
135
291
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
292
|
+
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
136
293
|
return {
|
|
137
294
|
ir,
|
|
138
295
|
duration: ir.duration ?? inferredEnd,
|
|
139
296
|
segments,
|
|
297
|
+
motionPaths,
|
|
140
298
|
initialValues,
|
|
141
299
|
nodeById,
|
|
142
300
|
nodeOrder,
|
|
143
|
-
labelTimes
|
|
301
|
+
labelTimes,
|
|
302
|
+
beatTimes
|
|
144
303
|
};
|
|
145
304
|
}
|
|
146
305
|
var key;
|
|
@@ -148,6 +307,7 @@ var init_compile = __esm({
|
|
|
148
307
|
"../core/src/compile.ts"() {
|
|
149
308
|
"use strict";
|
|
150
309
|
init_ir();
|
|
310
|
+
init_path();
|
|
151
311
|
key = (target, prop) => `${target}.${prop}`;
|
|
152
312
|
}
|
|
153
313
|
});
|
|
@@ -195,11 +355,11 @@ function validateScene(ir) {
|
|
|
195
355
|
);
|
|
196
356
|
}
|
|
197
357
|
const labels = /* @__PURE__ */ new Set();
|
|
198
|
-
const checkTimeline = (tl,
|
|
358
|
+
const checkTimeline = (tl, path2) => {
|
|
199
359
|
if ("label" in tl && tl.label !== void 0) {
|
|
200
360
|
if (labels.has(tl.label)) {
|
|
201
361
|
problems.push(
|
|
202
|
-
`${
|
|
362
|
+
`${path2}: duplicate timeline label "${tl.label}" \u2014 labels are overlay addresses and must be unique`
|
|
203
363
|
);
|
|
204
364
|
}
|
|
205
365
|
labels.add(tl.label);
|
|
@@ -207,33 +367,63 @@ function validateScene(ir) {
|
|
|
207
367
|
switch (tl.kind) {
|
|
208
368
|
case "seq":
|
|
209
369
|
case "par":
|
|
210
|
-
tl.children.forEach((c, i) => checkTimeline(c, `${
|
|
370
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.${tl.kind}[${i}]`));
|
|
211
371
|
break;
|
|
212
372
|
case "stagger":
|
|
213
|
-
if (tl.interval < 0) problems.push(`${
|
|
214
|
-
tl.children.forEach((c, i) => checkTimeline(c, `${
|
|
373
|
+
if (tl.interval < 0) problems.push(`${path2}: stagger interval must be >= 0`);
|
|
374
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.stagger[${i}]`));
|
|
215
375
|
break;
|
|
216
376
|
case "to":
|
|
217
377
|
if (!(tl.state in states)) {
|
|
218
378
|
problems.push(
|
|
219
|
-
`${
|
|
379
|
+
`${path2}: to("${tl.state}") references an undefined state \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
|
|
220
380
|
);
|
|
221
381
|
}
|
|
222
382
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
223
|
-
problems.push(`${
|
|
383
|
+
problems.push(`${path2}: to("${tl.state}") duration must be > 0`);
|
|
224
384
|
}
|
|
225
385
|
for (const id of tl.filter ?? []) {
|
|
226
|
-
if (!nodeById.has(id)) problems.push(`${
|
|
386
|
+
if (!nodeById.has(id)) problems.push(`${path2}: filter contains unknown node "${id}"`);
|
|
227
387
|
}
|
|
228
388
|
break;
|
|
229
389
|
case "tween":
|
|
230
|
-
checkProps(
|
|
390
|
+
checkProps(path2, tl.target, tl.props);
|
|
231
391
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
232
|
-
problems.push(`${
|
|
392
|
+
problems.push(`${path2}: tween duration must be > 0`);
|
|
233
393
|
}
|
|
234
394
|
break;
|
|
395
|
+
case "motionPath": {
|
|
396
|
+
const node = nodeById.get(tl.target);
|
|
397
|
+
if (!node) {
|
|
398
|
+
problems.push(
|
|
399
|
+
`${path2}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
400
|
+
);
|
|
401
|
+
} else if (node.type === "line") {
|
|
402
|
+
problems.push(`${path2}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
|
|
403
|
+
}
|
|
404
|
+
if (tl.points.length < 1) problems.push(`${path2}: motionPath "${tl.target}" needs at least 1 point`);
|
|
405
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
406
|
+
problems.push(`${path2}: motionPath "${tl.target}" duration must be > 0`);
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
235
410
|
case "wait":
|
|
236
|
-
if (tl.duration < 0) problems.push(`${
|
|
411
|
+
if (tl.duration < 0) problems.push(`${path2}: wait duration must be >= 0`);
|
|
412
|
+
break;
|
|
413
|
+
case "beat":
|
|
414
|
+
if (labels.has(tl.name)) {
|
|
415
|
+
problems.push(
|
|
416
|
+
`${path2}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
labels.add(tl.name);
|
|
420
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
421
|
+
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
422
|
+
}
|
|
423
|
+
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
424
|
+
problems.push(`${path2}: beat "${tl.name}" scale must be > 0`);
|
|
425
|
+
}
|
|
426
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.beat(${tl.name})[${i}]`));
|
|
237
427
|
break;
|
|
238
428
|
}
|
|
239
429
|
};
|
|
@@ -284,6 +474,7 @@ var init_validate = __esm({
|
|
|
284
474
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
285
475
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
286
476
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
477
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
287
478
|
group: COMMON_PROPS
|
|
288
479
|
};
|
|
289
480
|
SceneValidationError = class extends Error {
|
|
@@ -299,6 +490,51 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
299
490
|
});
|
|
300
491
|
|
|
301
492
|
// ../core/src/dsl.ts
|
|
493
|
+
function scene(input) {
|
|
494
|
+
const ir = { version: 1, ...input };
|
|
495
|
+
validateScene(ir);
|
|
496
|
+
if (ir.duration === void 0 && ir.timeline) {
|
|
497
|
+
ir.duration = compileScene(ir).duration;
|
|
498
|
+
}
|
|
499
|
+
return ir;
|
|
500
|
+
}
|
|
501
|
+
function rect(props) {
|
|
502
|
+
const { id, ...rest } = props;
|
|
503
|
+
return { type: "rect", id, props: rest };
|
|
504
|
+
}
|
|
505
|
+
function text(props) {
|
|
506
|
+
const { id, ...rest } = props;
|
|
507
|
+
return { type: "text", id, props: rest };
|
|
508
|
+
}
|
|
509
|
+
function path(props) {
|
|
510
|
+
const { id, ...rest } = props;
|
|
511
|
+
return { type: "path", id, props: rest };
|
|
512
|
+
}
|
|
513
|
+
function group(props, children) {
|
|
514
|
+
const { id, ...rest } = props;
|
|
515
|
+
return { type: "group", id, props: rest, children };
|
|
516
|
+
}
|
|
517
|
+
function seq(...children) {
|
|
518
|
+
return { kind: "seq", children };
|
|
519
|
+
}
|
|
520
|
+
function par(...children) {
|
|
521
|
+
return { kind: "par", children };
|
|
522
|
+
}
|
|
523
|
+
function stagger(interval, ...children) {
|
|
524
|
+
return { kind: "stagger", interval, children };
|
|
525
|
+
}
|
|
526
|
+
function beat(name, opts, children) {
|
|
527
|
+
return { kind: "beat", name, children, ...opts };
|
|
528
|
+
}
|
|
529
|
+
function tween(target, props, opts = {}) {
|
|
530
|
+
return { kind: "tween", target, props, ...opts };
|
|
531
|
+
}
|
|
532
|
+
function wait(duration, label) {
|
|
533
|
+
return { kind: "wait", duration, ...label !== void 0 && { label } };
|
|
534
|
+
}
|
|
535
|
+
function motionPath(target, points, opts = {}) {
|
|
536
|
+
return { kind: "motionPath", target, points, ...opts };
|
|
537
|
+
}
|
|
302
538
|
var init_dsl = __esm({
|
|
303
539
|
"../core/src/dsl.ts"() {
|
|
304
540
|
"use strict";
|
|
@@ -430,13 +666,16 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
430
666
|
const byLabel = /* @__PURE__ */ new Map();
|
|
431
667
|
const walkTimeline = (tl) => {
|
|
432
668
|
if ("label" in tl && tl.label !== void 0) byLabel.set(tl.label, tl);
|
|
669
|
+
if (tl.kind === "beat") byLabel.set(tl.name, tl);
|
|
433
670
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
434
671
|
};
|
|
435
672
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
436
673
|
const PATCHABLE = {
|
|
437
674
|
to: ["duration", "ease", "stagger"],
|
|
438
675
|
tween: ["duration", "ease"],
|
|
439
|
-
wait: ["duration"]
|
|
676
|
+
wait: ["duration"],
|
|
677
|
+
motionPath: ["points", "duration", "ease"],
|
|
678
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
440
679
|
};
|
|
441
680
|
let timingPatched = false;
|
|
442
681
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
@@ -460,7 +699,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
460
699
|
}
|
|
461
700
|
step[key2] = value;
|
|
462
701
|
applied(`timeline.${label}.${key2}`, "set");
|
|
463
|
-
if (
|
|
702
|
+
if (["duration", "stagger", "at", "gap", "scale", "order"].includes(key2)) timingPatched = true;
|
|
464
703
|
}
|
|
465
704
|
}
|
|
466
705
|
if (timingPatched && overlay.scene?.duration === void 0) {
|
|
@@ -484,6 +723,185 @@ var init_compose = __esm({
|
|
|
484
723
|
}
|
|
485
724
|
});
|
|
486
725
|
|
|
726
|
+
// ../core/src/presets.ts
|
|
727
|
+
function makeRng(seed) {
|
|
728
|
+
let a = seed >>> 0 || 2654435769;
|
|
729
|
+
return () => {
|
|
730
|
+
a = a + 1831565813 | 0;
|
|
731
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
732
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
733
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function ctx(o) {
|
|
737
|
+
const rand = makeRng((o.seed ?? 0) + 1);
|
|
738
|
+
return {
|
|
739
|
+
e: clamp01(o.energy ?? 0.5),
|
|
740
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
741
|
+
it: clamp01(o.intensity ?? 0.5),
|
|
742
|
+
from: o.from,
|
|
743
|
+
rand,
|
|
744
|
+
jit: (amp) => (rand() - 0.5) * 2 * amp,
|
|
745
|
+
g: o.target.group,
|
|
746
|
+
cx: o.target.center[0],
|
|
747
|
+
cy: o.target.center[1],
|
|
748
|
+
s: o.target.baseScale,
|
|
749
|
+
fills: o.target.fills,
|
|
750
|
+
inks: o.target.inks
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function settleEase(e) {
|
|
754
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
755
|
+
}
|
|
756
|
+
function fromVec(from, dist) {
|
|
757
|
+
switch (from) {
|
|
758
|
+
case "left":
|
|
759
|
+
return [-dist, 0];
|
|
760
|
+
case "right":
|
|
761
|
+
return [dist, 0];
|
|
762
|
+
case "top":
|
|
763
|
+
return [0, -dist];
|
|
764
|
+
default:
|
|
765
|
+
return [0, dist];
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
769
|
+
return stagger(
|
|
770
|
+
gap / c.sp,
|
|
771
|
+
...c.fills.map(
|
|
772
|
+
(id, i) => tween(id, { opacity: 1 }, { duration: dur(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
773
|
+
)
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
function drawInks(c) {
|
|
777
|
+
return stagger(
|
|
778
|
+
0.15 / c.sp,
|
|
779
|
+
...c.inks.map(
|
|
780
|
+
(id, i) => tween(id, { progress: 1 }, { duration: dur(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
781
|
+
)
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
function motionPreset(name, opts) {
|
|
785
|
+
const c = ctx(opts);
|
|
786
|
+
switch (name) {
|
|
787
|
+
case "draw-bloom":
|
|
788
|
+
return beat("draw-bloom", {}, [
|
|
789
|
+
drawInks(c),
|
|
790
|
+
fadeFills(c, 0.45),
|
|
791
|
+
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
792
|
+
]);
|
|
793
|
+
case "punch-in": {
|
|
794
|
+
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
795
|
+
return beat("punch-in", {}, [
|
|
796
|
+
par(
|
|
797
|
+
fadeFills(c, 0.25),
|
|
798
|
+
seq(
|
|
799
|
+
tween(c.g, { scale: peak }, { duration: dur(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
800
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.5, c.sp), ease: settleEase(c.e) })
|
|
801
|
+
)
|
|
802
|
+
)
|
|
803
|
+
]);
|
|
804
|
+
}
|
|
805
|
+
case "rise-settle": {
|
|
806
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
807
|
+
const dist = (220 + 260 * c.it) * es;
|
|
808
|
+
const [dx, dy] = fromVec(c.from ?? "bottom", dist);
|
|
809
|
+
const jx = c.jit(110);
|
|
810
|
+
return beat("rise-settle", {}, [
|
|
811
|
+
par(
|
|
812
|
+
motionPath(
|
|
813
|
+
c.g,
|
|
814
|
+
[
|
|
815
|
+
[c.cx + dx + jx, c.cy + dy],
|
|
816
|
+
[c.cx + dx * 0.4 - jx * 0.6, c.cy + dy * 0.4],
|
|
817
|
+
[c.cx, c.cy]
|
|
818
|
+
],
|
|
819
|
+
{ duration: dur(1.1, c.sp), ease: settleEase(c.e), label: "rise" }
|
|
820
|
+
),
|
|
821
|
+
fadeFills(c, 0.4)
|
|
822
|
+
)
|
|
823
|
+
]);
|
|
824
|
+
}
|
|
825
|
+
case "slide-bank": {
|
|
826
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
827
|
+
const dist = (420 + 240 * c.it) * es;
|
|
828
|
+
const [dx, dy] = fromVec(c.from ?? "left", dist);
|
|
829
|
+
const arc = c.jit(140);
|
|
830
|
+
const midx = c.jit(120);
|
|
831
|
+
const move = dur(1.2, c.sp);
|
|
832
|
+
return beat("slide-bank", {}, [
|
|
833
|
+
par(
|
|
834
|
+
motionPath(
|
|
835
|
+
c.g,
|
|
836
|
+
[
|
|
837
|
+
[c.cx + dx, c.cy + dy],
|
|
838
|
+
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
839
|
+
[c.cx, c.cy]
|
|
840
|
+
],
|
|
841
|
+
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
842
|
+
),
|
|
843
|
+
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
844
|
+
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur(0.5, c.sp), ease: "easeOutCubic" })),
|
|
845
|
+
fadeFills(c, 0.4)
|
|
846
|
+
)
|
|
847
|
+
]);
|
|
848
|
+
}
|
|
849
|
+
case "reveal-orbit": {
|
|
850
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
851
|
+
const orbit = (180 + 160 * c.it) * es;
|
|
852
|
+
const jx = c.jit(0.4);
|
|
853
|
+
const jy = c.jit(0.4);
|
|
854
|
+
return beat("reveal-orbit", {}, [
|
|
855
|
+
drawInks(c),
|
|
856
|
+
fadeFills(c, 0.45),
|
|
857
|
+
par(
|
|
858
|
+
motionPath(
|
|
859
|
+
c.g,
|
|
860
|
+
[
|
|
861
|
+
[c.cx, c.cy],
|
|
862
|
+
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
863
|
+
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
864
|
+
[c.cx, c.cy]
|
|
865
|
+
],
|
|
866
|
+
{ duration: dur(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
867
|
+
),
|
|
868
|
+
seq(
|
|
869
|
+
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur(0.85, c.sp), ease: "easeOutBack" }),
|
|
870
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.85, c.sp), ease: "easeInOutQuad" })
|
|
871
|
+
)
|
|
872
|
+
)
|
|
873
|
+
]);
|
|
874
|
+
}
|
|
875
|
+
case "spin-forge": {
|
|
876
|
+
const turns = 1 + Math.round(c.it);
|
|
877
|
+
const dir = c.rand() < 0.5 ? -1 : 1;
|
|
878
|
+
const startRot = dir * 360 * turns;
|
|
879
|
+
const peak = c.s * (1 + 0.05 + 0.2 * c.e);
|
|
880
|
+
return beat("spin-forge", {}, [
|
|
881
|
+
par(
|
|
882
|
+
seq(
|
|
883
|
+
tween(c.g, { scale: c.s * 0.2, rotation: startRot }, { duration: SET }),
|
|
884
|
+
// establish (invisible)
|
|
885
|
+
tween(c.g, { scale: peak, rotation: 0 }, { duration: dur(0.9, c.sp), ease: "easeOutBack", label: "spin" }),
|
|
886
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.3, c.sp), ease: "easeInOutQuad" })
|
|
887
|
+
),
|
|
888
|
+
seq(wait(SET), fadeFills(c, 0.3))
|
|
889
|
+
)
|
|
890
|
+
]);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
var clamp01, SET, dur;
|
|
895
|
+
var init_presets = __esm({
|
|
896
|
+
"../core/src/presets.ts"() {
|
|
897
|
+
"use strict";
|
|
898
|
+
init_dsl();
|
|
899
|
+
clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
900
|
+
SET = 1 / 120;
|
|
901
|
+
dur = (base, sp) => base / sp;
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
|
|
487
905
|
// ../core/src/audio.ts
|
|
488
906
|
function resolveAudioPlan(compiled) {
|
|
489
907
|
const audio = compiled.ir.audio;
|
|
@@ -569,10 +987,23 @@ var init_behaviors = __esm({
|
|
|
569
987
|
});
|
|
570
988
|
|
|
571
989
|
// ../core/src/interpolate.ts
|
|
572
|
-
|
|
990
|
+
function easeOutBounce(u) {
|
|
991
|
+
const n1 = 7.5625;
|
|
992
|
+
const d1 = 2.75;
|
|
993
|
+
if (u < 1 / d1) return n1 * u * u;
|
|
994
|
+
if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
|
|
995
|
+
if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
|
|
996
|
+
return n1 * (u -= 2.625 / d1) * u + 0.984375;
|
|
997
|
+
}
|
|
998
|
+
var BACK_C1, BACK_C2, BACK_C3, ELASTIC_C4, ELASTIC_C5, EASE_TABLE, EASE_NAMES;
|
|
573
999
|
var init_interpolate = __esm({
|
|
574
1000
|
"../core/src/interpolate.ts"() {
|
|
575
1001
|
"use strict";
|
|
1002
|
+
BACK_C1 = 1.70158;
|
|
1003
|
+
BACK_C2 = BACK_C1 * 1.525;
|
|
1004
|
+
BACK_C3 = BACK_C1 + 1;
|
|
1005
|
+
ELASTIC_C4 = 2 * Math.PI / 3;
|
|
1006
|
+
ELASTIC_C5 = 2 * Math.PI / 4.5;
|
|
576
1007
|
EASE_TABLE = {
|
|
577
1008
|
linear: (u) => u,
|
|
578
1009
|
easeInQuad: (u) => u * u,
|
|
@@ -586,7 +1017,20 @@ var init_interpolate = __esm({
|
|
|
586
1017
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
587
1018
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
588
1019
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
589
|
-
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
|
|
1020
|
+
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
|
|
1021
|
+
// --- expressive eases (GSAP's signature feel) — standard Penner equations ---
|
|
1022
|
+
// back: overshoots past the target then settles (pop / snap)
|
|
1023
|
+
easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
|
|
1024
|
+
easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
|
|
1025
|
+
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,
|
|
1026
|
+
// elastic: rings around the target before settling (playful spring)
|
|
1027
|
+
easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
|
|
1028
|
+
easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
|
|
1029
|
+
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,
|
|
1030
|
+
// bounce: drops and bounces to rest (lands without overshoot)
|
|
1031
|
+
easeInBounce: (u) => 1 - easeOutBounce(1 - u),
|
|
1032
|
+
easeOutBounce,
|
|
1033
|
+
easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
|
|
590
1034
|
};
|
|
591
1035
|
EASE_NAMES = Object.keys(EASE_TABLE);
|
|
592
1036
|
}
|
|
@@ -598,6 +1042,7 @@ var init_evaluate = __esm({
|
|
|
598
1042
|
"use strict";
|
|
599
1043
|
init_behaviors();
|
|
600
1044
|
init_interpolate();
|
|
1045
|
+
init_path();
|
|
601
1046
|
}
|
|
602
1047
|
});
|
|
603
1048
|
|
|
@@ -638,6 +1083,14 @@ var init_assets = __esm({
|
|
|
638
1083
|
}
|
|
639
1084
|
});
|
|
640
1085
|
|
|
1086
|
+
// ../core/src/motion.ts
|
|
1087
|
+
var init_motion = __esm({
|
|
1088
|
+
"../core/src/motion.ts"() {
|
|
1089
|
+
"use strict";
|
|
1090
|
+
init_dsl();
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
|
|
641
1094
|
// ../core/src/index.ts
|
|
642
1095
|
var init_src = __esm({
|
|
643
1096
|
"../core/src/index.ts"() {
|
|
@@ -647,11 +1100,160 @@ var init_src = __esm({
|
|
|
647
1100
|
init_validate();
|
|
648
1101
|
init_compose();
|
|
649
1102
|
init_compile();
|
|
1103
|
+
init_path();
|
|
1104
|
+
init_presets();
|
|
650
1105
|
init_audio();
|
|
651
1106
|
init_evaluate();
|
|
652
1107
|
init_interpolate();
|
|
653
1108
|
init_behaviors();
|
|
654
1109
|
init_assets();
|
|
1110
|
+
init_motion();
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
// ../render-cli/src/logoSting.ts
|
|
1115
|
+
var logoSting_exports = {};
|
|
1116
|
+
__export(logoSting_exports, {
|
|
1117
|
+
LOGO_PRESETS: () => LOGO_PRESETS,
|
|
1118
|
+
buildLogoSting: () => buildLogoSting,
|
|
1119
|
+
resolveLogo: () => resolveLogo
|
|
1120
|
+
});
|
|
1121
|
+
import { existsSync } from "node:fs";
|
|
1122
|
+
import { readFile } from "node:fs/promises";
|
|
1123
|
+
async function loadSvg(arg) {
|
|
1124
|
+
if (existsSync(arg)) {
|
|
1125
|
+
return { svg: await readFile(arg, "utf8"), name: arg.split("/").pop().replace(/\.svg$/i, "") };
|
|
1126
|
+
}
|
|
1127
|
+
const slug = arg.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1128
|
+
const r = await fetch(`https://cdn.simpleicons.org/${slug}`);
|
|
1129
|
+
if (!r.ok) throw new Error(`no local file "${arg}", and simple-icons has no "${slug}" (${r.status})`);
|
|
1130
|
+
return { svg: await r.text(), name: arg };
|
|
1131
|
+
}
|
|
1132
|
+
function tooDark(hex) {
|
|
1133
|
+
const h = hex.replace("#", "");
|
|
1134
|
+
const n = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
1135
|
+
const r = parseInt(n.slice(0, 2), 16);
|
|
1136
|
+
const g = parseInt(n.slice(2, 4), 16);
|
|
1137
|
+
const b = parseInt(n.slice(4, 6), 16);
|
|
1138
|
+
return 0.299 * r + 0.587 * g + 0.114 * b < 40;
|
|
1139
|
+
}
|
|
1140
|
+
function parseSvg(svg) {
|
|
1141
|
+
let viewBox = { minX: 0, minY: 0, w: 100, h: 100 };
|
|
1142
|
+
const vb = svg.match(/viewBox\s*=\s*"([\d.\-\s]+)"/i);
|
|
1143
|
+
if (vb) {
|
|
1144
|
+
const [a, b, c, d] = vb[1].trim().split(/\s+/).map(Number);
|
|
1145
|
+
viewBox = { minX: a, minY: b, w: c, h: d };
|
|
1146
|
+
} else {
|
|
1147
|
+
const w = svg.match(/\bwidth\s*=\s*"([\d.]+)/i);
|
|
1148
|
+
const h = svg.match(/\bheight\s*=\s*"([\d.]+)/i);
|
|
1149
|
+
if (w && h) viewBox = { minX: 0, minY: 0, w: +w[1], h: +h[1] };
|
|
1150
|
+
}
|
|
1151
|
+
const rootFill = svg.match(/<svg[^>]*\bfill\s*=\s*"(#[0-9a-fA-F]{3,8})"/)?.[1];
|
|
1152
|
+
const fallback = rootFill && !tooDark(rootFill) ? rootFill : "#E6EDF3";
|
|
1153
|
+
const paths = [];
|
|
1154
|
+
const re = /<path\b[^>]*>/g;
|
|
1155
|
+
let m;
|
|
1156
|
+
while (m = re.exec(svg)) {
|
|
1157
|
+
const tag = m[0];
|
|
1158
|
+
const d = tag.match(/\bd\s*=\s*"([^"]+)"/)?.[1];
|
|
1159
|
+
if (!d) continue;
|
|
1160
|
+
let fill = tag.match(/\bfill\s*=\s*"(#[0-9a-fA-F]{3,8})"/)?.[1] ?? fallback;
|
|
1161
|
+
if (tooDark(fill)) fill = fallback;
|
|
1162
|
+
paths.push({ d, fill });
|
|
1163
|
+
}
|
|
1164
|
+
return { paths, viewBox };
|
|
1165
|
+
}
|
|
1166
|
+
async function resolveLogo(arg, displayName, opts) {
|
|
1167
|
+
if (opts.motion && !LOGO_PRESETS.includes(opts.motion)) {
|
|
1168
|
+
throw new Error(`unknown --motion "${opts.motion}". options: ${LOGO_PRESETS.join(", ")}`);
|
|
1169
|
+
}
|
|
1170
|
+
const { svg, name } = await loadSvg(arg);
|
|
1171
|
+
const { paths, viewBox } = parseSvg(svg);
|
|
1172
|
+
if (paths.length === 0) throw new Error("no <path> elements found \u2014 logo stings need a path-based SVG");
|
|
1173
|
+
const from = FROMS.includes(opts.from) ? opts.from : void 0;
|
|
1174
|
+
const data = {
|
|
1175
|
+
name: displayName ?? titleCase(name),
|
|
1176
|
+
paths,
|
|
1177
|
+
viewBox,
|
|
1178
|
+
...opts.motion && { motion: opts.motion },
|
|
1179
|
+
...opts.energy !== void 0 && { energy: opts.energy },
|
|
1180
|
+
...opts.speed !== void 0 && { speed: opts.speed },
|
|
1181
|
+
...opts.intensity !== void 0 && { intensity: opts.intensity },
|
|
1182
|
+
...from && { from },
|
|
1183
|
+
...opts.seed !== void 0 && { seed: opts.seed }
|
|
1184
|
+
};
|
|
1185
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "logo";
|
|
1186
|
+
return { data, slug };
|
|
1187
|
+
}
|
|
1188
|
+
function buildLogoSting(d) {
|
|
1189
|
+
const W = 1080;
|
|
1190
|
+
const H = 1080;
|
|
1191
|
+
const CX = 540;
|
|
1192
|
+
const CY = 500;
|
|
1193
|
+
const vcx = d.viewBox.minX + d.viewBox.w / 2;
|
|
1194
|
+
const vcy = d.viewBox.minY + d.viewBox.h / 2;
|
|
1195
|
+
const fit = LOGO_PX / Math.max(d.viewBox.w, d.viewBox.h);
|
|
1196
|
+
const sw = 2.2 / fit;
|
|
1197
|
+
const fills = d.paths.map(
|
|
1198
|
+
(p, i) => path({ id: `fill-${i}`, d: p.d, originX: vcx, originY: vcy, x: 0, y: 0, fill: p.fill, opacity: 0 })
|
|
1199
|
+
);
|
|
1200
|
+
const inks = d.paths.map(
|
|
1201
|
+
(p, i) => path({ id: `ink-${i}`, d: p.d, originX: vcx, originY: vcy, x: 0, y: 0, stroke: p.fill, strokeWidth: sw, progress: 0 })
|
|
1202
|
+
);
|
|
1203
|
+
const rig = {
|
|
1204
|
+
group: "logo",
|
|
1205
|
+
center: [CX, CY],
|
|
1206
|
+
baseScale: fit,
|
|
1207
|
+
fills: fills.map((n) => n.id),
|
|
1208
|
+
inks: inks.map((n) => n.id)
|
|
1209
|
+
};
|
|
1210
|
+
return scene({
|
|
1211
|
+
id: "logo-sting",
|
|
1212
|
+
size: { width: W, height: H },
|
|
1213
|
+
fps: 30,
|
|
1214
|
+
background: BG,
|
|
1215
|
+
nodes: [
|
|
1216
|
+
rect({ id: "bg", x: 0, y: 0, width: W, height: H, fill: BG }),
|
|
1217
|
+
group({ id: "logo", x: CX, y: CY, scale: fit }, [...fills, ...inks]),
|
|
1218
|
+
text({ id: "word", x: CX, y: 905, anchor: "center", content: d.name, fontFamily: "Inter", fontSize: 56, fontWeight: 800, fill: FG, opacity: 0 }),
|
|
1219
|
+
text({ id: "made", x: CX, y: 968, anchor: "center", content: "made with reframe", fontFamily: "Inter", fontSize: 20, fill: MUTED, opacity: 0 })
|
|
1220
|
+
],
|
|
1221
|
+
timeline: seq(
|
|
1222
|
+
motionPreset(d.motion ?? "reveal-orbit", {
|
|
1223
|
+
target: rig,
|
|
1224
|
+
...d.energy !== void 0 && { energy: d.energy },
|
|
1225
|
+
...d.speed !== void 0 && { speed: d.speed },
|
|
1226
|
+
...d.intensity !== void 0 && { intensity: d.intensity },
|
|
1227
|
+
...d.from !== void 0 && { from: d.from },
|
|
1228
|
+
...d.seed !== void 0 && { seed: d.seed }
|
|
1229
|
+
}),
|
|
1230
|
+
par(
|
|
1231
|
+
tween("word", { opacity: 1 }, { duration: 0.5, ease: "easeOutQuad", label: "word" }),
|
|
1232
|
+
seq(wait(0.2), tween("made", { opacity: 1 }, { duration: 0.5, ease: "easeOutQuad" }))
|
|
1233
|
+
),
|
|
1234
|
+
wait(0.8, "hold")
|
|
1235
|
+
)
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
var LOGO_PRESETS, titleCase, FROMS, BG, FG, MUTED, LOGO_PX;
|
|
1239
|
+
var init_logoSting = __esm({
|
|
1240
|
+
"../render-cli/src/logoSting.ts"() {
|
|
1241
|
+
"use strict";
|
|
1242
|
+
init_src();
|
|
1243
|
+
LOGO_PRESETS = [
|
|
1244
|
+
"draw-bloom",
|
|
1245
|
+
"punch-in",
|
|
1246
|
+
"rise-settle",
|
|
1247
|
+
"slide-bank",
|
|
1248
|
+
"reveal-orbit",
|
|
1249
|
+
"spin-forge"
|
|
1250
|
+
];
|
|
1251
|
+
titleCase = (s) => s.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
|
|
1252
|
+
FROMS = ["left", "right", "top", "bottom"];
|
|
1253
|
+
BG = "#0D1117";
|
|
1254
|
+
FG = "#E6EDF3";
|
|
1255
|
+
MUTED = "#8B949E";
|
|
1256
|
+
LOGO_PX = 520;
|
|
655
1257
|
}
|
|
656
1258
|
});
|
|
657
1259
|
|
|
@@ -699,52 +1301,52 @@ function buffer(duration) {
|
|
|
699
1301
|
return { out: new Float32Array(n), n };
|
|
700
1302
|
}
|
|
701
1303
|
function whoosh(seed) {
|
|
702
|
-
const
|
|
703
|
-
const { out, n } = buffer(
|
|
1304
|
+
const dur2 = 0.35;
|
|
1305
|
+
const { out, n } = buffer(dur2);
|
|
704
1306
|
let lp = 0;
|
|
705
1307
|
let lp2 = 0;
|
|
706
1308
|
for (let i = 0; i < n; i++) {
|
|
707
1309
|
const t = i / SAMPLE_RATE;
|
|
708
|
-
const u = t /
|
|
1310
|
+
const u = t / dur2;
|
|
709
1311
|
const center = 1200 * Math.pow(300 / 1200, u);
|
|
710
1312
|
const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
|
|
711
1313
|
lp += alpha * (noise(i, seed) - lp);
|
|
712
1314
|
lp2 += alpha * 0.5 * (lp - lp2);
|
|
713
|
-
const env = u < 0.3 ? u / 0.3 : expDecay(t - 0.3 *
|
|
1315
|
+
const env = u < 0.3 ? u / 0.3 : expDecay(t - 0.3 * dur2, dur2 * 0.7, 4);
|
|
714
1316
|
out[i] = (lp - lp2) * env * 2.2;
|
|
715
1317
|
}
|
|
716
1318
|
return out;
|
|
717
1319
|
}
|
|
718
1320
|
function pop(seed) {
|
|
719
|
-
const
|
|
720
|
-
const { out, n } = buffer(
|
|
1321
|
+
const dur2 = 0.12;
|
|
1322
|
+
const { out, n } = buffer(dur2);
|
|
721
1323
|
let phase = 0;
|
|
722
1324
|
for (let i = 0; i < n; i++) {
|
|
723
1325
|
const t = i / SAMPLE_RATE;
|
|
724
1326
|
const freq = 600 * Math.pow(150 / 600, t / 0.08);
|
|
725
1327
|
phase += TAU * freq / SAMPLE_RATE;
|
|
726
1328
|
const transient = t < 2e-3 ? noise(i, seed) * 0.5 : 0;
|
|
727
|
-
out[i] = (Math.sin(phase) + transient) * expDecay(t,
|
|
1329
|
+
out[i] = (Math.sin(phase) + transient) * expDecay(t, dur2, 6) * 0.8;
|
|
728
1330
|
}
|
|
729
1331
|
return out;
|
|
730
1332
|
}
|
|
731
1333
|
function tick(seed) {
|
|
732
|
-
const
|
|
733
|
-
const { out, n } = buffer(
|
|
1334
|
+
const dur2 = 0.03;
|
|
1335
|
+
const { out, n } = buffer(dur2);
|
|
734
1336
|
for (let i = 0; i < n; i++) {
|
|
735
1337
|
const t = i / SAMPLE_RATE;
|
|
736
1338
|
const sine = t < 4e-3 ? Math.sin(TAU * 4e3 * t) : 0;
|
|
737
|
-
out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t,
|
|
1339
|
+
out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur2, 8);
|
|
738
1340
|
}
|
|
739
1341
|
return out;
|
|
740
1342
|
}
|
|
741
1343
|
function rise(seed) {
|
|
742
|
-
const
|
|
743
|
-
const { out, n } = buffer(
|
|
1344
|
+
const dur2 = 0.5;
|
|
1345
|
+
const { out, n } = buffer(dur2);
|
|
744
1346
|
let phase = 0;
|
|
745
1347
|
for (let i = 0; i < n; i++) {
|
|
746
1348
|
const t = i / SAMPLE_RATE;
|
|
747
|
-
const u = t /
|
|
1349
|
+
const u = t / dur2;
|
|
748
1350
|
const freq = 220 * Math.pow(880 / 220, u);
|
|
749
1351
|
phase += TAU * freq / SAMPLE_RATE;
|
|
750
1352
|
const env = Math.sin(Math.PI * Math.min(1, u * 1.05)) ** 1.5;
|
|
@@ -753,8 +1355,8 @@ function rise(seed) {
|
|
|
753
1355
|
return out;
|
|
754
1356
|
}
|
|
755
1357
|
function shimmer(seed) {
|
|
756
|
-
const
|
|
757
|
-
const { out, n } = buffer(
|
|
1358
|
+
const dur2 = 0.9;
|
|
1359
|
+
const { out, n } = buffer(dur2);
|
|
758
1360
|
const partials = Array.from({ length: 5 }, (_, p) => ({
|
|
759
1361
|
freq: 2e3 + hash01(p, seed + 7) * 2e3,
|
|
760
1362
|
am: 0.5 + hash01(p, seed + 8) * 1.5,
|
|
@@ -762,7 +1364,7 @@ function shimmer(seed) {
|
|
|
762
1364
|
}));
|
|
763
1365
|
for (let i = 0; i < n; i++) {
|
|
764
1366
|
const t = i / SAMPLE_RATE;
|
|
765
|
-
const u = t /
|
|
1367
|
+
const u = t / dur2;
|
|
766
1368
|
const env = Math.sin(Math.PI * u) ** 1.2;
|
|
767
1369
|
let s = 0;
|
|
768
1370
|
for (const part of partials) {
|
|
@@ -773,8 +1375,8 @@ function shimmer(seed) {
|
|
|
773
1375
|
return out;
|
|
774
1376
|
}
|
|
775
1377
|
function thud(seed) {
|
|
776
|
-
const
|
|
777
|
-
const { out, n } = buffer(
|
|
1378
|
+
const dur2 = 0.25;
|
|
1379
|
+
const { out, n } = buffer(dur2);
|
|
778
1380
|
let phase = 0;
|
|
779
1381
|
let lp = 0;
|
|
780
1382
|
for (let i = 0; i < n; i++) {
|
|
@@ -783,7 +1385,7 @@ function thud(seed) {
|
|
|
783
1385
|
phase += TAU * freq / SAMPLE_RATE;
|
|
784
1386
|
lp += 0.02 * (noise(i, seed) - lp);
|
|
785
1387
|
const attack = t < 0.01 ? lp * 3 : 0;
|
|
786
|
-
out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t,
|
|
1388
|
+
out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur2, 5);
|
|
787
1389
|
}
|
|
788
1390
|
return out;
|
|
789
1391
|
}
|
|
@@ -818,7 +1420,7 @@ var init_synth = __esm({
|
|
|
818
1420
|
init_wav();
|
|
819
1421
|
noise = (n, seed) => hash01(n, seed) * 2 - 1;
|
|
820
1422
|
TAU = Math.PI * 2;
|
|
821
|
-
expDecay = (t,
|
|
1423
|
+
expDecay = (t, dur2, k = 5) => Math.exp(-k * t / dur2);
|
|
822
1424
|
RECIPES = {
|
|
823
1425
|
whoosh,
|
|
824
1426
|
pop,
|
|
@@ -832,26 +1434,26 @@ var init_synth = __esm({
|
|
|
832
1434
|
|
|
833
1435
|
// ../render-cli/src/audio/sfx.ts
|
|
834
1436
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
835
|
-
import { existsSync } from "node:fs";
|
|
1437
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
836
1438
|
import { tmpdir } from "node:os";
|
|
837
1439
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
838
1440
|
import { fileURLToPath } from "node:url";
|
|
839
|
-
function fnv1a(
|
|
1441
|
+
function fnv1a(text2) {
|
|
840
1442
|
let h = 2166136261;
|
|
841
|
-
for (let i = 0; i <
|
|
842
|
-
h ^=
|
|
1443
|
+
for (let i = 0; i < text2.length; i++) {
|
|
1444
|
+
h ^= text2.charCodeAt(i);
|
|
843
1445
|
h = Math.imul(h, 16777619);
|
|
844
1446
|
}
|
|
845
1447
|
return (h >>> 0).toString(16);
|
|
846
1448
|
}
|
|
847
1449
|
async function writeCached(key2, make) {
|
|
848
|
-
const
|
|
849
|
-
if (
|
|
1450
|
+
const path2 = join(CACHE, `${key2}.wav`);
|
|
1451
|
+
if (existsSync2(path2)) return path2;
|
|
850
1452
|
await mkdir(CACHE, { recursive: true });
|
|
851
|
-
const temp = `${
|
|
1453
|
+
const temp = `${path2}.${process.pid}.${fnv1a(String(performance.now()))}.tmp`;
|
|
852
1454
|
await writeFile(temp, encodeWavMono16(make()));
|
|
853
|
-
await rename(temp,
|
|
854
|
-
return
|
|
1455
|
+
await rename(temp, path2);
|
|
1456
|
+
return path2;
|
|
855
1457
|
}
|
|
856
1458
|
async function resolveCueFile(cue, sceneDir) {
|
|
857
1459
|
if (cue.source.kind === "file") {
|
|
@@ -861,14 +1463,14 @@ async function resolveCueFile(cue, sceneDir) {
|
|
|
861
1463
|
resolve(sceneDir, p),
|
|
862
1464
|
join(VENDORED, p)
|
|
863
1465
|
]) {
|
|
864
|
-
if (candidate &&
|
|
1466
|
+
if (candidate && existsSync2(candidate)) return candidate;
|
|
865
1467
|
}
|
|
866
1468
|
throw new Error(
|
|
867
1469
|
`audio cue file "${p}" not found (tried absolute, scene-relative, assets/sfx/)`
|
|
868
1470
|
);
|
|
869
1471
|
}
|
|
870
1472
|
const vendored = join(VENDORED, `${cue.source.name}.wav`);
|
|
871
|
-
if (
|
|
1473
|
+
if (existsSync2(vendored)) return vendored;
|
|
872
1474
|
const { name, params } = cue.source;
|
|
873
1475
|
return writeCached(`${name}-${fnv1a(JSON.stringify(params))}`, () => synthSfx(name, params));
|
|
874
1476
|
}
|
|
@@ -876,7 +1478,7 @@ async function resolveBgmFile(source, duration, sceneDir) {
|
|
|
876
1478
|
if (source.kind === "file") {
|
|
877
1479
|
const p = source.path;
|
|
878
1480
|
for (const candidate of [isAbsolute(p) ? p : null, resolve(sceneDir, p), join(VENDORED, p)]) {
|
|
879
|
-
if (candidate &&
|
|
1481
|
+
if (candidate && existsSync2(candidate)) return candidate;
|
|
880
1482
|
}
|
|
881
1483
|
throw new Error(`bgm file "${p}" not found`);
|
|
882
1484
|
}
|
|
@@ -1046,14 +1648,14 @@ var init_encode = __esm({
|
|
|
1046
1648
|
});
|
|
1047
1649
|
|
|
1048
1650
|
// ../render-cli/src/fonts.ts
|
|
1049
|
-
import { readFile } from "node:fs/promises";
|
|
1651
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1050
1652
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
1051
1653
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1052
1654
|
async function fontFaceCss() {
|
|
1053
1655
|
if (cssCache) return cssCache;
|
|
1054
1656
|
const rules = await Promise.all(
|
|
1055
1657
|
WEIGHTS.map(async (weight) => {
|
|
1056
|
-
const data = await
|
|
1658
|
+
const data = await readFile2(join3(FONTS_DIR, `inter-${weight}.woff2`));
|
|
1057
1659
|
return `@font-face {
|
|
1058
1660
|
font-family: "Inter";
|
|
1059
1661
|
font-style: normal;
|
|
@@ -1076,8 +1678,8 @@ var init_fonts = __esm({
|
|
|
1076
1678
|
});
|
|
1077
1679
|
|
|
1078
1680
|
// ../render-cli/src/images.ts
|
|
1079
|
-
import { readFile as
|
|
1080
|
-
import { existsSync as
|
|
1681
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1682
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1081
1683
|
import { extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
1082
1684
|
async function buildImageAssets(ir, sceneDir) {
|
|
1083
1685
|
const assets = {};
|
|
@@ -1091,11 +1693,11 @@ async function buildImageAssets(ir, sceneDir) {
|
|
|
1091
1693
|
const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
|
|
1092
1694
|
(c) => c !== null
|
|
1093
1695
|
);
|
|
1094
|
-
const found = candidates.find((c) =>
|
|
1696
|
+
const found = candidates.find((c) => existsSync3(c));
|
|
1095
1697
|
if (!found) {
|
|
1096
1698
|
throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
1097
1699
|
}
|
|
1098
|
-
const data = await
|
|
1700
|
+
const data = await readFile3(found);
|
|
1099
1701
|
assets[src] = `data:${mime};base64,${data.toString("base64")}`;
|
|
1100
1702
|
}
|
|
1101
1703
|
return assets;
|
|
@@ -1219,8 +1821,8 @@ async function withPage(size, fn) {
|
|
|
1219
1821
|
async function browserBundle() {
|
|
1220
1822
|
if (bundleCache) return bundleCache;
|
|
1221
1823
|
if (true) {
|
|
1222
|
-
const { readFile:
|
|
1223
|
-
bundleCache = await
|
|
1824
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
1825
|
+
bundleCache = await readFile6(
|
|
1224
1826
|
join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
1225
1827
|
"utf8"
|
|
1226
1828
|
);
|
|
@@ -1282,7 +1884,7 @@ __export(batch_exports, {
|
|
|
1282
1884
|
parseCsv: () => parseCsv,
|
|
1283
1885
|
runBatch: () => runBatch
|
|
1284
1886
|
});
|
|
1285
|
-
import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as
|
|
1887
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
1286
1888
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
1287
1889
|
import { join as join5, dirname as dirname5 } from "node:path";
|
|
1288
1890
|
function overlayFromFlat(row, name) {
|
|
@@ -1321,8 +1923,8 @@ function overlayFromFlat(row, name) {
|
|
|
1321
1923
|
}
|
|
1322
1924
|
return doc;
|
|
1323
1925
|
}
|
|
1324
|
-
function parseCsv(
|
|
1325
|
-
const lines =
|
|
1926
|
+
function parseCsv(text2) {
|
|
1927
|
+
const lines = text2.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
1326
1928
|
if (lines.length < 2) return [];
|
|
1327
1929
|
const split = (line) => {
|
|
1328
1930
|
const out = [];
|
|
@@ -1357,14 +1959,14 @@ function parseCsv(text) {
|
|
|
1357
1959
|
return row;
|
|
1358
1960
|
});
|
|
1359
1961
|
}
|
|
1360
|
-
async function loadRows(
|
|
1361
|
-
const
|
|
1362
|
-
if (
|
|
1363
|
-
const parsed = JSON.parse(
|
|
1364
|
-
if (!Array.isArray(parsed)) throw new Error(`${
|
|
1962
|
+
async function loadRows(path2) {
|
|
1963
|
+
const text2 = await readFile4(path2, "utf8");
|
|
1964
|
+
if (path2.endsWith(".csv")) return parseCsv(text2);
|
|
1965
|
+
const parsed = JSON.parse(text2);
|
|
1966
|
+
if (!Array.isArray(parsed)) throw new Error(`${path2}: expected a JSON array of row objects`);
|
|
1365
1967
|
return parsed;
|
|
1366
1968
|
}
|
|
1367
|
-
async function runBatch(
|
|
1969
|
+
async function runBatch(scene2, rows, opts) {
|
|
1368
1970
|
await mkdir3(opts.outDir, { recursive: true });
|
|
1369
1971
|
const results = new Array(rows.length);
|
|
1370
1972
|
let next = 0;
|
|
@@ -1377,7 +1979,7 @@ async function runBatch(scene, rows, opts) {
|
|
|
1377
1979
|
let result;
|
|
1378
1980
|
try {
|
|
1379
1981
|
const rowOverlay = overlayFromFlat(row, name);
|
|
1380
|
-
const { ir, report } = composeScene(
|
|
1982
|
+
const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
|
|
1381
1983
|
const framesDir = await mkdtemp2(join5(tmpdir3(), `reframe-batch-${index}-`));
|
|
1382
1984
|
const output = join5(opts.outDir, `${name}.mp4`);
|
|
1383
1985
|
const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
|
|
@@ -1443,19 +2045,19 @@ __export(loadScene_exports, {
|
|
|
1443
2045
|
loadScene: () => loadScene
|
|
1444
2046
|
});
|
|
1445
2047
|
import { build as build2 } from "esbuild";
|
|
1446
|
-
import { readFile as
|
|
2048
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1447
2049
|
import { dirname as dirname6, resolve as resolve3 } from "node:path";
|
|
1448
2050
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1449
|
-
async function loadScene(
|
|
1450
|
-
if (
|
|
1451
|
-
const ir = JSON.parse(await
|
|
2051
|
+
async function loadScene(path2) {
|
|
2052
|
+
if (path2.endsWith(".json")) {
|
|
2053
|
+
const ir = JSON.parse(await readFile5(path2, "utf8"));
|
|
1452
2054
|
validateScene(ir);
|
|
1453
2055
|
return ir;
|
|
1454
2056
|
}
|
|
1455
2057
|
let code;
|
|
1456
2058
|
try {
|
|
1457
2059
|
const out = await build2({
|
|
1458
|
-
entryPoints: [
|
|
2060
|
+
entryPoints: [path2],
|
|
1459
2061
|
bundle: true,
|
|
1460
2062
|
format: "esm",
|
|
1461
2063
|
platform: "neutral",
|
|
@@ -1469,12 +2071,12 @@ async function loadScene(path) {
|
|
|
1469
2071
|
code = out.outputFiles[0].text;
|
|
1470
2072
|
} catch (err) {
|
|
1471
2073
|
throw new Error(
|
|
1472
|
-
`failed to bundle ${
|
|
2074
|
+
`failed to bundle ${path2}:
|
|
1473
2075
|
${err instanceof Error ? err.message : String(err)}`
|
|
1474
2076
|
);
|
|
1475
2077
|
}
|
|
1476
2078
|
const mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
|
|
1477
|
-
if (!mod.default) throw new Error(`${
|
|
2079
|
+
if (!mod.default) throw new Error(`${path2} must default-export a scene`);
|
|
1478
2080
|
return mod.default;
|
|
1479
2081
|
}
|
|
1480
2082
|
var HERE, CORE_ENTRY;
|
|
@@ -1489,7 +2091,7 @@ var init_loadScene = __esm({
|
|
|
1489
2091
|
|
|
1490
2092
|
// ../render-cli/src/reframe.ts
|
|
1491
2093
|
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
1492
|
-
import { existsSync as
|
|
2094
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1493
2095
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
1494
2096
|
import { basename, isAbsolute as isAbsolute3, join as join6, resolve as resolve4 } from "node:path";
|
|
1495
2097
|
import { dirname as dirname7 } from "node:path";
|
|
@@ -1500,15 +2102,20 @@ var ROOT2 = PACKAGED ? resolve4(HERE2, "..") : resolve4(HERE2, "..", "..", "..")
|
|
|
1500
2102
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
1501
2103
|
var RENDER_CLI = PACKAGED ? join6(ROOT2, "dist", "cli.js") : join6(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
1502
2104
|
var ANALYZE = PACKAGED ? join6(ROOT2, "dist", "analyze.js") : join6(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
2105
|
+
var TRACE = PACKAGED ? join6(ROOT2, "dist", "trace-cli.js") : join6(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
1503
2106
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
1504
2107
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
1505
2108
|
|
|
1506
2109
|
usage:
|
|
1507
2110
|
${CMD} render <scene.ts|.json|.html> [--overlay edits.json]... [-o out.mp4] [--fps N] [--duration S] [--no-audio]
|
|
1508
2111
|
${CMD} batch <scene.ts> <data.json|csv> [-o outDir] [--overlay base.json]... [--concurrency N] [--fps N]
|
|
2112
|
+
${CMD} logo <logo.svg|brand-slug> ["Name"] [--motion <preset>] [--energy 0..1] [--seed N] [-o out.mp4]
|
|
2113
|
+
animate a logo into a sting (presets: draw-bloom, punch-in,
|
|
2114
|
+
rise-settle, slide-bank, reveal-orbit, spin-forge)
|
|
1509
2115
|
${CMD} preview open the scrub/edit UI (lists scenes in your directory)
|
|
1510
2116
|
${CMD} new <scene-name> scaffold <scene-name>.ts in your directory
|
|
1511
2117
|
${CMD} motion <mp4|framesDir> motion-profile a rendered clip
|
|
2118
|
+
${CMD} trace <ref.mp4> [--apply scene.ts] extract a video's motion structure \u2192 MotionSketch / timeline
|
|
1512
2119
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
1513
2120
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
1514
2121
|
`;
|
|
@@ -1531,9 +2138,9 @@ function run(cmd, args, opts = {}) {
|
|
|
1531
2138
|
});
|
|
1532
2139
|
let sawBrowserError = false;
|
|
1533
2140
|
proc.stderr.on("data", (d) => {
|
|
1534
|
-
const
|
|
1535
|
-
if (/Executable doesn't exist|browserType\.launch/.test(
|
|
1536
|
-
process.stderr.write(
|
|
2141
|
+
const text2 = d.toString();
|
|
2142
|
+
if (/Executable doesn't exist|browserType\.launch/.test(text2)) sawBrowserError = true;
|
|
2143
|
+
process.stderr.write(text2);
|
|
1537
2144
|
});
|
|
1538
2145
|
proc.on("close", (code) => {
|
|
1539
2146
|
if (code !== 0 && sawBrowserError) {
|
|
@@ -1614,7 +2221,7 @@ async function main() {
|
|
|
1614
2221
|
|
|
1615
2222
|
${USAGE}`);
|
|
1616
2223
|
const inputPath = userPath(input);
|
|
1617
|
-
if (!
|
|
2224
|
+
if (!existsSync4(inputPath)) fail(`no such file: ${inputPath}`);
|
|
1618
2225
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
1619
2226
|
if (!mode) {
|
|
1620
2227
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -1637,12 +2244,49 @@ ${USAGE}`);
|
|
|
1637
2244
|
await (PACKAGED ? run(process.execPath, [RENDER_CLI, mode, inputPath, ...outArgs]) : run("npx", ["tsx", RENDER_CLI, mode, inputPath, ...outArgs]))
|
|
1638
2245
|
);
|
|
1639
2246
|
}
|
|
2247
|
+
case "logo": {
|
|
2248
|
+
const positional = [];
|
|
2249
|
+
const flags = {};
|
|
2250
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2251
|
+
const a = rest[i];
|
|
2252
|
+
if (a.startsWith("--")) flags[a.slice(2)] = rest[++i] ?? "";
|
|
2253
|
+
else if (a === "-o") flags.o = rest[++i] ?? "";
|
|
2254
|
+
else positional.push(a);
|
|
2255
|
+
}
|
|
2256
|
+
const arg = positional[0];
|
|
2257
|
+
if (!arg) {
|
|
2258
|
+
fail(`usage: ${CMD} logo <logo.svg | brand-slug> ["Display Name"] [--motion <preset>] [--energy 0..1] [--speed n] [--intensity 0..1] [--from left|right|top|bottom] [--seed n] [-o out.mp4]`);
|
|
2259
|
+
}
|
|
2260
|
+
preflightFfmpeg();
|
|
2261
|
+
const { tmpdir: tmpdir4 } = await import("node:os");
|
|
2262
|
+
const { resolveLogo: resolveLogo2, buildLogoSting: buildLogoSting2 } = await Promise.resolve().then(() => (init_logoSting(), logoSting_exports));
|
|
2263
|
+
const num = (k) => flags[k] !== void 0 ? Number(flags[k]) : void 0;
|
|
2264
|
+
console.log(`loading logo: ${arg} \u2026`);
|
|
2265
|
+
const { data, slug } = await resolveLogo2(arg, positional[1], {
|
|
2266
|
+
motion: flags.motion,
|
|
2267
|
+
energy: num("energy"),
|
|
2268
|
+
speed: num("speed"),
|
|
2269
|
+
intensity: num("intensity"),
|
|
2270
|
+
from: flags.from,
|
|
2271
|
+
seed: num("seed")
|
|
2272
|
+
});
|
|
2273
|
+
const sceneIR = buildLogoSting2(data);
|
|
2274
|
+
const tmp = join6(tmpdir4(), `reframe-logo-${slug}-${process.pid}.json`);
|
|
2275
|
+
await writeFile5(tmp, JSON.stringify(sceneIR));
|
|
2276
|
+
const outBase = PACKAGED ? join6(USER_CWD, "out") : join6(ROOT2, "out");
|
|
2277
|
+
const out = flags.o ? userPath(flags.o) : join6(outBase, `logo-${slug}.mp4`);
|
|
2278
|
+
await mkdir4(dirname7(out), { recursive: true });
|
|
2279
|
+
console.log(`rendering ${data.name} (${data.paths.length} path${data.paths.length > 1 ? "s" : ""}, motion: ${data.motion ?? "reveal-orbit"}) \u2192 ${out}`);
|
|
2280
|
+
process.exit(
|
|
2281
|
+
await (PACKAGED ? run(process.execPath, [RENDER_CLI, "ir", tmp, "-o", out, "--no-audio"]) : run("npx", ["tsx", RENDER_CLI, "ir", tmp, "-o", out, "--no-audio"]))
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
1640
2284
|
case "batch": {
|
|
1641
2285
|
const [sceneArg, dataArg, ...flags] = rest;
|
|
1642
2286
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
1643
2287
|
const scenePath = userPath(sceneArg);
|
|
1644
2288
|
const dataPath = userPath(dataArg);
|
|
1645
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
2289
|
+
for (const p of [scenePath, dataPath]) if (!existsSync4(p)) fail(`no such file: ${p}`);
|
|
1646
2290
|
preflightFfmpeg();
|
|
1647
2291
|
let outDir = PACKAGED ? join6(USER_CWD, "out", "batch") : join6(ROOT2, "out", "batch");
|
|
1648
2292
|
let concurrency = 3;
|
|
@@ -1657,15 +2301,15 @@ ${USAGE}`);
|
|
|
1657
2301
|
}
|
|
1658
2302
|
const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
1659
2303
|
const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
|
|
1660
|
-
const { readFile:
|
|
1661
|
-
const
|
|
2304
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
2305
|
+
const scene2 = await loadScene2(scenePath);
|
|
1662
2306
|
const baseOverlays = await Promise.all(
|
|
1663
|
-
baseOverlayPaths.map(async (p) => JSON.parse(await
|
|
2307
|
+
baseOverlayPaths.map(async (p) => JSON.parse(await readFile6(p, "utf8")))
|
|
1664
2308
|
);
|
|
1665
2309
|
const rows = await loadRows2(dataPath);
|
|
1666
2310
|
if (rows.length === 0) fail(`${dataPath}: no data rows`);
|
|
1667
2311
|
console.log(`batch: ${rows.length} rows \xD7 ${concurrency} workers \u2192 ${outDir}`);
|
|
1668
|
-
const results = await runBatch2(
|
|
2312
|
+
const results = await runBatch2(scene2, rows, {
|
|
1669
2313
|
outDir,
|
|
1670
2314
|
baseOverlays,
|
|
1671
2315
|
concurrency,
|
|
@@ -1714,7 +2358,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1714
2358
|
const targetDir = inRepo ? join6(ROOT2, "examples", "scenes") : USER_CWD;
|
|
1715
2359
|
const target = join6(targetDir, `${name}.ts`);
|
|
1716
2360
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
1717
|
-
if (
|
|
2361
|
+
if (existsSync4(target)) fail(`${shown} already exists`);
|
|
1718
2362
|
const id = name.split("-")[0] ?? name;
|
|
1719
2363
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
1720
2364
|
console.log(`created ${shown}
|
|
@@ -1731,10 +2375,21 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1731
2375
|
await (PACKAGED ? run(process.execPath, [ANALYZE, userPath(input), ...rest.slice(1)]) : run("npx", ["tsx", ANALYZE, userPath(input), ...rest.slice(1)]))
|
|
1732
2376
|
);
|
|
1733
2377
|
}
|
|
2378
|
+
case "trace": {
|
|
2379
|
+
const input = rest[0];
|
|
2380
|
+
if (!input || input.startsWith("-")) fail(`usage: ${CMD} trace <ref.mp4> [--apply scene.ts] [-o out.json]`);
|
|
2381
|
+
preflightFfmpeg();
|
|
2382
|
+
const args = rest.slice(1).map(
|
|
2383
|
+
(a, i) => rest.slice(1)[i - 1] === "--apply" || rest.slice(1)[i - 1] === "-o" ? userPath(a) : a
|
|
2384
|
+
);
|
|
2385
|
+
process.exit(
|
|
2386
|
+
await (PACKAGED ? run(process.execPath, [TRACE, userPath(input), ...args]) : run("npx", ["tsx", TRACE, userPath(input), ...args]))
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
1734
2389
|
case "guide": {
|
|
1735
2390
|
const file = rest.includes("--regen") ? PACKAGED ? join6(ROOT2, "guides", "regen-contract.md") : join6(ROOT2, "docs", "regen-contract.md") : PACKAGED ? join6(ROOT2, "guides", "edsl-guide.md") : join6(ROOT2, "benchmark", "guides", "edsl-guide.md");
|
|
1736
|
-
const { readFile:
|
|
1737
|
-
process.stdout.write(await
|
|
2391
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
2392
|
+
process.stdout.write(await readFile6(file, "utf8"));
|
|
1738
2393
|
return;
|
|
1739
2394
|
}
|
|
1740
2395
|
case "demo":
|