reframe-video 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/sfx/LICENSE.md +1 -1
- package/assets/sfx/bong_001.ogg +0 -0
- package/assets/sfx/click_001.ogg +0 -0
- package/assets/sfx/confirmation_002.ogg +0 -0
- package/assets/sfx/confirmation_003.ogg +0 -0
- package/assets/sfx/confirmation_004.ogg +0 -0
- package/assets/sfx/glass_001.ogg +0 -0
- package/assets/sfx/maximize_001.ogg +0 -0
- package/assets/sfx/maximize_002.ogg +0 -0
- package/assets/sfx/maximize_005.ogg +0 -0
- package/assets/sfx/maximize_009.ogg +0 -0
- package/assets/sfx/open_001.ogg +0 -0
- package/assets/sfx/pluck_001.ogg +0 -0
- package/assets/sfx/pluck_002.ogg +0 -0
- package/assets/sfx/select_001.ogg +0 -0
- package/assets/sfx/select_002.ogg +0 -0
- package/assets/sfx/select_003.ogg +0 -0
- package/dist/bin.js +724 -131
- package/dist/browserEntry.js +130 -68
- package/dist/cli.js +445 -85
- package/dist/index.js +674 -86
- package/dist/labels.js +606 -0
- package/dist/renderer-canvas.js +15 -0
- package/dist/trace-cli.js +9 -9
- package/dist/types/audio.d.ts +9 -0
- package/dist/types/compile.d.ts +1 -0
- package/dist/types/compose.d.ts +18 -2
- package/dist/types/composeComposition.d.ts +27 -0
- package/dist/types/devicePreset.d.ts +65 -0
- package/dist/types/dsl.d.ts +12 -1
- package/dist/types/evaluate.d.ts +32 -0
- package/dist/types/index.d.ts +6 -3
- package/dist/types/ir.d.ts +68 -0
- package/dist/types/motionOps.d.ts +36 -0
- package/dist/types/path.d.ts +7 -3
- package/dist/types/validate.d.ts +4 -1
- package/guides/edsl-guide.md +2 -1
- package/package.json +1 -1
- package/preview/index.html +56 -3
- package/preview/src/main.ts +1132 -46
- package/preview/src/panel.ts +478 -8
- package/preview/src/store.ts +323 -6
package/dist/bin.js
CHANGED
|
@@ -42,7 +42,7 @@ function segCountOf(points, closed) {
|
|
|
42
42
|
if (n < 2) return 0;
|
|
43
43
|
return closed ? n : n - 1;
|
|
44
44
|
}
|
|
45
|
-
function pathPoint(points, closed, u) {
|
|
45
|
+
function pathPoint(points, closed, u, curviness = 1) {
|
|
46
46
|
const n = points.length;
|
|
47
47
|
if (n === 0) return [0, 0];
|
|
48
48
|
if (n === 1) return [points[0][0], points[0][1]];
|
|
@@ -51,19 +51,41 @@ function pathPoint(points, closed, u) {
|
|
|
51
51
|
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
52
52
|
const t2 = t * t;
|
|
53
53
|
const t3 = t2 * t;
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (curviness === 1) {
|
|
55
|
+
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);
|
|
56
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
57
|
+
}
|
|
58
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
59
|
+
const h10 = t3 - 2 * t2 + t;
|
|
60
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
61
|
+
const h11 = t3 - t2;
|
|
62
|
+
const k = curviness * 0.5;
|
|
63
|
+
const H = (a, b, c, d) => h00 * b + h10 * k * (c - a) + h01 * c + h11 * k * (d - b);
|
|
64
|
+
return [H(p0[0], p1[0], p2[0], p3[0]), H(p0[1], p1[1], p2[1], p3[1])];
|
|
56
65
|
}
|
|
57
|
-
function pathTangentAngle(points, closed, u) {
|
|
66
|
+
function pathTangentAngle(points, closed, u, curviness = 1) {
|
|
58
67
|
const n = points.length;
|
|
59
68
|
if (n < 2) return 0;
|
|
60
69
|
const segs = segCountOf(points, closed);
|
|
61
70
|
const { i, t } = locate(segs, u);
|
|
62
71
|
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
63
72
|
const t2 = t * t;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
let dx;
|
|
74
|
+
let dy;
|
|
75
|
+
if (curviness === 1) {
|
|
76
|
+
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);
|
|
77
|
+
dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
78
|
+
dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
79
|
+
} else {
|
|
80
|
+
const g00 = 6 * t2 - 6 * t;
|
|
81
|
+
const g10 = 3 * t2 - 4 * t + 1;
|
|
82
|
+
const g01 = -6 * t2 + 6 * t;
|
|
83
|
+
const g11 = 3 * t2 - 2 * t;
|
|
84
|
+
const k = curviness * 0.5;
|
|
85
|
+
const D = (a, b, c, e) => g00 * b + g10 * k * (c - a) + g01 * c + g11 * k * (e - b);
|
|
86
|
+
dx = D(p0[0], p1[0], p2[0], p3[0]);
|
|
87
|
+
dy = D(p0[1], p1[1], p2[1], p3[1]);
|
|
88
|
+
}
|
|
67
89
|
if (dx === 0 && dy === 0) return 0;
|
|
68
90
|
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
69
91
|
}
|
|
@@ -144,8 +166,8 @@ function compileScene(ir) {
|
|
|
144
166
|
const currentValue = (target, prop) => {
|
|
145
167
|
const v = current.get(key(target, prop));
|
|
146
168
|
if (v !== void 0) return v;
|
|
147
|
-
if (prop === "opacity" || prop === "scale" || prop === "progress") return 1;
|
|
148
|
-
if (prop === "rotation") return 0;
|
|
169
|
+
if (prop === "opacity" || prop === "scale" || prop === "progress" || prop === "scaleX" || prop === "scaleY") return 1;
|
|
170
|
+
if (prop === "rotation" || prop === "skewX" || prop === "skewY") return 0;
|
|
149
171
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
150
172
|
};
|
|
151
173
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
@@ -248,16 +270,17 @@ function compileScene(ir) {
|
|
|
248
270
|
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
249
271
|
const points = tl.points;
|
|
250
272
|
const closed = tl.closed ?? false;
|
|
273
|
+
const curviness = tl.curviness ?? 1;
|
|
251
274
|
const autoRotate = tl.autoRotate ?? false;
|
|
252
275
|
const rotateOffset = tl.rotateOffset ?? 0;
|
|
253
276
|
let list = motionPaths.get(tl.target);
|
|
254
277
|
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 } });
|
|
278
|
+
list.push({ t0: start, t1: start + duration, points, closed, curviness, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
256
279
|
if (points.length > 0) {
|
|
257
|
-
const [ex, ey] = pathPoint(points, closed, 1);
|
|
280
|
+
const [ex, ey] = pathPoint(points, closed, 1, curviness);
|
|
258
281
|
current.set(key(tl.target, "x"), ex);
|
|
259
282
|
current.set(key(tl.target, "y"), ey);
|
|
260
|
-
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
283
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1, curviness) + rotateOffset);
|
|
261
284
|
}
|
|
262
285
|
return start + duration;
|
|
263
286
|
}
|
|
@@ -322,7 +345,18 @@ function validateScene(ir) {
|
|
|
322
345
|
problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
|
|
323
346
|
}
|
|
324
347
|
nodeById.set(node.id, node);
|
|
325
|
-
if (node.type === "group")
|
|
348
|
+
if (node.type === "group") {
|
|
349
|
+
const clip = node.props.clip;
|
|
350
|
+
if (clip) {
|
|
351
|
+
if (clip.kind !== "rect" && clip.kind !== "ellipse") {
|
|
352
|
+
problems.push(`group "${node.id}" clip: unknown kind "${clip.kind}" \u2014 use "rect" or "ellipse"`);
|
|
353
|
+
}
|
|
354
|
+
if (!(clip.width > 0) || !(clip.height > 0)) {
|
|
355
|
+
problems.push(`group "${node.id}" clip: width and height must be > 0`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
collect(node.children);
|
|
359
|
+
}
|
|
326
360
|
}
|
|
327
361
|
};
|
|
328
362
|
collect(ir.nodes);
|
|
@@ -355,11 +389,11 @@ function validateScene(ir) {
|
|
|
355
389
|
);
|
|
356
390
|
}
|
|
357
391
|
const labels = /* @__PURE__ */ new Set();
|
|
358
|
-
const checkTimeline = (tl,
|
|
392
|
+
const checkTimeline = (tl, path2) => {
|
|
359
393
|
if ("label" in tl && tl.label !== void 0) {
|
|
360
394
|
if (labels.has(tl.label)) {
|
|
361
395
|
problems.push(
|
|
362
|
-
`${
|
|
396
|
+
`${path2}: duplicate timeline label "${tl.label}" \u2014 labels are overlay addresses and must be unique`
|
|
363
397
|
);
|
|
364
398
|
}
|
|
365
399
|
labels.add(tl.label);
|
|
@@ -367,63 +401,73 @@ function validateScene(ir) {
|
|
|
367
401
|
switch (tl.kind) {
|
|
368
402
|
case "seq":
|
|
369
403
|
case "par":
|
|
370
|
-
tl.children.forEach((c, i) => checkTimeline(c, `${
|
|
404
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.${tl.kind}[${i}]`));
|
|
371
405
|
break;
|
|
372
406
|
case "stagger":
|
|
373
|
-
if (tl.interval < 0) problems.push(`${
|
|
374
|
-
tl.children.forEach((c, i) => checkTimeline(c, `${
|
|
407
|
+
if (tl.interval < 0) problems.push(`${path2}: stagger interval must be >= 0`);
|
|
408
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.stagger[${i}]`));
|
|
375
409
|
break;
|
|
376
410
|
case "to":
|
|
377
411
|
if (!(tl.state in states)) {
|
|
378
412
|
problems.push(
|
|
379
|
-
`${
|
|
413
|
+
`${path2}: to("${tl.state}") references an undefined state \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
|
|
380
414
|
);
|
|
381
415
|
}
|
|
382
416
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
383
|
-
problems.push(`${
|
|
417
|
+
problems.push(`${path2}: to("${tl.state}") duration must be > 0`);
|
|
384
418
|
}
|
|
385
419
|
for (const id of tl.filter ?? []) {
|
|
386
|
-
if (!nodeById.has(id)) problems.push(`${
|
|
420
|
+
if (!nodeById.has(id)) problems.push(`${path2}: filter contains unknown node "${id}"`);
|
|
387
421
|
}
|
|
388
422
|
break;
|
|
389
423
|
case "tween":
|
|
390
|
-
checkProps(
|
|
424
|
+
checkProps(path2, tl.target, tl.props);
|
|
391
425
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
392
|
-
problems.push(`${
|
|
426
|
+
problems.push(`${path2}: tween duration must be > 0`);
|
|
393
427
|
}
|
|
394
428
|
break;
|
|
395
429
|
case "motionPath": {
|
|
396
430
|
const node = nodeById.get(tl.target);
|
|
397
431
|
if (!node) {
|
|
398
432
|
problems.push(
|
|
399
|
-
`${
|
|
433
|
+
`${path2}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
400
434
|
);
|
|
401
435
|
} else if (node.type === "line") {
|
|
402
|
-
problems.push(`${
|
|
436
|
+
problems.push(`${path2}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
|
|
403
437
|
}
|
|
404
|
-
if (tl.points.length < 1) problems.push(`${
|
|
438
|
+
if (tl.points.length < 1) problems.push(`${path2}: motionPath "${tl.target}" needs at least 1 point`);
|
|
405
439
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
406
|
-
problems.push(`${
|
|
440
|
+
problems.push(`${path2}: motionPath "${tl.target}" duration must be > 0`);
|
|
441
|
+
}
|
|
442
|
+
if (tl.curviness !== void 0 && tl.curviness < 0) {
|
|
443
|
+
problems.push(`${path2}: motionPath "${tl.target}" curviness must be >= 0`);
|
|
407
444
|
}
|
|
408
445
|
break;
|
|
409
446
|
}
|
|
410
447
|
case "wait":
|
|
411
|
-
if (tl.duration < 0) problems.push(`${
|
|
448
|
+
if (tl.duration < 0) problems.push(`${path2}: wait duration must be >= 0`);
|
|
412
449
|
break;
|
|
413
450
|
case "beat":
|
|
414
451
|
if (labels.has(tl.name)) {
|
|
415
452
|
problems.push(
|
|
416
|
-
`${
|
|
453
|
+
`${path2}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
|
|
417
454
|
);
|
|
418
455
|
}
|
|
419
456
|
labels.add(tl.name);
|
|
420
457
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
421
|
-
problems.push(`${
|
|
458
|
+
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
422
459
|
}
|
|
423
460
|
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
424
|
-
problems.push(`${
|
|
461
|
+
problems.push(`${path2}: beat "${tl.name}" scale must be > 0`);
|
|
425
462
|
}
|
|
426
|
-
|
|
463
|
+
for (const id of tl.nodes ?? []) {
|
|
464
|
+
if (!nodeById.has(id)) {
|
|
465
|
+
problems.push(
|
|
466
|
+
`${path2}: beat "${tl.name}" owns unknown node "${id}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.beat(${tl.name})[${i}]`));
|
|
427
471
|
break;
|
|
428
472
|
}
|
|
429
473
|
};
|
|
@@ -463,11 +507,40 @@ function validateScene(ir) {
|
|
|
463
507
|
}
|
|
464
508
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
465
509
|
}
|
|
466
|
-
|
|
510
|
+
function validateComposition(comp) {
|
|
511
|
+
const problems = [];
|
|
512
|
+
if (comp.scenes.length === 0) problems.push("composition has no scenes");
|
|
513
|
+
const seen = /* @__PURE__ */ new Set();
|
|
514
|
+
for (const [i, entry] of comp.scenes.entries()) {
|
|
515
|
+
const where = `scenes[${i}]`;
|
|
516
|
+
try {
|
|
517
|
+
validateScene(entry.scene);
|
|
518
|
+
} catch (err) {
|
|
519
|
+
if (err instanceof SceneValidationError) {
|
|
520
|
+
for (const p of err.problems) problems.push(`${where} (scene "${entry.scene.id}"): ${p}`);
|
|
521
|
+
} else throw err;
|
|
522
|
+
}
|
|
523
|
+
if (seen.has(entry.scene.id)) {
|
|
524
|
+
problems.push(`${where}: duplicate scene id "${entry.scene.id}" \u2014 scene ids must be unique in a composition`);
|
|
525
|
+
}
|
|
526
|
+
seen.add(entry.scene.id);
|
|
527
|
+
if (entry.transition !== void 0 && !TRANSITIONS.includes(entry.transition)) {
|
|
528
|
+
problems.push(`${where}: unknown transition "${entry.transition}" \u2014 valid: ${TRANSITIONS.join(", ")}`);
|
|
529
|
+
}
|
|
530
|
+
if (typeof entry.at === "string" && Number.isNaN(Number(entry.at))) {
|
|
531
|
+
problems.push(`${where}: "at" string "${entry.at}" is not a number (use "-0.5"/"+0.5" or a number)`);
|
|
532
|
+
}
|
|
533
|
+
if (typeof entry.at === "number" && entry.at < 0) {
|
|
534
|
+
problems.push(`${where}: absolute "at" must be >= 0`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
538
|
+
}
|
|
539
|
+
var COMMON_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
467
540
|
var init_validate = __esm({
|
|
468
541
|
"../core/src/validate.ts"() {
|
|
469
542
|
"use strict";
|
|
470
|
-
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "anchor"];
|
|
543
|
+
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor"];
|
|
471
544
|
PROPS_BY_TYPE = {
|
|
472
545
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
473
546
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
@@ -486,10 +559,56 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
486
559
|
}
|
|
487
560
|
problems;
|
|
488
561
|
};
|
|
562
|
+
TRANSITIONS = ["cut", "crossfade"];
|
|
489
563
|
}
|
|
490
564
|
});
|
|
491
565
|
|
|
492
566
|
// ../core/src/dsl.ts
|
|
567
|
+
function scene(input) {
|
|
568
|
+
const ir = { version: 1, ...input };
|
|
569
|
+
validateScene(ir);
|
|
570
|
+
if (ir.duration === void 0 && ir.timeline) {
|
|
571
|
+
ir.duration = compileScene(ir).duration;
|
|
572
|
+
}
|
|
573
|
+
return ir;
|
|
574
|
+
}
|
|
575
|
+
function rect(props) {
|
|
576
|
+
const { id, ...rest } = props;
|
|
577
|
+
return { type: "rect", id, props: rest };
|
|
578
|
+
}
|
|
579
|
+
function text(props) {
|
|
580
|
+
const { id, ...rest } = props;
|
|
581
|
+
return { type: "text", id, props: rest };
|
|
582
|
+
}
|
|
583
|
+
function path(props) {
|
|
584
|
+
const { id, ...rest } = props;
|
|
585
|
+
return { type: "path", id, props: rest };
|
|
586
|
+
}
|
|
587
|
+
function group(props, children) {
|
|
588
|
+
const { id, ...rest } = props;
|
|
589
|
+
return { type: "group", id, props: rest, children };
|
|
590
|
+
}
|
|
591
|
+
function seq(...children) {
|
|
592
|
+
return { kind: "seq", children };
|
|
593
|
+
}
|
|
594
|
+
function par(...children) {
|
|
595
|
+
return { kind: "par", children };
|
|
596
|
+
}
|
|
597
|
+
function stagger(interval, ...children) {
|
|
598
|
+
return { kind: "stagger", interval, children };
|
|
599
|
+
}
|
|
600
|
+
function beat(name, opts, children) {
|
|
601
|
+
return { kind: "beat", name, children, ...opts };
|
|
602
|
+
}
|
|
603
|
+
function tween(target, props, opts = {}) {
|
|
604
|
+
return { kind: "tween", target, props, ...opts };
|
|
605
|
+
}
|
|
606
|
+
function wait(duration, label) {
|
|
607
|
+
return { kind: "wait", duration, ...label !== void 0 && { label } };
|
|
608
|
+
}
|
|
609
|
+
function motionPath(target, points, opts = {}) {
|
|
610
|
+
return { kind: "motionPath", target, points, ...opts };
|
|
611
|
+
}
|
|
493
612
|
var init_dsl = __esm({
|
|
494
613
|
"../core/src/dsl.ts"() {
|
|
495
614
|
"use strict";
|
|
@@ -498,10 +617,27 @@ var init_dsl = __esm({
|
|
|
498
617
|
}
|
|
499
618
|
});
|
|
500
619
|
|
|
620
|
+
// ../core/src/composeComposition.ts
|
|
621
|
+
var init_composeComposition = __esm({
|
|
622
|
+
"../core/src/composeComposition.ts"() {
|
|
623
|
+
"use strict";
|
|
624
|
+
init_compile();
|
|
625
|
+
init_ir();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
501
629
|
// ../core/src/compose.ts
|
|
502
630
|
function composeScene(base, ...overlays) {
|
|
503
631
|
const ir = structuredClone(base);
|
|
504
632
|
const report = { applied: [], orphans: [], warnings: [] };
|
|
633
|
+
const baseNodeIds = /* @__PURE__ */ new Set();
|
|
634
|
+
const collectBase = (nodes) => {
|
|
635
|
+
for (const node of nodes) {
|
|
636
|
+
baseNodeIds.add(node.id);
|
|
637
|
+
if (node.type === "group") collectBase(node.children);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
collectBase(base.nodes);
|
|
505
641
|
overlays.forEach((overlay, index) => {
|
|
506
642
|
const layer = overlay.name ?? `overlay-${index}`;
|
|
507
643
|
if (overlay.target !== void 0 && overlay.target !== ir.id) {
|
|
@@ -509,12 +645,12 @@ function composeScene(base, ...overlays) {
|
|
|
509
645
|
`${layer}: authored against scene "${overlay.target}" but composing onto "${ir.id}"`
|
|
510
646
|
);
|
|
511
647
|
}
|
|
512
|
-
applyOverlay(ir, overlay, layer, report);
|
|
648
|
+
applyOverlay(ir, overlay, layer, report, baseNodeIds);
|
|
513
649
|
});
|
|
514
650
|
validateScene(ir);
|
|
515
651
|
return { ir, report };
|
|
516
652
|
}
|
|
517
|
-
function applyOverlay(ir, overlay, layer, report) {
|
|
653
|
+
function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
|
|
518
654
|
const nodeById = /* @__PURE__ */ new Map();
|
|
519
655
|
const collect = (nodes) => {
|
|
520
656
|
for (const node of nodes) {
|
|
@@ -629,7 +765,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
629
765
|
to: ["duration", "ease", "stagger"],
|
|
630
766
|
tween: ["duration", "ease"],
|
|
631
767
|
wait: ["duration"],
|
|
632
|
-
motionPath: ["points", "duration", "ease"],
|
|
768
|
+
motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
|
|
633
769
|
beat: ["at", "gap", "scale", "duration", "order"]
|
|
634
770
|
};
|
|
635
771
|
let timingPatched = false;
|
|
@@ -667,6 +803,49 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
667
803
|
nodeById.set(node.id, node);
|
|
668
804
|
applied(`addNodes.${node.id}`, "add-node");
|
|
669
805
|
}
|
|
806
|
+
for (const id of overlay.removeNodes ?? []) {
|
|
807
|
+
if (baseNodeIds.has(id)) {
|
|
808
|
+
orphan(
|
|
809
|
+
`removeNodes.${id}`,
|
|
810
|
+
`"${id}" is a base scene node \u2014 the scene owns it; hide it with opacity: 0 instead of removing`
|
|
811
|
+
);
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const index = ir.nodes.findIndex((n) => n.id === id);
|
|
815
|
+
if (index < 0) {
|
|
816
|
+
orphan(
|
|
817
|
+
`removeNodes.${id}`,
|
|
818
|
+
`unknown overlay-added node "${id}" \u2014 nothing to remove`
|
|
819
|
+
);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
ir.nodes.splice(index, 1);
|
|
823
|
+
nodeById.delete(id);
|
|
824
|
+
applied(`removeNodes.${id}`, "remove-node");
|
|
825
|
+
}
|
|
826
|
+
if (overlay.addTimeline && overlay.addTimeline.length > 0) {
|
|
827
|
+
const collectTargets = (tl, out) => {
|
|
828
|
+
if (tl.kind === "tween" || tl.kind === "motionPath") out.add(tl.target);
|
|
829
|
+
if ("children" in tl) tl.children.forEach((c) => collectTargets(c, out));
|
|
830
|
+
};
|
|
831
|
+
const valid = [];
|
|
832
|
+
overlay.addTimeline.forEach((frag, i) => {
|
|
833
|
+
const targets = /* @__PURE__ */ new Set();
|
|
834
|
+
collectTargets(frag, targets);
|
|
835
|
+
const missing = [...targets].filter((id) => !nodeById.has(id));
|
|
836
|
+
if (missing.length > 0) {
|
|
837
|
+
orphan(`addTimeline[${i}]`, `targets unknown node(s) ${missing.join(", ")} \u2014 known ids: ${knownIds()}`);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
valid.push(structuredClone(frag));
|
|
841
|
+
applied(`addTimeline[${i}]`, "add-timeline");
|
|
842
|
+
});
|
|
843
|
+
if (valid.length > 0) {
|
|
844
|
+
ir.timeline = ir.timeline ? { kind: "par", children: [ir.timeline, ...valid] } : valid.length === 1 ? valid[0] : { kind: "par", children: valid };
|
|
845
|
+
delete ir.duration;
|
|
846
|
+
ir.duration = compileScene(ir).duration;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
670
849
|
}
|
|
671
850
|
var SCENE_PATCHABLE;
|
|
672
851
|
var init_compose = __esm({
|
|
@@ -679,12 +858,197 @@ var init_compose = __esm({
|
|
|
679
858
|
});
|
|
680
859
|
|
|
681
860
|
// ../core/src/presets.ts
|
|
682
|
-
|
|
861
|
+
function makeRng(seed) {
|
|
862
|
+
let a = seed >>> 0 || 2654435769;
|
|
863
|
+
return () => {
|
|
864
|
+
a = a + 1831565813 | 0;
|
|
865
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
866
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
867
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function ctx(o) {
|
|
871
|
+
const rand = makeRng((o.seed ?? 0) + 1);
|
|
872
|
+
return {
|
|
873
|
+
e: clamp01(o.energy ?? 0.5),
|
|
874
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
875
|
+
it: clamp01(o.intensity ?? 0.5),
|
|
876
|
+
from: o.from,
|
|
877
|
+
rand,
|
|
878
|
+
jit: (amp) => (rand() - 0.5) * 2 * amp,
|
|
879
|
+
g: o.target.group,
|
|
880
|
+
cx: o.target.center[0],
|
|
881
|
+
cy: o.target.center[1],
|
|
882
|
+
s: o.target.baseScale,
|
|
883
|
+
fills: o.target.fills,
|
|
884
|
+
inks: o.target.inks
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function settleEase(e) {
|
|
888
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
889
|
+
}
|
|
890
|
+
function fromVec(from, dist) {
|
|
891
|
+
switch (from) {
|
|
892
|
+
case "left":
|
|
893
|
+
return [-dist, 0];
|
|
894
|
+
case "right":
|
|
895
|
+
return [dist, 0];
|
|
896
|
+
case "top":
|
|
897
|
+
return [0, -dist];
|
|
898
|
+
default:
|
|
899
|
+
return [0, dist];
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
903
|
+
return stagger(
|
|
904
|
+
gap / c.sp,
|
|
905
|
+
...c.fills.map(
|
|
906
|
+
(id, i) => tween(id, { opacity: 1 }, { duration: dur(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
907
|
+
)
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
function drawInks(c) {
|
|
911
|
+
return stagger(
|
|
912
|
+
0.15 / c.sp,
|
|
913
|
+
...c.inks.map(
|
|
914
|
+
(id, i) => tween(id, { progress: 1 }, { duration: dur(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
915
|
+
)
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
function motionPreset(name, opts) {
|
|
919
|
+
const c = ctx(opts);
|
|
920
|
+
switch (name) {
|
|
921
|
+
case "draw-bloom":
|
|
922
|
+
return beat("draw-bloom", {}, [
|
|
923
|
+
drawInks(c),
|
|
924
|
+
fadeFills(c, 0.45),
|
|
925
|
+
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
926
|
+
]);
|
|
927
|
+
case "punch-in": {
|
|
928
|
+
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
929
|
+
return beat("punch-in", {}, [
|
|
930
|
+
par(
|
|
931
|
+
fadeFills(c, 0.25),
|
|
932
|
+
seq(
|
|
933
|
+
tween(c.g, { scale: peak }, { duration: dur(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
934
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.5, c.sp), ease: settleEase(c.e) })
|
|
935
|
+
)
|
|
936
|
+
)
|
|
937
|
+
]);
|
|
938
|
+
}
|
|
939
|
+
case "rise-settle": {
|
|
940
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
941
|
+
const dist = (220 + 260 * c.it) * es;
|
|
942
|
+
const [dx, dy] = fromVec(c.from ?? "bottom", dist);
|
|
943
|
+
const jx = c.jit(110);
|
|
944
|
+
return beat("rise-settle", {}, [
|
|
945
|
+
par(
|
|
946
|
+
motionPath(
|
|
947
|
+
c.g,
|
|
948
|
+
[
|
|
949
|
+
[c.cx + dx + jx, c.cy + dy],
|
|
950
|
+
[c.cx + dx * 0.4 - jx * 0.6, c.cy + dy * 0.4],
|
|
951
|
+
[c.cx, c.cy]
|
|
952
|
+
],
|
|
953
|
+
{ duration: dur(1.1, c.sp), ease: settleEase(c.e), label: "rise" }
|
|
954
|
+
),
|
|
955
|
+
fadeFills(c, 0.4)
|
|
956
|
+
)
|
|
957
|
+
]);
|
|
958
|
+
}
|
|
959
|
+
case "slide-bank": {
|
|
960
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
961
|
+
const dist = (420 + 240 * c.it) * es;
|
|
962
|
+
const [dx, dy] = fromVec(c.from ?? "left", dist);
|
|
963
|
+
const arc = c.jit(140);
|
|
964
|
+
const midx = c.jit(120);
|
|
965
|
+
const move = dur(1.2, c.sp);
|
|
966
|
+
return beat("slide-bank", {}, [
|
|
967
|
+
par(
|
|
968
|
+
motionPath(
|
|
969
|
+
c.g,
|
|
970
|
+
[
|
|
971
|
+
[c.cx + dx, c.cy + dy],
|
|
972
|
+
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
973
|
+
[c.cx, c.cy]
|
|
974
|
+
],
|
|
975
|
+
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
976
|
+
),
|
|
977
|
+
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
978
|
+
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur(0.5, c.sp), ease: "easeOutCubic" })),
|
|
979
|
+
fadeFills(c, 0.4)
|
|
980
|
+
)
|
|
981
|
+
]);
|
|
982
|
+
}
|
|
983
|
+
case "reveal-orbit": {
|
|
984
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
985
|
+
const orbit = (180 + 160 * c.it) * es;
|
|
986
|
+
const jx = c.jit(0.4);
|
|
987
|
+
const jy = c.jit(0.4);
|
|
988
|
+
return beat("reveal-orbit", {}, [
|
|
989
|
+
drawInks(c),
|
|
990
|
+
fadeFills(c, 0.45),
|
|
991
|
+
par(
|
|
992
|
+
motionPath(
|
|
993
|
+
c.g,
|
|
994
|
+
[
|
|
995
|
+
[c.cx, c.cy],
|
|
996
|
+
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
997
|
+
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
998
|
+
[c.cx, c.cy]
|
|
999
|
+
],
|
|
1000
|
+
{ duration: dur(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
1001
|
+
),
|
|
1002
|
+
seq(
|
|
1003
|
+
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur(0.85, c.sp), ease: "easeOutBack" }),
|
|
1004
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.85, c.sp), ease: "easeInOutQuad" })
|
|
1005
|
+
)
|
|
1006
|
+
)
|
|
1007
|
+
]);
|
|
1008
|
+
}
|
|
1009
|
+
case "spin-forge": {
|
|
1010
|
+
const turns = 1 + Math.round(c.it);
|
|
1011
|
+
const dir = c.rand() < 0.5 ? -1 : 1;
|
|
1012
|
+
const startRot = dir * 360 * turns;
|
|
1013
|
+
const peak = c.s * (1 + 0.05 + 0.2 * c.e);
|
|
1014
|
+
return beat("spin-forge", {}, [
|
|
1015
|
+
par(
|
|
1016
|
+
seq(
|
|
1017
|
+
tween(c.g, { scale: c.s * 0.2, rotation: startRot }, { duration: SET }),
|
|
1018
|
+
// establish (invisible)
|
|
1019
|
+
tween(c.g, { scale: peak, rotation: 0 }, { duration: dur(0.9, c.sp), ease: "easeOutBack", label: "spin" }),
|
|
1020
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.3, c.sp), ease: "easeInOutQuad" })
|
|
1021
|
+
),
|
|
1022
|
+
seq(wait(SET), fadeFills(c, 0.3))
|
|
1023
|
+
)
|
|
1024
|
+
]);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
var clamp01, SET, dur;
|
|
683
1029
|
var init_presets = __esm({
|
|
684
1030
|
"../core/src/presets.ts"() {
|
|
685
1031
|
"use strict";
|
|
686
1032
|
init_dsl();
|
|
1033
|
+
clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
687
1034
|
SET = 1 / 120;
|
|
1035
|
+
dur = (base, sp) => base / sp;
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
// ../core/src/devicePreset.ts
|
|
1040
|
+
var init_devicePreset = __esm({
|
|
1041
|
+
"../core/src/devicePreset.ts"() {
|
|
1042
|
+
"use strict";
|
|
1043
|
+
init_dsl();
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// ../core/src/motionOps.ts
|
|
1048
|
+
var init_motionOps = __esm({
|
|
1049
|
+
"../core/src/motionOps.ts"() {
|
|
1050
|
+
"use strict";
|
|
1051
|
+
init_dsl();
|
|
688
1052
|
}
|
|
689
1053
|
});
|
|
690
1054
|
|
|
@@ -724,6 +1088,15 @@ function resolveAudioPlan(compiled) {
|
|
|
724
1088
|
});
|
|
725
1089
|
}
|
|
726
1090
|
cues.sort((a, b) => a.t - b.t);
|
|
1091
|
+
return {
|
|
1092
|
+
duration,
|
|
1093
|
+
bgm: resolveBgm(audio.bgm),
|
|
1094
|
+
cues,
|
|
1095
|
+
duckWindows: mergeDuckWindows(cues, duration),
|
|
1096
|
+
warnings
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
function mergeDuckWindows(cues, duration) {
|
|
727
1100
|
const duckWindows = [];
|
|
728
1101
|
for (const cue of cues) {
|
|
729
1102
|
const window2 = { t0: cue.t, t1: Math.min(duration, cue.t + cue.duration) };
|
|
@@ -731,23 +1104,22 @@ function resolveAudioPlan(compiled) {
|
|
|
731
1104
|
if (last && window2.t0 <= last.t1 + 0.1) last.t1 = Math.max(last.t1, window2.t1);
|
|
732
1105
|
else duckWindows.push(window2);
|
|
733
1106
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
return { duration, bgm, cues, duckWindows, warnings };
|
|
1107
|
+
return duckWindows;
|
|
1108
|
+
}
|
|
1109
|
+
function resolveBgm(b) {
|
|
1110
|
+
if (!b) return null;
|
|
1111
|
+
const duck = b.duck === false ? null : {
|
|
1112
|
+
depth: b.duck?.depth ?? 0.5,
|
|
1113
|
+
attack: b.duck?.attack ?? 0.05,
|
|
1114
|
+
release: b.duck?.release ?? 0.25
|
|
1115
|
+
};
|
|
1116
|
+
return {
|
|
1117
|
+
source: b.file ? { kind: "file", path: b.file } : { kind: "synth", name: b.synth ?? "ambient-pad" },
|
|
1118
|
+
gain: b.gain ?? 0.5,
|
|
1119
|
+
fadeIn: b.fadeIn ?? 0,
|
|
1120
|
+
fadeOut: b.fadeOut ?? 0,
|
|
1121
|
+
duck
|
|
1122
|
+
};
|
|
751
1123
|
}
|
|
752
1124
|
var SFX_DURATION, FILE_CUE_DURATION;
|
|
753
1125
|
var init_audio = __esm({
|
|
@@ -884,10 +1256,13 @@ var init_src = __esm({
|
|
|
884
1256
|
init_ir();
|
|
885
1257
|
init_dsl();
|
|
886
1258
|
init_validate();
|
|
1259
|
+
init_composeComposition();
|
|
887
1260
|
init_compose();
|
|
888
1261
|
init_compile();
|
|
889
1262
|
init_path();
|
|
890
1263
|
init_presets();
|
|
1264
|
+
init_devicePreset();
|
|
1265
|
+
init_motionOps();
|
|
891
1266
|
init_audio();
|
|
892
1267
|
init_evaluate();
|
|
893
1268
|
init_interpolate();
|
|
@@ -897,6 +1272,152 @@ var init_src = __esm({
|
|
|
897
1272
|
}
|
|
898
1273
|
});
|
|
899
1274
|
|
|
1275
|
+
// ../render-cli/src/logoSting.ts
|
|
1276
|
+
var logoSting_exports = {};
|
|
1277
|
+
__export(logoSting_exports, {
|
|
1278
|
+
LOGO_PRESETS: () => LOGO_PRESETS,
|
|
1279
|
+
buildLogoSting: () => buildLogoSting,
|
|
1280
|
+
resolveLogo: () => resolveLogo
|
|
1281
|
+
});
|
|
1282
|
+
import { existsSync } from "node:fs";
|
|
1283
|
+
import { readFile } from "node:fs/promises";
|
|
1284
|
+
async function loadSvg(arg) {
|
|
1285
|
+
if (existsSync(arg)) {
|
|
1286
|
+
return { svg: await readFile(arg, "utf8"), name: arg.split("/").pop().replace(/\.svg$/i, "") };
|
|
1287
|
+
}
|
|
1288
|
+
const slug = arg.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1289
|
+
const r = await fetch(`https://cdn.simpleicons.org/${slug}`);
|
|
1290
|
+
if (!r.ok) throw new Error(`no local file "${arg}", and simple-icons has no "${slug}" (${r.status})`);
|
|
1291
|
+
return { svg: await r.text(), name: arg };
|
|
1292
|
+
}
|
|
1293
|
+
function tooDark(hex) {
|
|
1294
|
+
const h = hex.replace("#", "");
|
|
1295
|
+
const n = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
1296
|
+
const r = parseInt(n.slice(0, 2), 16);
|
|
1297
|
+
const g = parseInt(n.slice(2, 4), 16);
|
|
1298
|
+
const b = parseInt(n.slice(4, 6), 16);
|
|
1299
|
+
return 0.299 * r + 0.587 * g + 0.114 * b < 40;
|
|
1300
|
+
}
|
|
1301
|
+
function parseSvg(svg) {
|
|
1302
|
+
let viewBox = { minX: 0, minY: 0, w: 100, h: 100 };
|
|
1303
|
+
const vb = svg.match(/viewBox\s*=\s*"([\d.\-\s]+)"/i);
|
|
1304
|
+
if (vb) {
|
|
1305
|
+
const [a, b, c, d] = vb[1].trim().split(/\s+/).map(Number);
|
|
1306
|
+
viewBox = { minX: a, minY: b, w: c, h: d };
|
|
1307
|
+
} else {
|
|
1308
|
+
const w = svg.match(/\bwidth\s*=\s*"([\d.]+)/i);
|
|
1309
|
+
const h = svg.match(/\bheight\s*=\s*"([\d.]+)/i);
|
|
1310
|
+
if (w && h) viewBox = { minX: 0, minY: 0, w: +w[1], h: +h[1] };
|
|
1311
|
+
}
|
|
1312
|
+
const rootFill = svg.match(/<svg[^>]*\bfill\s*=\s*"(#[0-9a-fA-F]{3,8})"/)?.[1];
|
|
1313
|
+
const fallback = rootFill && !tooDark(rootFill) ? rootFill : "#E6EDF3";
|
|
1314
|
+
const paths = [];
|
|
1315
|
+
const re = /<path\b[^>]*>/g;
|
|
1316
|
+
let m;
|
|
1317
|
+
while (m = re.exec(svg)) {
|
|
1318
|
+
const tag = m[0];
|
|
1319
|
+
const d = tag.match(/\bd\s*=\s*"([^"]+)"/)?.[1];
|
|
1320
|
+
if (!d) continue;
|
|
1321
|
+
let fill = tag.match(/\bfill\s*=\s*"(#[0-9a-fA-F]{3,8})"/)?.[1] ?? fallback;
|
|
1322
|
+
if (tooDark(fill)) fill = fallback;
|
|
1323
|
+
paths.push({ d, fill });
|
|
1324
|
+
}
|
|
1325
|
+
return { paths, viewBox };
|
|
1326
|
+
}
|
|
1327
|
+
async function resolveLogo(arg, displayName, opts) {
|
|
1328
|
+
if (opts.motion && !LOGO_PRESETS.includes(opts.motion)) {
|
|
1329
|
+
throw new Error(`unknown --motion "${opts.motion}". options: ${LOGO_PRESETS.join(", ")}`);
|
|
1330
|
+
}
|
|
1331
|
+
const { svg, name } = await loadSvg(arg);
|
|
1332
|
+
const { paths, viewBox } = parseSvg(svg);
|
|
1333
|
+
if (paths.length === 0) throw new Error("no <path> elements found \u2014 logo stings need a path-based SVG");
|
|
1334
|
+
const from = FROMS.includes(opts.from) ? opts.from : void 0;
|
|
1335
|
+
const data = {
|
|
1336
|
+
name: displayName ?? titleCase(name),
|
|
1337
|
+
paths,
|
|
1338
|
+
viewBox,
|
|
1339
|
+
...opts.motion && { motion: opts.motion },
|
|
1340
|
+
...opts.energy !== void 0 && { energy: opts.energy },
|
|
1341
|
+
...opts.speed !== void 0 && { speed: opts.speed },
|
|
1342
|
+
...opts.intensity !== void 0 && { intensity: opts.intensity },
|
|
1343
|
+
...from && { from },
|
|
1344
|
+
...opts.seed !== void 0 && { seed: opts.seed }
|
|
1345
|
+
};
|
|
1346
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "logo";
|
|
1347
|
+
return { data, slug };
|
|
1348
|
+
}
|
|
1349
|
+
function buildLogoSting(d) {
|
|
1350
|
+
const W = 1080;
|
|
1351
|
+
const H = 1080;
|
|
1352
|
+
const CX = 540;
|
|
1353
|
+
const CY = 500;
|
|
1354
|
+
const vcx = d.viewBox.minX + d.viewBox.w / 2;
|
|
1355
|
+
const vcy = d.viewBox.minY + d.viewBox.h / 2;
|
|
1356
|
+
const fit = LOGO_PX / Math.max(d.viewBox.w, d.viewBox.h);
|
|
1357
|
+
const sw = 2.2 / fit;
|
|
1358
|
+
const fills = d.paths.map(
|
|
1359
|
+
(p, i) => path({ id: `fill-${i}`, d: p.d, originX: vcx, originY: vcy, x: 0, y: 0, fill: p.fill, opacity: 0 })
|
|
1360
|
+
);
|
|
1361
|
+
const inks = d.paths.map(
|
|
1362
|
+
(p, i) => path({ id: `ink-${i}`, d: p.d, originX: vcx, originY: vcy, x: 0, y: 0, stroke: p.fill, strokeWidth: sw, progress: 0 })
|
|
1363
|
+
);
|
|
1364
|
+
const rig = {
|
|
1365
|
+
group: "logo",
|
|
1366
|
+
center: [CX, CY],
|
|
1367
|
+
baseScale: fit,
|
|
1368
|
+
fills: fills.map((n) => n.id),
|
|
1369
|
+
inks: inks.map((n) => n.id)
|
|
1370
|
+
};
|
|
1371
|
+
return scene({
|
|
1372
|
+
id: "logo-sting",
|
|
1373
|
+
size: { width: W, height: H },
|
|
1374
|
+
fps: 30,
|
|
1375
|
+
background: BG,
|
|
1376
|
+
nodes: [
|
|
1377
|
+
rect({ id: "bg", x: 0, y: 0, width: W, height: H, fill: BG }),
|
|
1378
|
+
group({ id: "logo", x: CX, y: CY, scale: fit }, [...fills, ...inks]),
|
|
1379
|
+
text({ id: "word", x: CX, y: 905, anchor: "center", content: d.name, fontFamily: "Inter", fontSize: 56, fontWeight: 800, fill: FG, opacity: 0 }),
|
|
1380
|
+
text({ id: "made", x: CX, y: 968, anchor: "center", content: "made with reframe", fontFamily: "Inter", fontSize: 20, fill: MUTED, opacity: 0 })
|
|
1381
|
+
],
|
|
1382
|
+
timeline: seq(
|
|
1383
|
+
motionPreset(d.motion ?? "reveal-orbit", {
|
|
1384
|
+
target: rig,
|
|
1385
|
+
...d.energy !== void 0 && { energy: d.energy },
|
|
1386
|
+
...d.speed !== void 0 && { speed: d.speed },
|
|
1387
|
+
...d.intensity !== void 0 && { intensity: d.intensity },
|
|
1388
|
+
...d.from !== void 0 && { from: d.from },
|
|
1389
|
+
...d.seed !== void 0 && { seed: d.seed }
|
|
1390
|
+
}),
|
|
1391
|
+
par(
|
|
1392
|
+
tween("word", { opacity: 1 }, { duration: 0.5, ease: "easeOutQuad", label: "word" }),
|
|
1393
|
+
seq(wait(0.2), tween("made", { opacity: 1 }, { duration: 0.5, ease: "easeOutQuad" }))
|
|
1394
|
+
),
|
|
1395
|
+
wait(0.8, "hold")
|
|
1396
|
+
)
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
var LOGO_PRESETS, titleCase, FROMS, BG, FG, MUTED, LOGO_PX;
|
|
1400
|
+
var init_logoSting = __esm({
|
|
1401
|
+
"../render-cli/src/logoSting.ts"() {
|
|
1402
|
+
"use strict";
|
|
1403
|
+
init_src();
|
|
1404
|
+
LOGO_PRESETS = [
|
|
1405
|
+
"draw-bloom",
|
|
1406
|
+
"punch-in",
|
|
1407
|
+
"rise-settle",
|
|
1408
|
+
"slide-bank",
|
|
1409
|
+
"reveal-orbit",
|
|
1410
|
+
"spin-forge"
|
|
1411
|
+
];
|
|
1412
|
+
titleCase = (s) => s.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
|
|
1413
|
+
FROMS = ["left", "right", "top", "bottom"];
|
|
1414
|
+
BG = "#0D1117";
|
|
1415
|
+
FG = "#E6EDF3";
|
|
1416
|
+
MUTED = "#8B949E";
|
|
1417
|
+
LOGO_PX = 520;
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
|
|
900
1421
|
// ../render-cli/src/audio/wav.ts
|
|
901
1422
|
function encodeWavMono16(samples, sampleRate = SAMPLE_RATE) {
|
|
902
1423
|
const dataBytes = samples.length * 2;
|
|
@@ -941,52 +1462,52 @@ function buffer(duration) {
|
|
|
941
1462
|
return { out: new Float32Array(n), n };
|
|
942
1463
|
}
|
|
943
1464
|
function whoosh(seed) {
|
|
944
|
-
const
|
|
945
|
-
const { out, n } = buffer(
|
|
1465
|
+
const dur2 = 0.35;
|
|
1466
|
+
const { out, n } = buffer(dur2);
|
|
946
1467
|
let lp = 0;
|
|
947
1468
|
let lp2 = 0;
|
|
948
1469
|
for (let i = 0; i < n; i++) {
|
|
949
1470
|
const t = i / SAMPLE_RATE;
|
|
950
|
-
const u = t /
|
|
1471
|
+
const u = t / dur2;
|
|
951
1472
|
const center = 1200 * Math.pow(300 / 1200, u);
|
|
952
1473
|
const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
|
|
953
1474
|
lp += alpha * (noise(i, seed) - lp);
|
|
954
1475
|
lp2 += alpha * 0.5 * (lp - lp2);
|
|
955
|
-
const env = u < 0.3 ? u / 0.3 : expDecay(t - 0.3 *
|
|
1476
|
+
const env = u < 0.3 ? u / 0.3 : expDecay(t - 0.3 * dur2, dur2 * 0.7, 4);
|
|
956
1477
|
out[i] = (lp - lp2) * env * 2.2;
|
|
957
1478
|
}
|
|
958
1479
|
return out;
|
|
959
1480
|
}
|
|
960
1481
|
function pop(seed) {
|
|
961
|
-
const
|
|
962
|
-
const { out, n } = buffer(
|
|
1482
|
+
const dur2 = 0.12;
|
|
1483
|
+
const { out, n } = buffer(dur2);
|
|
963
1484
|
let phase = 0;
|
|
964
1485
|
for (let i = 0; i < n; i++) {
|
|
965
1486
|
const t = i / SAMPLE_RATE;
|
|
966
1487
|
const freq = 600 * Math.pow(150 / 600, t / 0.08);
|
|
967
1488
|
phase += TAU * freq / SAMPLE_RATE;
|
|
968
1489
|
const transient = t < 2e-3 ? noise(i, seed) * 0.5 : 0;
|
|
969
|
-
out[i] = (Math.sin(phase) + transient) * expDecay(t,
|
|
1490
|
+
out[i] = (Math.sin(phase) + transient) * expDecay(t, dur2, 6) * 0.8;
|
|
970
1491
|
}
|
|
971
1492
|
return out;
|
|
972
1493
|
}
|
|
973
1494
|
function tick(seed) {
|
|
974
|
-
const
|
|
975
|
-
const { out, n } = buffer(
|
|
1495
|
+
const dur2 = 0.03;
|
|
1496
|
+
const { out, n } = buffer(dur2);
|
|
976
1497
|
for (let i = 0; i < n; i++) {
|
|
977
1498
|
const t = i / SAMPLE_RATE;
|
|
978
1499
|
const sine = t < 4e-3 ? Math.sin(TAU * 4e3 * t) : 0;
|
|
979
|
-
out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t,
|
|
1500
|
+
out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur2, 8);
|
|
980
1501
|
}
|
|
981
1502
|
return out;
|
|
982
1503
|
}
|
|
983
1504
|
function rise(seed) {
|
|
984
|
-
const
|
|
985
|
-
const { out, n } = buffer(
|
|
1505
|
+
const dur2 = 0.5;
|
|
1506
|
+
const { out, n } = buffer(dur2);
|
|
986
1507
|
let phase = 0;
|
|
987
1508
|
for (let i = 0; i < n; i++) {
|
|
988
1509
|
const t = i / SAMPLE_RATE;
|
|
989
|
-
const u = t /
|
|
1510
|
+
const u = t / dur2;
|
|
990
1511
|
const freq = 220 * Math.pow(880 / 220, u);
|
|
991
1512
|
phase += TAU * freq / SAMPLE_RATE;
|
|
992
1513
|
const env = Math.sin(Math.PI * Math.min(1, u * 1.05)) ** 1.5;
|
|
@@ -995,8 +1516,8 @@ function rise(seed) {
|
|
|
995
1516
|
return out;
|
|
996
1517
|
}
|
|
997
1518
|
function shimmer(seed) {
|
|
998
|
-
const
|
|
999
|
-
const { out, n } = buffer(
|
|
1519
|
+
const dur2 = 0.9;
|
|
1520
|
+
const { out, n } = buffer(dur2);
|
|
1000
1521
|
const partials = Array.from({ length: 5 }, (_, p) => ({
|
|
1001
1522
|
freq: 2e3 + hash01(p, seed + 7) * 2e3,
|
|
1002
1523
|
am: 0.5 + hash01(p, seed + 8) * 1.5,
|
|
@@ -1004,7 +1525,7 @@ function shimmer(seed) {
|
|
|
1004
1525
|
}));
|
|
1005
1526
|
for (let i = 0; i < n; i++) {
|
|
1006
1527
|
const t = i / SAMPLE_RATE;
|
|
1007
|
-
const u = t /
|
|
1528
|
+
const u = t / dur2;
|
|
1008
1529
|
const env = Math.sin(Math.PI * u) ** 1.2;
|
|
1009
1530
|
let s = 0;
|
|
1010
1531
|
for (const part of partials) {
|
|
@@ -1015,8 +1536,8 @@ function shimmer(seed) {
|
|
|
1015
1536
|
return out;
|
|
1016
1537
|
}
|
|
1017
1538
|
function thud(seed) {
|
|
1018
|
-
const
|
|
1019
|
-
const { out, n } = buffer(
|
|
1539
|
+
const dur2 = 0.25;
|
|
1540
|
+
const { out, n } = buffer(dur2);
|
|
1020
1541
|
let phase = 0;
|
|
1021
1542
|
let lp = 0;
|
|
1022
1543
|
for (let i = 0; i < n; i++) {
|
|
@@ -1025,7 +1546,7 @@ function thud(seed) {
|
|
|
1025
1546
|
phase += TAU * freq / SAMPLE_RATE;
|
|
1026
1547
|
lp += 0.02 * (noise(i, seed) - lp);
|
|
1027
1548
|
const attack = t < 0.01 ? lp * 3 : 0;
|
|
1028
|
-
out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t,
|
|
1549
|
+
out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur2, 5);
|
|
1029
1550
|
}
|
|
1030
1551
|
return out;
|
|
1031
1552
|
}
|
|
@@ -1060,7 +1581,7 @@ var init_synth = __esm({
|
|
|
1060
1581
|
init_wav();
|
|
1061
1582
|
noise = (n, seed) => hash01(n, seed) * 2 - 1;
|
|
1062
1583
|
TAU = Math.PI * 2;
|
|
1063
|
-
expDecay = (t,
|
|
1584
|
+
expDecay = (t, dur2, k = 5) => Math.exp(-k * t / dur2);
|
|
1064
1585
|
RECIPES = {
|
|
1065
1586
|
whoosh,
|
|
1066
1587
|
pop,
|
|
@@ -1074,26 +1595,26 @@ var init_synth = __esm({
|
|
|
1074
1595
|
|
|
1075
1596
|
// ../render-cli/src/audio/sfx.ts
|
|
1076
1597
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
1077
|
-
import { existsSync } from "node:fs";
|
|
1598
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1078
1599
|
import { tmpdir } from "node:os";
|
|
1079
1600
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
1080
1601
|
import { fileURLToPath } from "node:url";
|
|
1081
|
-
function fnv1a(
|
|
1602
|
+
function fnv1a(text2) {
|
|
1082
1603
|
let h = 2166136261;
|
|
1083
|
-
for (let i = 0; i <
|
|
1084
|
-
h ^=
|
|
1604
|
+
for (let i = 0; i < text2.length; i++) {
|
|
1605
|
+
h ^= text2.charCodeAt(i);
|
|
1085
1606
|
h = Math.imul(h, 16777619);
|
|
1086
1607
|
}
|
|
1087
1608
|
return (h >>> 0).toString(16);
|
|
1088
1609
|
}
|
|
1089
1610
|
async function writeCached(key2, make) {
|
|
1090
|
-
const
|
|
1091
|
-
if (
|
|
1611
|
+
const path2 = join(CACHE, `${key2}.wav`);
|
|
1612
|
+
if (existsSync2(path2)) return path2;
|
|
1092
1613
|
await mkdir(CACHE, { recursive: true });
|
|
1093
|
-
const temp = `${
|
|
1614
|
+
const temp = `${path2}.${process.pid}.${fnv1a(String(performance.now()))}.tmp`;
|
|
1094
1615
|
await writeFile(temp, encodeWavMono16(make()));
|
|
1095
|
-
await rename(temp,
|
|
1096
|
-
return
|
|
1616
|
+
await rename(temp, path2);
|
|
1617
|
+
return path2;
|
|
1097
1618
|
}
|
|
1098
1619
|
async function resolveCueFile(cue, sceneDir) {
|
|
1099
1620
|
if (cue.source.kind === "file") {
|
|
@@ -1103,14 +1624,14 @@ async function resolveCueFile(cue, sceneDir) {
|
|
|
1103
1624
|
resolve(sceneDir, p),
|
|
1104
1625
|
join(VENDORED, p)
|
|
1105
1626
|
]) {
|
|
1106
|
-
if (candidate &&
|
|
1627
|
+
if (candidate && existsSync2(candidate)) return candidate;
|
|
1107
1628
|
}
|
|
1108
1629
|
throw new Error(
|
|
1109
1630
|
`audio cue file "${p}" not found (tried absolute, scene-relative, assets/sfx/)`
|
|
1110
1631
|
);
|
|
1111
1632
|
}
|
|
1112
1633
|
const vendored = join(VENDORED, `${cue.source.name}.wav`);
|
|
1113
|
-
if (
|
|
1634
|
+
if (existsSync2(vendored)) return vendored;
|
|
1114
1635
|
const { name, params } = cue.source;
|
|
1115
1636
|
return writeCached(`${name}-${fnv1a(JSON.stringify(params))}`, () => synthSfx(name, params));
|
|
1116
1637
|
}
|
|
@@ -1118,7 +1639,7 @@ async function resolveBgmFile(source, duration, sceneDir) {
|
|
|
1118
1639
|
if (source.kind === "file") {
|
|
1119
1640
|
const p = source.path;
|
|
1120
1641
|
for (const candidate of [isAbsolute(p) ? p : null, resolve(sceneDir, p), join(VENDORED, p)]) {
|
|
1121
|
-
if (candidate &&
|
|
1642
|
+
if (candidate && existsSync2(candidate)) return candidate;
|
|
1122
1643
|
}
|
|
1123
1644
|
throw new Error(`bgm file "${p}" not found`);
|
|
1124
1645
|
}
|
|
@@ -1288,14 +1809,14 @@ var init_encode = __esm({
|
|
|
1288
1809
|
});
|
|
1289
1810
|
|
|
1290
1811
|
// ../render-cli/src/fonts.ts
|
|
1291
|
-
import { readFile } from "node:fs/promises";
|
|
1812
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1292
1813
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
1293
1814
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1294
1815
|
async function fontFaceCss() {
|
|
1295
1816
|
if (cssCache) return cssCache;
|
|
1296
1817
|
const rules = await Promise.all(
|
|
1297
1818
|
WEIGHTS.map(async (weight) => {
|
|
1298
|
-
const data = await
|
|
1819
|
+
const data = await readFile2(join3(FONTS_DIR, `inter-${weight}.woff2`));
|
|
1299
1820
|
return `@font-face {
|
|
1300
1821
|
font-family: "Inter";
|
|
1301
1822
|
font-style: normal;
|
|
@@ -1318,8 +1839,8 @@ var init_fonts = __esm({
|
|
|
1318
1839
|
});
|
|
1319
1840
|
|
|
1320
1841
|
// ../render-cli/src/images.ts
|
|
1321
|
-
import { readFile as
|
|
1322
|
-
import { existsSync as
|
|
1842
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1843
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1323
1844
|
import { extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
1324
1845
|
async function buildImageAssets(ir, sceneDir) {
|
|
1325
1846
|
const assets = {};
|
|
@@ -1333,11 +1854,11 @@ async function buildImageAssets(ir, sceneDir) {
|
|
|
1333
1854
|
const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
|
|
1334
1855
|
(c) => c !== null
|
|
1335
1856
|
);
|
|
1336
|
-
const found = candidates.find((c) =>
|
|
1857
|
+
const found = candidates.find((c) => existsSync3(c));
|
|
1337
1858
|
if (!found) {
|
|
1338
1859
|
throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
1339
1860
|
}
|
|
1340
|
-
const data = await
|
|
1861
|
+
const data = await readFile3(found);
|
|
1341
1862
|
assets[src] = `data:${mime};base64,${data.toString("base64")}`;
|
|
1342
1863
|
}
|
|
1343
1864
|
return assets;
|
|
@@ -1461,8 +1982,8 @@ async function withPage(size, fn) {
|
|
|
1461
1982
|
async function browserBundle() {
|
|
1462
1983
|
if (bundleCache) return bundleCache;
|
|
1463
1984
|
if (true) {
|
|
1464
|
-
const { readFile:
|
|
1465
|
-
bundleCache = await
|
|
1985
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
1986
|
+
bundleCache = await readFile6(
|
|
1466
1987
|
join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
1467
1988
|
"utf8"
|
|
1468
1989
|
);
|
|
@@ -1524,7 +2045,7 @@ __export(batch_exports, {
|
|
|
1524
2045
|
parseCsv: () => parseCsv,
|
|
1525
2046
|
runBatch: () => runBatch
|
|
1526
2047
|
});
|
|
1527
|
-
import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as
|
|
2048
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
1528
2049
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
1529
2050
|
import { join as join5, dirname as dirname5 } from "node:path";
|
|
1530
2051
|
function overlayFromFlat(row, name) {
|
|
@@ -1563,8 +2084,8 @@ function overlayFromFlat(row, name) {
|
|
|
1563
2084
|
}
|
|
1564
2085
|
return doc;
|
|
1565
2086
|
}
|
|
1566
|
-
function parseCsv(
|
|
1567
|
-
const lines =
|
|
2087
|
+
function parseCsv(text2) {
|
|
2088
|
+
const lines = text2.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
1568
2089
|
if (lines.length < 2) return [];
|
|
1569
2090
|
const split = (line) => {
|
|
1570
2091
|
const out = [];
|
|
@@ -1599,14 +2120,14 @@ function parseCsv(text) {
|
|
|
1599
2120
|
return row;
|
|
1600
2121
|
});
|
|
1601
2122
|
}
|
|
1602
|
-
async function loadRows(
|
|
1603
|
-
const
|
|
1604
|
-
if (
|
|
1605
|
-
const parsed = JSON.parse(
|
|
1606
|
-
if (!Array.isArray(parsed)) throw new Error(`${
|
|
2123
|
+
async function loadRows(path2) {
|
|
2124
|
+
const text2 = await readFile4(path2, "utf8");
|
|
2125
|
+
if (path2.endsWith(".csv")) return parseCsv(text2);
|
|
2126
|
+
const parsed = JSON.parse(text2);
|
|
2127
|
+
if (!Array.isArray(parsed)) throw new Error(`${path2}: expected a JSON array of row objects`);
|
|
1607
2128
|
return parsed;
|
|
1608
2129
|
}
|
|
1609
|
-
async function runBatch(
|
|
2130
|
+
async function runBatch(scene2, rows, opts) {
|
|
1610
2131
|
await mkdir3(opts.outDir, { recursive: true });
|
|
1611
2132
|
const results = new Array(rows.length);
|
|
1612
2133
|
let next = 0;
|
|
@@ -1619,7 +2140,7 @@ async function runBatch(scene, rows, opts) {
|
|
|
1619
2140
|
let result;
|
|
1620
2141
|
try {
|
|
1621
2142
|
const rowOverlay = overlayFromFlat(row, name);
|
|
1622
|
-
const { ir, report } = composeScene(
|
|
2143
|
+
const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
|
|
1623
2144
|
const framesDir = await mkdtemp2(join5(tmpdir3(), `reframe-batch-${index}-`));
|
|
1624
2145
|
const output = join5(opts.outDir, `${name}.mp4`);
|
|
1625
2146
|
const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
|
|
@@ -1682,22 +2203,20 @@ var init_batch = __esm({
|
|
|
1682
2203
|
// ../render-cli/src/loadScene.ts
|
|
1683
2204
|
var loadScene_exports = {};
|
|
1684
2205
|
__export(loadScene_exports, {
|
|
2206
|
+
isComposition: () => isComposition,
|
|
2207
|
+
loadModule: () => loadModule,
|
|
1685
2208
|
loadScene: () => loadScene
|
|
1686
2209
|
});
|
|
1687
2210
|
import { build as build2 } from "esbuild";
|
|
1688
|
-
import { readFile as
|
|
2211
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1689
2212
|
import { dirname as dirname6, resolve as resolve3 } from "node:path";
|
|
1690
2213
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1691
|
-
async function
|
|
1692
|
-
if (
|
|
1693
|
-
const ir = JSON.parse(await readFile4(path, "utf8"));
|
|
1694
|
-
validateScene(ir);
|
|
1695
|
-
return ir;
|
|
1696
|
-
}
|
|
2214
|
+
async function loadDefault(path2) {
|
|
2215
|
+
if (path2.endsWith(".json")) return JSON.parse(await readFile5(path2, "utf8"));
|
|
1697
2216
|
let code;
|
|
1698
2217
|
try {
|
|
1699
2218
|
const out = await build2({
|
|
1700
|
-
entryPoints: [
|
|
2219
|
+
entryPoints: [path2],
|
|
1701
2220
|
bundle: true,
|
|
1702
2221
|
format: "esm",
|
|
1703
2222
|
platform: "neutral",
|
|
@@ -1710,15 +2229,33 @@ async function loadScene(path) {
|
|
|
1710
2229
|
});
|
|
1711
2230
|
code = out.outputFiles[0].text;
|
|
1712
2231
|
} catch (err) {
|
|
1713
|
-
throw new Error(
|
|
1714
|
-
|
|
1715
|
-
${err instanceof Error ? err.message : String(err)}`
|
|
1716
|
-
);
|
|
2232
|
+
throw new Error(`failed to bundle ${path2}:
|
|
2233
|
+
${err instanceof Error ? err.message : String(err)}`);
|
|
1717
2234
|
}
|
|
1718
2235
|
const mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
|
|
1719
|
-
if (
|
|
2236
|
+
if (mod.default === void 0) throw new Error(`${path2} must default-export a scene or composition`);
|
|
1720
2237
|
return mod.default;
|
|
1721
2238
|
}
|
|
2239
|
+
function isComposition(def) {
|
|
2240
|
+
return typeof def === "object" && def !== null && Array.isArray(def.scenes);
|
|
2241
|
+
}
|
|
2242
|
+
async function loadScene(path2) {
|
|
2243
|
+
const def = await loadDefault(path2);
|
|
2244
|
+
if (isComposition(def)) {
|
|
2245
|
+
throw new Error(`${path2} is a composition \u2014 render it directly, not as a single scene`);
|
|
2246
|
+
}
|
|
2247
|
+
validateScene(def);
|
|
2248
|
+
return def;
|
|
2249
|
+
}
|
|
2250
|
+
async function loadModule(path2) {
|
|
2251
|
+
const def = await loadDefault(path2);
|
|
2252
|
+
if (isComposition(def)) {
|
|
2253
|
+
validateComposition(def);
|
|
2254
|
+
return { kind: "composition", ir: def };
|
|
2255
|
+
}
|
|
2256
|
+
validateScene(def);
|
|
2257
|
+
return { kind: "scene", ir: def };
|
|
2258
|
+
}
|
|
1722
2259
|
var HERE, CORE_ENTRY;
|
|
1723
2260
|
var init_loadScene = __esm({
|
|
1724
2261
|
"../render-cli/src/loadScene.ts"() {
|
|
@@ -1731,7 +2268,7 @@ var init_loadScene = __esm({
|
|
|
1731
2268
|
|
|
1732
2269
|
// ../render-cli/src/reframe.ts
|
|
1733
2270
|
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
1734
|
-
import { existsSync as
|
|
2271
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1735
2272
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
1736
2273
|
import { basename, isAbsolute as isAbsolute3, join as join6, resolve as resolve4 } from "node:path";
|
|
1737
2274
|
import { dirname as dirname7 } from "node:path";
|
|
@@ -1741,6 +2278,7 @@ var HERE2 = dirname7(fileURLToPath5(import.meta.url));
|
|
|
1741
2278
|
var ROOT2 = PACKAGED ? resolve4(HERE2, "..") : resolve4(HERE2, "..", "..", "..");
|
|
1742
2279
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
1743
2280
|
var RENDER_CLI = PACKAGED ? join6(ROOT2, "dist", "cli.js") : join6(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
2281
|
+
var LABELS = PACKAGED ? join6(ROOT2, "dist", "labels.js") : join6(ROOT2, "packages", "render-cli", "src", "labels.ts");
|
|
1744
2282
|
var ANALYZE = PACKAGED ? join6(ROOT2, "dist", "analyze.js") : join6(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
1745
2283
|
var TRACE = PACKAGED ? join6(ROOT2, "dist", "trace-cli.js") : join6(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
1746
2284
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
@@ -1749,8 +2287,12 @@ var USAGE = `reframe \u2014 declarative motion graphics
|
|
|
1749
2287
|
usage:
|
|
1750
2288
|
${CMD} render <scene.ts|.json|.html> [--overlay edits.json]... [-o out.mp4] [--fps N] [--duration S] [--no-audio]
|
|
1751
2289
|
${CMD} batch <scene.ts> <data.json|csv> [-o outDir] [--overlay base.json]... [--concurrency N] [--fps N]
|
|
2290
|
+
${CMD} logo <logo.svg|brand-slug> ["Name"] [--motion <preset>] [--energy 0..1] [--seed N] [-o out.mp4]
|
|
2291
|
+
animate a logo into a sting (presets: draw-bloom, punch-in,
|
|
2292
|
+
rise-settle, slide-bank, reveal-orbit, spin-forge)
|
|
1752
2293
|
${CMD} preview open the scrub/edit UI (lists scenes in your directory)
|
|
1753
2294
|
${CMD} new <scene-name> scaffold <scene-name>.ts in your directory
|
|
2295
|
+
${CMD} labels <scene.ts|.json> print the event clock (label \u2192 exact seconds; for sound design / timing)
|
|
1754
2296
|
${CMD} motion <mp4|framesDir> motion-profile a rendered clip
|
|
1755
2297
|
${CMD} trace <ref.mp4> [--apply scene.ts] extract a video's motion structure \u2192 MotionSketch / timeline
|
|
1756
2298
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
@@ -1775,9 +2317,9 @@ function run(cmd, args, opts = {}) {
|
|
|
1775
2317
|
});
|
|
1776
2318
|
let sawBrowserError = false;
|
|
1777
2319
|
proc.stderr.on("data", (d) => {
|
|
1778
|
-
const
|
|
1779
|
-
if (/Executable doesn't exist|browserType\.launch/.test(
|
|
1780
|
-
process.stderr.write(
|
|
2320
|
+
const text2 = d.toString();
|
|
2321
|
+
if (/Executable doesn't exist|browserType\.launch/.test(text2)) sawBrowserError = true;
|
|
2322
|
+
process.stderr.write(text2);
|
|
1781
2323
|
});
|
|
1782
2324
|
proc.on("close", (code) => {
|
|
1783
2325
|
if (code !== 0 && sawBrowserError) {
|
|
@@ -1858,7 +2400,7 @@ async function main() {
|
|
|
1858
2400
|
|
|
1859
2401
|
${USAGE}`);
|
|
1860
2402
|
const inputPath = userPath(input);
|
|
1861
|
-
if (!
|
|
2403
|
+
if (!existsSync4(inputPath)) fail(`no such file: ${inputPath}`);
|
|
1862
2404
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
1863
2405
|
if (!mode) {
|
|
1864
2406
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -1881,12 +2423,60 @@ ${USAGE}`);
|
|
|
1881
2423
|
await (PACKAGED ? run(process.execPath, [RENDER_CLI, mode, inputPath, ...outArgs]) : run("npx", ["tsx", RENDER_CLI, mode, inputPath, ...outArgs]))
|
|
1882
2424
|
);
|
|
1883
2425
|
}
|
|
2426
|
+
case "labels": {
|
|
2427
|
+
const input = rest[0];
|
|
2428
|
+
if (!input || input.startsWith("-")) fail(`labels needs a scene file
|
|
2429
|
+
|
|
2430
|
+
${USAGE}`);
|
|
2431
|
+
const inputPath = userPath(input);
|
|
2432
|
+
if (!existsSync4(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2433
|
+
process.exit(
|
|
2434
|
+
await (PACKAGED ? run(process.execPath, [LABELS, inputPath]) : run("npx", ["tsx", LABELS, inputPath]))
|
|
2435
|
+
);
|
|
2436
|
+
}
|
|
2437
|
+
case "logo": {
|
|
2438
|
+
const positional = [];
|
|
2439
|
+
const flags = {};
|
|
2440
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2441
|
+
const a = rest[i];
|
|
2442
|
+
if (a.startsWith("--")) flags[a.slice(2)] = rest[++i] ?? "";
|
|
2443
|
+
else if (a === "-o") flags.o = rest[++i] ?? "";
|
|
2444
|
+
else positional.push(a);
|
|
2445
|
+
}
|
|
2446
|
+
const arg = positional[0];
|
|
2447
|
+
if (!arg) {
|
|
2448
|
+
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]`);
|
|
2449
|
+
}
|
|
2450
|
+
preflightFfmpeg();
|
|
2451
|
+
const { tmpdir: tmpdir4 } = await import("node:os");
|
|
2452
|
+
const { resolveLogo: resolveLogo2, buildLogoSting: buildLogoSting2 } = await Promise.resolve().then(() => (init_logoSting(), logoSting_exports));
|
|
2453
|
+
const num = (k) => flags[k] !== void 0 ? Number(flags[k]) : void 0;
|
|
2454
|
+
console.log(`loading logo: ${arg} \u2026`);
|
|
2455
|
+
const { data, slug } = await resolveLogo2(arg, positional[1], {
|
|
2456
|
+
motion: flags.motion,
|
|
2457
|
+
energy: num("energy"),
|
|
2458
|
+
speed: num("speed"),
|
|
2459
|
+
intensity: num("intensity"),
|
|
2460
|
+
from: flags.from,
|
|
2461
|
+
seed: num("seed")
|
|
2462
|
+
});
|
|
2463
|
+
const sceneIR = buildLogoSting2(data);
|
|
2464
|
+
const tmp = join6(tmpdir4(), `reframe-logo-${slug}-${process.pid}.json`);
|
|
2465
|
+
await writeFile5(tmp, JSON.stringify(sceneIR));
|
|
2466
|
+
const outBase = PACKAGED ? join6(USER_CWD, "out") : join6(ROOT2, "out");
|
|
2467
|
+
const out = flags.o ? userPath(flags.o) : join6(outBase, `logo-${slug}.mp4`);
|
|
2468
|
+
await mkdir4(dirname7(out), { recursive: true });
|
|
2469
|
+
console.log(`rendering ${data.name} (${data.paths.length} path${data.paths.length > 1 ? "s" : ""}, motion: ${data.motion ?? "reveal-orbit"}) \u2192 ${out}`);
|
|
2470
|
+
process.exit(
|
|
2471
|
+
await (PACKAGED ? run(process.execPath, [RENDER_CLI, "ir", tmp, "-o", out, "--no-audio"]) : run("npx", ["tsx", RENDER_CLI, "ir", tmp, "-o", out, "--no-audio"]))
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
1884
2474
|
case "batch": {
|
|
1885
2475
|
const [sceneArg, dataArg, ...flags] = rest;
|
|
1886
2476
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
1887
2477
|
const scenePath = userPath(sceneArg);
|
|
1888
2478
|
const dataPath = userPath(dataArg);
|
|
1889
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
2479
|
+
for (const p of [scenePath, dataPath]) if (!existsSync4(p)) fail(`no such file: ${p}`);
|
|
1890
2480
|
preflightFfmpeg();
|
|
1891
2481
|
let outDir = PACKAGED ? join6(USER_CWD, "out", "batch") : join6(ROOT2, "out", "batch");
|
|
1892
2482
|
let concurrency = 3;
|
|
@@ -1901,15 +2491,15 @@ ${USAGE}`);
|
|
|
1901
2491
|
}
|
|
1902
2492
|
const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
1903
2493
|
const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
|
|
1904
|
-
const { readFile:
|
|
1905
|
-
const
|
|
2494
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
2495
|
+
const scene2 = await loadScene2(scenePath);
|
|
1906
2496
|
const baseOverlays = await Promise.all(
|
|
1907
|
-
baseOverlayPaths.map(async (p) => JSON.parse(await
|
|
2497
|
+
baseOverlayPaths.map(async (p) => JSON.parse(await readFile6(p, "utf8")))
|
|
1908
2498
|
);
|
|
1909
2499
|
const rows = await loadRows2(dataPath);
|
|
1910
2500
|
if (rows.length === 0) fail(`${dataPath}: no data rows`);
|
|
1911
2501
|
console.log(`batch: ${rows.length} rows \xD7 ${concurrency} workers \u2192 ${outDir}`);
|
|
1912
|
-
const results = await runBatch2(
|
|
2502
|
+
const results = await runBatch2(scene2, rows, {
|
|
1913
2503
|
outDir,
|
|
1914
2504
|
baseOverlays,
|
|
1915
2505
|
concurrency,
|
|
@@ -1932,6 +2522,9 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1932
2522
|
process.exit(failed > 0 ? 1 : 0);
|
|
1933
2523
|
}
|
|
1934
2524
|
case "preview": {
|
|
2525
|
+
console.log(
|
|
2526
|
+
"preview: drag waypoints/nodes; double-click a path to add a waypoint or a handle to remove it;\n \u270E reshapes an ease curve; 'vary \xD74' proposes motion variants.\ndeep-link a scene + time: http://localhost:5173/?scene=<scene-name>&t=<seconds>"
|
|
2527
|
+
);
|
|
1935
2528
|
if (PACKAGED) {
|
|
1936
2529
|
const { createRequire } = await import("node:module");
|
|
1937
2530
|
const vitePkg = createRequire(import.meta.url).resolve("vite/package.json");
|
|
@@ -1958,7 +2551,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1958
2551
|
const targetDir = inRepo ? join6(ROOT2, "examples", "scenes") : USER_CWD;
|
|
1959
2552
|
const target = join6(targetDir, `${name}.ts`);
|
|
1960
2553
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
1961
|
-
if (
|
|
2554
|
+
if (existsSync4(target)) fail(`${shown} already exists`);
|
|
1962
2555
|
const id = name.split("-")[0] ?? name;
|
|
1963
2556
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
1964
2557
|
console.log(`created ${shown}
|
|
@@ -1988,8 +2581,8 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1988
2581
|
}
|
|
1989
2582
|
case "guide": {
|
|
1990
2583
|
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");
|
|
1991
|
-
const { readFile:
|
|
1992
|
-
process.stdout.write(await
|
|
2584
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
2585
|
+
process.stdout.write(await readFile6(file, "utf8"));
|
|
1993
2586
|
return;
|
|
1994
2587
|
}
|
|
1995
2588
|
case "demo":
|