reframe-video 0.1.2 → 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.
Files changed (2) hide show
  1. package/dist/bin.js +490 -90
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -355,11 +355,11 @@ function validateScene(ir) {
355
355
  );
356
356
  }
357
357
  const labels = /* @__PURE__ */ new Set();
358
- const checkTimeline = (tl, path) => {
358
+ const checkTimeline = (tl, path2) => {
359
359
  if ("label" in tl && tl.label !== void 0) {
360
360
  if (labels.has(tl.label)) {
361
361
  problems.push(
362
- `${path}: duplicate timeline label "${tl.label}" \u2014 labels are overlay addresses and must be unique`
362
+ `${path2}: duplicate timeline label "${tl.label}" \u2014 labels are overlay addresses and must be unique`
363
363
  );
364
364
  }
365
365
  labels.add(tl.label);
@@ -367,63 +367,63 @@ function validateScene(ir) {
367
367
  switch (tl.kind) {
368
368
  case "seq":
369
369
  case "par":
370
- tl.children.forEach((c, i) => checkTimeline(c, `${path}.${tl.kind}[${i}]`));
370
+ tl.children.forEach((c, i) => checkTimeline(c, `${path2}.${tl.kind}[${i}]`));
371
371
  break;
372
372
  case "stagger":
373
- if (tl.interval < 0) problems.push(`${path}: stagger interval must be >= 0`);
374
- tl.children.forEach((c, i) => checkTimeline(c, `${path}.stagger[${i}]`));
373
+ if (tl.interval < 0) problems.push(`${path2}: stagger interval must be >= 0`);
374
+ tl.children.forEach((c, i) => checkTimeline(c, `${path2}.stagger[${i}]`));
375
375
  break;
376
376
  case "to":
377
377
  if (!(tl.state in states)) {
378
378
  problems.push(
379
- `${path}: to("${tl.state}") references an undefined state \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
379
+ `${path2}: to("${tl.state}") references an undefined state \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
380
380
  );
381
381
  }
382
382
  if (tl.duration !== void 0 && tl.duration <= 0) {
383
- problems.push(`${path}: to("${tl.state}") duration must be > 0`);
383
+ problems.push(`${path2}: to("${tl.state}") duration must be > 0`);
384
384
  }
385
385
  for (const id of tl.filter ?? []) {
386
- if (!nodeById.has(id)) problems.push(`${path}: filter contains unknown node "${id}"`);
386
+ if (!nodeById.has(id)) problems.push(`${path2}: filter contains unknown node "${id}"`);
387
387
  }
388
388
  break;
389
389
  case "tween":
390
- checkProps(path, tl.target, tl.props);
390
+ checkProps(path2, tl.target, tl.props);
391
391
  if (tl.duration !== void 0 && tl.duration <= 0) {
392
- problems.push(`${path}: tween duration must be > 0`);
392
+ problems.push(`${path2}: tween duration must be > 0`);
393
393
  }
394
394
  break;
395
395
  case "motionPath": {
396
396
  const node = nodeById.get(tl.target);
397
397
  if (!node) {
398
398
  problems.push(
399
- `${path}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
399
+ `${path2}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
400
400
  );
401
401
  } else if (node.type === "line") {
402
- problems.push(`${path}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
402
+ problems.push(`${path2}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
403
403
  }
404
- if (tl.points.length < 1) problems.push(`${path}: motionPath "${tl.target}" needs at least 1 point`);
404
+ if (tl.points.length < 1) problems.push(`${path2}: motionPath "${tl.target}" needs at least 1 point`);
405
405
  if (tl.duration !== void 0 && tl.duration <= 0) {
406
- problems.push(`${path}: motionPath "${tl.target}" duration must be > 0`);
406
+ problems.push(`${path2}: motionPath "${tl.target}" duration must be > 0`);
407
407
  }
408
408
  break;
409
409
  }
410
410
  case "wait":
411
- if (tl.duration < 0) problems.push(`${path}: wait duration must be >= 0`);
411
+ if (tl.duration < 0) problems.push(`${path2}: wait duration must be >= 0`);
412
412
  break;
413
413
  case "beat":
414
414
  if (labels.has(tl.name)) {
415
415
  problems.push(
416
- `${path}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
416
+ `${path2}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
417
417
  );
418
418
  }
419
419
  labels.add(tl.name);
420
420
  if (tl.duration !== void 0 && tl.duration <= 0) {
421
- problems.push(`${path}: beat "${tl.name}" duration must be > 0`);
421
+ problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
422
422
  }
423
423
  if (tl.scale !== void 0 && tl.scale <= 0) {
424
- problems.push(`${path}: beat "${tl.name}" scale must be > 0`);
424
+ problems.push(`${path2}: beat "${tl.name}" scale must be > 0`);
425
425
  }
426
- tl.children.forEach((c, i) => checkTimeline(c, `${path}.beat(${tl.name})[${i}]`));
426
+ tl.children.forEach((c, i) => checkTimeline(c, `${path2}.beat(${tl.name})[${i}]`));
427
427
  break;
428
428
  }
429
429
  };
@@ -490,6 +490,51 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
490
490
  });
491
491
 
492
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
+ }
493
538
  var init_dsl = __esm({
494
539
  "../core/src/dsl.ts"() {
495
540
  "use strict";
@@ -679,12 +724,181 @@ var init_compose = __esm({
679
724
  });
680
725
 
681
726
  // ../core/src/presets.ts
682
- var SET;
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;
683
895
  var init_presets = __esm({
684
896
  "../core/src/presets.ts"() {
685
897
  "use strict";
686
898
  init_dsl();
899
+ clamp01 = (x) => Math.max(0, Math.min(1, x));
687
900
  SET = 1 / 120;
901
+ dur = (base, sp) => base / sp;
688
902
  }
689
903
  });
690
904
 
@@ -897,6 +1111,152 @@ var init_src = __esm({
897
1111
  }
898
1112
  });
899
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;
1257
+ }
1258
+ });
1259
+
900
1260
  // ../render-cli/src/audio/wav.ts
901
1261
  function encodeWavMono16(samples, sampleRate = SAMPLE_RATE) {
902
1262
  const dataBytes = samples.length * 2;
@@ -941,52 +1301,52 @@ function buffer(duration) {
941
1301
  return { out: new Float32Array(n), n };
942
1302
  }
943
1303
  function whoosh(seed) {
944
- const dur = 0.35;
945
- const { out, n } = buffer(dur);
1304
+ const dur2 = 0.35;
1305
+ const { out, n } = buffer(dur2);
946
1306
  let lp = 0;
947
1307
  let lp2 = 0;
948
1308
  for (let i = 0; i < n; i++) {
949
1309
  const t = i / SAMPLE_RATE;
950
- const u = t / dur;
1310
+ const u = t / dur2;
951
1311
  const center = 1200 * Math.pow(300 / 1200, u);
952
1312
  const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
953
1313
  lp += alpha * (noise(i, seed) - lp);
954
1314
  lp2 += alpha * 0.5 * (lp - lp2);
955
- const env = u < 0.3 ? u / 0.3 : expDecay(t - 0.3 * dur, dur * 0.7, 4);
1315
+ const env = u < 0.3 ? u / 0.3 : expDecay(t - 0.3 * dur2, dur2 * 0.7, 4);
956
1316
  out[i] = (lp - lp2) * env * 2.2;
957
1317
  }
958
1318
  return out;
959
1319
  }
960
1320
  function pop(seed) {
961
- const dur = 0.12;
962
- const { out, n } = buffer(dur);
1321
+ const dur2 = 0.12;
1322
+ const { out, n } = buffer(dur2);
963
1323
  let phase = 0;
964
1324
  for (let i = 0; i < n; i++) {
965
1325
  const t = i / SAMPLE_RATE;
966
1326
  const freq = 600 * Math.pow(150 / 600, t / 0.08);
967
1327
  phase += TAU * freq / SAMPLE_RATE;
968
1328
  const transient = t < 2e-3 ? noise(i, seed) * 0.5 : 0;
969
- out[i] = (Math.sin(phase) + transient) * expDecay(t, dur, 6) * 0.8;
1329
+ out[i] = (Math.sin(phase) + transient) * expDecay(t, dur2, 6) * 0.8;
970
1330
  }
971
1331
  return out;
972
1332
  }
973
1333
  function tick(seed) {
974
- const dur = 0.03;
975
- const { out, n } = buffer(dur);
1334
+ const dur2 = 0.03;
1335
+ const { out, n } = buffer(dur2);
976
1336
  for (let i = 0; i < n; i++) {
977
1337
  const t = i / SAMPLE_RATE;
978
1338
  const sine = t < 4e-3 ? Math.sin(TAU * 4e3 * t) : 0;
979
- out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur, 8);
1339
+ out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur2, 8);
980
1340
  }
981
1341
  return out;
982
1342
  }
983
1343
  function rise(seed) {
984
- const dur = 0.5;
985
- const { out, n } = buffer(dur);
1344
+ const dur2 = 0.5;
1345
+ const { out, n } = buffer(dur2);
986
1346
  let phase = 0;
987
1347
  for (let i = 0; i < n; i++) {
988
1348
  const t = i / SAMPLE_RATE;
989
- const u = t / dur;
1349
+ const u = t / dur2;
990
1350
  const freq = 220 * Math.pow(880 / 220, u);
991
1351
  phase += TAU * freq / SAMPLE_RATE;
992
1352
  const env = Math.sin(Math.PI * Math.min(1, u * 1.05)) ** 1.5;
@@ -995,8 +1355,8 @@ function rise(seed) {
995
1355
  return out;
996
1356
  }
997
1357
  function shimmer(seed) {
998
- const dur = 0.9;
999
- const { out, n } = buffer(dur);
1358
+ const dur2 = 0.9;
1359
+ const { out, n } = buffer(dur2);
1000
1360
  const partials = Array.from({ length: 5 }, (_, p) => ({
1001
1361
  freq: 2e3 + hash01(p, seed + 7) * 2e3,
1002
1362
  am: 0.5 + hash01(p, seed + 8) * 1.5,
@@ -1004,7 +1364,7 @@ function shimmer(seed) {
1004
1364
  }));
1005
1365
  for (let i = 0; i < n; i++) {
1006
1366
  const t = i / SAMPLE_RATE;
1007
- const u = t / dur;
1367
+ const u = t / dur2;
1008
1368
  const env = Math.sin(Math.PI * u) ** 1.2;
1009
1369
  let s = 0;
1010
1370
  for (const part of partials) {
@@ -1015,8 +1375,8 @@ function shimmer(seed) {
1015
1375
  return out;
1016
1376
  }
1017
1377
  function thud(seed) {
1018
- const dur = 0.25;
1019
- const { out, n } = buffer(dur);
1378
+ const dur2 = 0.25;
1379
+ const { out, n } = buffer(dur2);
1020
1380
  let phase = 0;
1021
1381
  let lp = 0;
1022
1382
  for (let i = 0; i < n; i++) {
@@ -1025,7 +1385,7 @@ function thud(seed) {
1025
1385
  phase += TAU * freq / SAMPLE_RATE;
1026
1386
  lp += 0.02 * (noise(i, seed) - lp);
1027
1387
  const attack = t < 0.01 ? lp * 3 : 0;
1028
- out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur, 5);
1388
+ out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur2, 5);
1029
1389
  }
1030
1390
  return out;
1031
1391
  }
@@ -1060,7 +1420,7 @@ var init_synth = __esm({
1060
1420
  init_wav();
1061
1421
  noise = (n, seed) => hash01(n, seed) * 2 - 1;
1062
1422
  TAU = Math.PI * 2;
1063
- expDecay = (t, dur, k = 5) => Math.exp(-k * t / dur);
1423
+ expDecay = (t, dur2, k = 5) => Math.exp(-k * t / dur2);
1064
1424
  RECIPES = {
1065
1425
  whoosh,
1066
1426
  pop,
@@ -1074,26 +1434,26 @@ var init_synth = __esm({
1074
1434
 
1075
1435
  // ../render-cli/src/audio/sfx.ts
1076
1436
  import { mkdir, rename, writeFile } from "node:fs/promises";
1077
- import { existsSync } from "node:fs";
1437
+ import { existsSync as existsSync2 } from "node:fs";
1078
1438
  import { tmpdir } from "node:os";
1079
1439
  import { dirname, isAbsolute, join, resolve } from "node:path";
1080
1440
  import { fileURLToPath } from "node:url";
1081
- function fnv1a(text) {
1441
+ function fnv1a(text2) {
1082
1442
  let h = 2166136261;
1083
- for (let i = 0; i < text.length; i++) {
1084
- h ^= text.charCodeAt(i);
1443
+ for (let i = 0; i < text2.length; i++) {
1444
+ h ^= text2.charCodeAt(i);
1085
1445
  h = Math.imul(h, 16777619);
1086
1446
  }
1087
1447
  return (h >>> 0).toString(16);
1088
1448
  }
1089
1449
  async function writeCached(key2, make) {
1090
- const path = join(CACHE, `${key2}.wav`);
1091
- if (existsSync(path)) return path;
1450
+ const path2 = join(CACHE, `${key2}.wav`);
1451
+ if (existsSync2(path2)) return path2;
1092
1452
  await mkdir(CACHE, { recursive: true });
1093
- const temp = `${path}.${process.pid}.${fnv1a(String(performance.now()))}.tmp`;
1453
+ const temp = `${path2}.${process.pid}.${fnv1a(String(performance.now()))}.tmp`;
1094
1454
  await writeFile(temp, encodeWavMono16(make()));
1095
- await rename(temp, path);
1096
- return path;
1455
+ await rename(temp, path2);
1456
+ return path2;
1097
1457
  }
1098
1458
  async function resolveCueFile(cue, sceneDir) {
1099
1459
  if (cue.source.kind === "file") {
@@ -1103,14 +1463,14 @@ async function resolveCueFile(cue, sceneDir) {
1103
1463
  resolve(sceneDir, p),
1104
1464
  join(VENDORED, p)
1105
1465
  ]) {
1106
- if (candidate && existsSync(candidate)) return candidate;
1466
+ if (candidate && existsSync2(candidate)) return candidate;
1107
1467
  }
1108
1468
  throw new Error(
1109
1469
  `audio cue file "${p}" not found (tried absolute, scene-relative, assets/sfx/)`
1110
1470
  );
1111
1471
  }
1112
1472
  const vendored = join(VENDORED, `${cue.source.name}.wav`);
1113
- if (existsSync(vendored)) return vendored;
1473
+ if (existsSync2(vendored)) return vendored;
1114
1474
  const { name, params } = cue.source;
1115
1475
  return writeCached(`${name}-${fnv1a(JSON.stringify(params))}`, () => synthSfx(name, params));
1116
1476
  }
@@ -1118,7 +1478,7 @@ async function resolveBgmFile(source, duration, sceneDir) {
1118
1478
  if (source.kind === "file") {
1119
1479
  const p = source.path;
1120
1480
  for (const candidate of [isAbsolute(p) ? p : null, resolve(sceneDir, p), join(VENDORED, p)]) {
1121
- if (candidate && existsSync(candidate)) return candidate;
1481
+ if (candidate && existsSync2(candidate)) return candidate;
1122
1482
  }
1123
1483
  throw new Error(`bgm file "${p}" not found`);
1124
1484
  }
@@ -1288,14 +1648,14 @@ var init_encode = __esm({
1288
1648
  });
1289
1649
 
1290
1650
  // ../render-cli/src/fonts.ts
1291
- import { readFile } from "node:fs/promises";
1651
+ import { readFile as readFile2 } from "node:fs/promises";
1292
1652
  import { dirname as dirname3, join as join3 } from "node:path";
1293
1653
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1294
1654
  async function fontFaceCss() {
1295
1655
  if (cssCache) return cssCache;
1296
1656
  const rules = await Promise.all(
1297
1657
  WEIGHTS.map(async (weight) => {
1298
- const data = await readFile(join3(FONTS_DIR, `inter-${weight}.woff2`));
1658
+ const data = await readFile2(join3(FONTS_DIR, `inter-${weight}.woff2`));
1299
1659
  return `@font-face {
1300
1660
  font-family: "Inter";
1301
1661
  font-style: normal;
@@ -1318,8 +1678,8 @@ var init_fonts = __esm({
1318
1678
  });
1319
1679
 
1320
1680
  // ../render-cli/src/images.ts
1321
- import { readFile as readFile2 } from "node:fs/promises";
1322
- import { existsSync as existsSync2 } from "node:fs";
1681
+ import { readFile as readFile3 } from "node:fs/promises";
1682
+ import { existsSync as existsSync3 } from "node:fs";
1323
1683
  import { extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
1324
1684
  async function buildImageAssets(ir, sceneDir) {
1325
1685
  const assets = {};
@@ -1333,11 +1693,11 @@ async function buildImageAssets(ir, sceneDir) {
1333
1693
  const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
1334
1694
  (c) => c !== null
1335
1695
  );
1336
- const found = candidates.find((c) => existsSync2(c));
1696
+ const found = candidates.find((c) => existsSync3(c));
1337
1697
  if (!found) {
1338
1698
  throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
1339
1699
  }
1340
- const data = await readFile2(found);
1700
+ const data = await readFile3(found);
1341
1701
  assets[src] = `data:${mime};base64,${data.toString("base64")}`;
1342
1702
  }
1343
1703
  return assets;
@@ -1461,8 +1821,8 @@ async function withPage(size, fn) {
1461
1821
  async function browserBundle() {
1462
1822
  if (bundleCache) return bundleCache;
1463
1823
  if (true) {
1464
- const { readFile: readFile5 } = await import("node:fs/promises");
1465
- bundleCache = await readFile5(
1824
+ const { readFile: readFile6 } = await import("node:fs/promises");
1825
+ bundleCache = await readFile6(
1466
1826
  join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
1467
1827
  "utf8"
1468
1828
  );
@@ -1524,7 +1884,7 @@ __export(batch_exports, {
1524
1884
  parseCsv: () => parseCsv,
1525
1885
  runBatch: () => runBatch
1526
1886
  });
1527
- import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile3, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
1887
+ import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
1528
1888
  import { tmpdir as tmpdir3 } from "node:os";
1529
1889
  import { join as join5, dirname as dirname5 } from "node:path";
1530
1890
  function overlayFromFlat(row, name) {
@@ -1563,8 +1923,8 @@ function overlayFromFlat(row, name) {
1563
1923
  }
1564
1924
  return doc;
1565
1925
  }
1566
- function parseCsv(text) {
1567
- const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
1926
+ function parseCsv(text2) {
1927
+ const lines = text2.split(/\r?\n/).filter((l) => l.trim().length > 0);
1568
1928
  if (lines.length < 2) return [];
1569
1929
  const split = (line) => {
1570
1930
  const out = [];
@@ -1599,14 +1959,14 @@ function parseCsv(text) {
1599
1959
  return row;
1600
1960
  });
1601
1961
  }
1602
- async function loadRows(path) {
1603
- const text = await readFile3(path, "utf8");
1604
- if (path.endsWith(".csv")) return parseCsv(text);
1605
- const parsed = JSON.parse(text);
1606
- if (!Array.isArray(parsed)) throw new Error(`${path}: expected a JSON array of row objects`);
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`);
1607
1967
  return parsed;
1608
1968
  }
1609
- async function runBatch(scene, rows, opts) {
1969
+ async function runBatch(scene2, rows, opts) {
1610
1970
  await mkdir3(opts.outDir, { recursive: true });
1611
1971
  const results = new Array(rows.length);
1612
1972
  let next = 0;
@@ -1619,7 +1979,7 @@ async function runBatch(scene, rows, opts) {
1619
1979
  let result;
1620
1980
  try {
1621
1981
  const rowOverlay = overlayFromFlat(row, name);
1622
- const { ir, report } = composeScene(scene, ...opts.baseOverlays, rowOverlay);
1982
+ const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
1623
1983
  const framesDir = await mkdtemp2(join5(tmpdir3(), `reframe-batch-${index}-`));
1624
1984
  const output = join5(opts.outDir, `${name}.mp4`);
1625
1985
  const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
@@ -1685,19 +2045,19 @@ __export(loadScene_exports, {
1685
2045
  loadScene: () => loadScene
1686
2046
  });
1687
2047
  import { build as build2 } from "esbuild";
1688
- import { readFile as readFile4 } from "node:fs/promises";
2048
+ import { readFile as readFile5 } from "node:fs/promises";
1689
2049
  import { dirname as dirname6, resolve as resolve3 } from "node:path";
1690
2050
  import { fileURLToPath as fileURLToPath4 } from "node:url";
1691
- async function loadScene(path) {
1692
- if (path.endsWith(".json")) {
1693
- const ir = JSON.parse(await readFile4(path, "utf8"));
2051
+ async function loadScene(path2) {
2052
+ if (path2.endsWith(".json")) {
2053
+ const ir = JSON.parse(await readFile5(path2, "utf8"));
1694
2054
  validateScene(ir);
1695
2055
  return ir;
1696
2056
  }
1697
2057
  let code;
1698
2058
  try {
1699
2059
  const out = await build2({
1700
- entryPoints: [path],
2060
+ entryPoints: [path2],
1701
2061
  bundle: true,
1702
2062
  format: "esm",
1703
2063
  platform: "neutral",
@@ -1711,12 +2071,12 @@ async function loadScene(path) {
1711
2071
  code = out.outputFiles[0].text;
1712
2072
  } catch (err) {
1713
2073
  throw new Error(
1714
- `failed to bundle ${path}:
2074
+ `failed to bundle ${path2}:
1715
2075
  ${err instanceof Error ? err.message : String(err)}`
1716
2076
  );
1717
2077
  }
1718
2078
  const mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
1719
- if (!mod.default) throw new Error(`${path} must default-export a scene`);
2079
+ if (!mod.default) throw new Error(`${path2} must default-export a scene`);
1720
2080
  return mod.default;
1721
2081
  }
1722
2082
  var HERE, CORE_ENTRY;
@@ -1731,7 +2091,7 @@ var init_loadScene = __esm({
1731
2091
 
1732
2092
  // ../render-cli/src/reframe.ts
1733
2093
  import { spawn as spawn3, spawnSync } from "node:child_process";
1734
- import { existsSync as existsSync3 } from "node:fs";
2094
+ import { existsSync as existsSync4 } from "node:fs";
1735
2095
  import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
1736
2096
  import { basename, isAbsolute as isAbsolute3, join as join6, resolve as resolve4 } from "node:path";
1737
2097
  import { dirname as dirname7 } from "node:path";
@@ -1749,6 +2109,9 @@ var USAGE = `reframe \u2014 declarative motion graphics
1749
2109
  usage:
1750
2110
  ${CMD} render <scene.ts|.json|.html> [--overlay edits.json]... [-o out.mp4] [--fps N] [--duration S] [--no-audio]
1751
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)
1752
2115
  ${CMD} preview open the scrub/edit UI (lists scenes in your directory)
1753
2116
  ${CMD} new <scene-name> scaffold <scene-name>.ts in your directory
1754
2117
  ${CMD} motion <mp4|framesDir> motion-profile a rendered clip
@@ -1775,9 +2138,9 @@ function run(cmd, args, opts = {}) {
1775
2138
  });
1776
2139
  let sawBrowserError = false;
1777
2140
  proc.stderr.on("data", (d) => {
1778
- const text = d.toString();
1779
- if (/Executable doesn't exist|browserType\.launch/.test(text)) sawBrowserError = true;
1780
- process.stderr.write(text);
2141
+ const text2 = d.toString();
2142
+ if (/Executable doesn't exist|browserType\.launch/.test(text2)) sawBrowserError = true;
2143
+ process.stderr.write(text2);
1781
2144
  });
1782
2145
  proc.on("close", (code) => {
1783
2146
  if (code !== 0 && sawBrowserError) {
@@ -1858,7 +2221,7 @@ async function main() {
1858
2221
 
1859
2222
  ${USAGE}`);
1860
2223
  const inputPath = userPath(input);
1861
- if (!existsSync3(inputPath)) fail(`no such file: ${inputPath}`);
2224
+ if (!existsSync4(inputPath)) fail(`no such file: ${inputPath}`);
1862
2225
  const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
1863
2226
  if (!mode) {
1864
2227
  fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
@@ -1881,12 +2244,49 @@ ${USAGE}`);
1881
2244
  await (PACKAGED ? run(process.execPath, [RENDER_CLI, mode, inputPath, ...outArgs]) : run("npx", ["tsx", RENDER_CLI, mode, inputPath, ...outArgs]))
1882
2245
  );
1883
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
+ }
1884
2284
  case "batch": {
1885
2285
  const [sceneArg, dataArg, ...flags] = rest;
1886
2286
  if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
1887
2287
  const scenePath = userPath(sceneArg);
1888
2288
  const dataPath = userPath(dataArg);
1889
- for (const p of [scenePath, dataPath]) if (!existsSync3(p)) fail(`no such file: ${p}`);
2289
+ for (const p of [scenePath, dataPath]) if (!existsSync4(p)) fail(`no such file: ${p}`);
1890
2290
  preflightFfmpeg();
1891
2291
  let outDir = PACKAGED ? join6(USER_CWD, "out", "batch") : join6(ROOT2, "out", "batch");
1892
2292
  let concurrency = 3;
@@ -1901,15 +2301,15 @@ ${USAGE}`);
1901
2301
  }
1902
2302
  const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
1903
2303
  const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
1904
- const { readFile: readFile5 } = await import("node:fs/promises");
1905
- const scene = await loadScene2(scenePath);
2304
+ const { readFile: readFile6 } = await import("node:fs/promises");
2305
+ const scene2 = await loadScene2(scenePath);
1906
2306
  const baseOverlays = await Promise.all(
1907
- baseOverlayPaths.map(async (p) => JSON.parse(await readFile5(p, "utf8")))
2307
+ baseOverlayPaths.map(async (p) => JSON.parse(await readFile6(p, "utf8")))
1908
2308
  );
1909
2309
  const rows = await loadRows2(dataPath);
1910
2310
  if (rows.length === 0) fail(`${dataPath}: no data rows`);
1911
2311
  console.log(`batch: ${rows.length} rows \xD7 ${concurrency} workers \u2192 ${outDir}`);
1912
- const results = await runBatch2(scene, rows, {
2312
+ const results = await runBatch2(scene2, rows, {
1913
2313
  outDir,
1914
2314
  baseOverlays,
1915
2315
  concurrency,
@@ -1958,7 +2358,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
1958
2358
  const targetDir = inRepo ? join6(ROOT2, "examples", "scenes") : USER_CWD;
1959
2359
  const target = join6(targetDir, `${name}.ts`);
1960
2360
  const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
1961
- if (existsSync3(target)) fail(`${shown} already exists`);
2361
+ if (existsSync4(target)) fail(`${shown} already exists`);
1962
2362
  const id = name.split("-")[0] ?? name;
1963
2363
  await writeFile5(target, SCENE_TEMPLATE(name, id));
1964
2364
  console.log(`created ${shown}
@@ -1988,8 +2388,8 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
1988
2388
  }
1989
2389
  case "guide": {
1990
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");
1991
- const { readFile: readFile5 } = await import("node:fs/promises");
1992
- process.stdout.write(await readFile5(file, "utf8"));
2391
+ const { readFile: readFile6 } = await import("node:fs/promises");
2392
+ process.stdout.write(await readFile6(file, "utf8"));
1993
2393
  return;
1994
2394
  }
1995
2395
  case "demo":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
5
5
  "keywords": [
6
6
  "motion-graphics",