reframe-video 0.6.20 → 0.6.22

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "reframe",
2
+ "name": "kiyeonjeon21",
3
3
  "owner": {
4
4
  "name": "Kiyeon Jeon",
5
5
  "url": "https://github.com/kiyeonjeon21"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Create and iterate motion-graphics videos as addressable data: deterministic mp4 renders, human edits that survive AI regeneration, label-anchored audio, data-driven batch rendering.",
5
5
  "author": {
6
6
  "name": "Kiyeon Jeon",
package/dist/bin.js CHANGED
@@ -2804,6 +2804,17 @@ var PLAYER = PACKAGED ? join9(ROOT2, "dist", "player.js") : join9(ROOT2, "packag
2804
2804
  var ANALYZE = PACKAGED ? join9(ROOT2, "dist", "analyze.js") : join9(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
2805
2805
  var TRACE = PACKAGED ? join9(ROOT2, "dist", "trace-cli.js") : join9(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
2806
2806
  var CMD = PACKAGED ? "reframe" : "pnpm reframe";
2807
+ var GUIDE = PACKAGED ? {
2808
+ regen: join9(ROOT2, "guides", "regen-contract.md"),
2809
+ directing: join9(ROOT2, "guides", "directing-guide.md"),
2810
+ html: join9(ROOT2, "guides", "html-guide.md"),
2811
+ edsl: join9(ROOT2, "guides", "edsl-guide.md")
2812
+ } : {
2813
+ regen: join9(ROOT2, "docs", "regen-contract.md"),
2814
+ directing: join9(ROOT2, "docs", "guides", "directing-guide.md"),
2815
+ html: join9(ROOT2, "docs", "guides", "html-guide.md"),
2816
+ edsl: join9(ROOT2, "docs", "guides", "edsl-guide.md")
2817
+ };
2807
2818
  var USAGE = `reframe \u2014 declarative motion graphics
2808
2819
 
2809
2820
  usage:
@@ -2824,7 +2835,7 @@ usage:
2824
2835
  ${CMD} motion <mp4|framesDir> motion-profile a rendered clip
2825
2836
  ${CMD} trace <ref.mp4> [--apply scene.ts] extract a video's motion structure \u2192 MotionSketch / timeline
2826
2837
  ${CMD} diff <ref-image> [<scene.ts>] [--t S] [--mode side|blend|diff|grid] compare/measure a render against a reference image
2827
- ${CMD} guide [--regen|--directing] print a guide (--regen: stable-address contract; --directing: high-end workflow)
2838
+ ${CMD} guide [--directing|--regen|--html] print a guide (default: eDSL syntax; --directing: high-end workflow; --regen: stable-address contract; --html: HTML/GSAP scenes)
2828
2839
  ${CMD} demo run the edit-survival demo (3 mp4s into out/)
2829
2840
  `;
2830
2841
  var userPath = (p) => isAbsolute5(p) ? p : resolve6(USER_CWD, p);
@@ -3185,10 +3196,9 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
3185
3196
  );
3186
3197
  }
3187
3198
  case "guide": {
3188
- const which = rest.includes("--regen") ? "regen" : rest.includes("--directing") ? "directing" : "edsl";
3189
- const repoFile = { regen: join9(ROOT2, "docs", "regen-contract.md"), directing: join9(ROOT2, "benchmark", "guides", "directing-guide.md"), edsl: join9(ROOT2, "benchmark", "guides", "edsl-guide.md") };
3190
- const pkgFile = { regen: join9(ROOT2, "guides", "regen-contract.md"), directing: join9(ROOT2, "guides", "directing-guide.md"), edsl: join9(ROOT2, "guides", "edsl-guide.md") };
3191
- const file = (PACKAGED ? pkgFile : repoFile)[which];
3199
+ const which = rest.includes("--regen") ? "regen" : rest.includes("--directing") ? "directing" : rest.includes("--html") ? "html" : "edsl";
3200
+ const file = GUIDE[which];
3201
+ if (!existsSync6(file)) fail(`guide not found: ${file}`);
3192
3202
  const { readFile: readFile7 } = await import("node:fs/promises");
3193
3203
  process.stdout.write(await readFile7(file, "utf8"));
3194
3204
  return;
@@ -74,6 +74,11 @@ flagship scenes — reuse the technique, vary the content:
74
74
 
75
75
  ### 4. Verify objectively (don't argue about "more dynamic")
76
76
 
77
+ Mind the tiers so you're not full-rendering to check small things: `compile`
78
+ (validate eDSL → IR, ~1s) and `labels` are cheap, no render; `frame --t <sec>`
79
+ is the cheap visual look (one PNG, ~1s); `motion`/`trace` below need a finished
80
+ render or reference video, so they're end-stage measurement, not the per-edit loop.
81
+
77
82
  - `reframe labels scene.ts` — every label → exact seconds. The timing source for audio + a
78
83
  sanity check that beats land when you think.
79
84
  - `reframe motion out.mp4` — speeds, static fraction, oscillation rhythm, spikes. A vague
@@ -97,5 +102,6 @@ addresses), so the human's polish isn't lost when you redo the base.
97
102
  matching with `diff` before adding motion.
98
103
  - Keep `id`s/labels stable across rewrites (see `reframe guide --regen`) so the user's
99
104
  overlay edits survive.
100
- - It's still iterative. The tools cut the rounds; they don't remove the loop. Render, look,
101
- adjust the agent should render frames and read them, not guess.
105
+ - It's still iterative. The tools cut the rounds; they don't remove the loop. `compile` to
106
+ validate (~1s), `frame --t <sec>` to look at a held moment (~1s), adjust — the agent should
107
+ read frames, not guess. Full `render` is the last step, not the per-edit check.
@@ -0,0 +1,180 @@
1
+ # HTML + GSAP motion guide
2
+
3
+ You write a motion-graphics scene as a **single self-contained `.html` file**
4
+ using HTML, CSS, and GSAP. The page is rendered to video frame-by-frame by a
5
+ deterministic capture harness.
6
+
7
+ ## Required structure
8
+
9
+ ```html
10
+ <!DOCTYPE html>
11
+ <html>
12
+ <head>
13
+ <meta charset="utf-8" />
14
+ <style>
15
+ body { margin: 0; }
16
+ #stage {
17
+ position: relative; width: 1920px; height: 1080px;
18
+ background: #101014; overflow: hidden;
19
+ font-family: Inter, sans-serif;
20
+ }
21
+ /* static styling for your elements */
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div id="stage">
26
+ <!-- your elements -->
27
+ </div>
28
+ <script src="./gsap.min.js"></script>
29
+ <script>
30
+ // your animation code
31
+ </script>
32
+ </body>
33
+ </html>
34
+ ```
35
+
36
+ - The video frame is exactly the `#stage` element — keep it `1920px × 1080px`
37
+ with `overflow: hidden`, and put everything inside it.
38
+ - GSAP 3 is available locally at `./gsap.min.js` (already next to your file).
39
+ Do not load anything else from the network — no CDNs, no images, no iframes.
40
+ - Font: use `font-family: Inter` (weights 400/700/800 are pre-installed by the
41
+ harness; no @font-face needed). Fallback `sans-serif`.
42
+
43
+ ## Animation rules (important)
44
+
45
+ The harness virtualizes time: `requestAnimationFrame`, `setTimeout`,
46
+ `setInterval`, `performance.now()` and `Date.now()` all follow a virtual
47
+ clock, so GSAP timelines and hand-rolled rAF loops are captured exactly.
48
+
49
+ - **All motion must be driven by GSAP or JavaScript.** CSS `animation`,
50
+ CSS `transition`, and SMIL run on the compositor clock and will NOT be
51
+ captured — a scene that relies on them renders as a frozen frame.
52
+ Static CSS styling (layout, colors, border-radius, flex...) is fine.
53
+ - No `Math.random()` unless seeded yourself deterministically; no `<video>`;
54
+ no user interaction. The page must play by itself from t=0.
55
+ - Match the brief's total duration: time your timeline so the action completes
56
+ within it (trailing hold is fine).
57
+
58
+ ## GSAP quick reference
59
+
60
+ ```js
61
+ const tl = gsap.timeline({ delay: 0.2 });
62
+ tl.to("#el", { x: 300, opacity: 1, duration: 0.5, ease: "power2.out" })
63
+ .fromTo("#el2", { scale: 0.5 }, { scale: 1.1, duration: 0.2, ease: "power2.out" })
64
+ .to("#el2", { scale: 1, duration: 0.1, ease: "power1.inOut" }) // settle
65
+ .to("#el3", { y: -40, duration: 0.3 }, "<") // "<" = start with previous
66
+ .to("#el4", { opacity: 0, duration: 0.3 }, "+=1.5"); // "+=" = gap after previous
67
+
68
+ gsap.to(".items", { opacity: 1, stagger: 0.1, duration: 0.4 }); // staggered
69
+ gsap.to("#el", { rotation: 3, yoyo: true, repeat: -1, duration: 0.6,
70
+ ease: "sine.inOut" }); // continuous wiggle during holds
71
+ ```
72
+
73
+ Eases: `power1/2/3/4.out` (decelerate — entrances), `.in` (accelerate — exits),
74
+ `.inOut`, `sine.inOut`, `back.out(1.7)` (overshoot), `expo.out`.
75
+ Position params: `"<"` start with previous, `"+=0.5"` gap, absolute `1.2`.
76
+
77
+ Useful patterns:
78
+
79
+ ```js
80
+ // Count-up number label
81
+ const counter = { value: 0 };
82
+ gsap.to(counter, { value: 14.0, duration: 1, ease: "power2.out",
83
+ onUpdate: () => { el.textContent = counter.value.toFixed(1); } });
84
+
85
+ // Center an element at a point (so scale/rotation pivot around its center)
86
+ // CSS: position:absolute; left:960px; top:540px; transform:translate(-50%,-50%)
87
+ // Then animate with xPercent/yPercent preserved:
88
+ gsap.fromTo("#el", { xPercent: -50, yPercent: -50, scale: 0.5 },
89
+ { xPercent: -50, yPercent: -50, scale: 1, duration: 0.3 });
90
+
91
+ // Grow a bar upward: anchor it with bottom CSS, animate height
92
+ gsap.fromTo("#bar", { height: 0 }, { height: 320, duration: 0.7, ease: "power3.out" });
93
+ ```
94
+
95
+ ## Worked example A — countdown (3, 2, 1, GO!)
96
+
97
+ ```html
98
+ <!DOCTYPE html>
99
+ <html>
100
+ <head>
101
+ <meta charset="utf-8" />
102
+ <style>
103
+ body { margin: 0; }
104
+ #stage { position: relative; width: 1920px; height: 1080px;
105
+ background: #101014; overflow: hidden; font-family: Inter, sans-serif; }
106
+ #ring { position: absolute; left: 960px; top: 540px;
107
+ width: 360px; height: 360px; margin: -180px 0 0 -180px;
108
+ border: 10px solid #3B82F6; border-radius: 50%; opacity: 0; }
109
+ .num, #go { position: absolute; left: 960px; top: 540px;
110
+ transform: translate(-50%, -50%); font-weight: 800; color: #fff; opacity: 0; }
111
+ .num { font-size: 220px; }
112
+ #go { font-size: 320px; color: #FF4D00; }
113
+ </style>
114
+ </head>
115
+ <body>
116
+ <div id="stage">
117
+ <div id="ring"></div>
118
+ <div class="num" id="num-3">3</div>
119
+ <div class="num" id="num-2">2</div>
120
+ <div class="num" id="num-1">1</div>
121
+ <div id="go">GO!</div>
122
+ </div>
123
+ <script src="./gsap.min.js"></script>
124
+ <script>
125
+ const tl = gsap.timeline();
126
+ tl.to("#ring", { opacity: 1, duration: 0.3, ease: "power2.out" });
127
+ for (const n of ["3", "2", "1"]) {
128
+ tl.fromTo(`#num-${n}`,
129
+ { opacity: 0, scale: 0.5 },
130
+ { opacity: 1, scale: 1.1, duration: 0.2, ease: "power2.out" })
131
+ .to(`#num-${n}`, { scale: 1, duration: 0.1, ease: "power1.inOut" })
132
+ .to(`#num-${n}`, { opacity: 0, scale: 0.7, duration: 0.1, ease: "power1.in" }, "+=0.45");
133
+ }
134
+ tl.fromTo("#go",
135
+ { opacity: 0, scale: 0.5 },
136
+ { opacity: 1, scale: 1.15, duration: 0.25, ease: "power2.out" })
137
+ .to("#ring", { scale: 1.6, opacity: 0, duration: 0.4, ease: "power2.out" }, "<")
138
+ .to("#go", { scale: 1, duration: 0.15, ease: "power1.inOut" });
139
+ </script>
140
+ </body>
141
+ </html>
142
+ ```
143
+
144
+ ## Worked example B — badge pop (overshoot + wiggle + drop)
145
+
146
+ ```html
147
+ <!DOCTYPE html>
148
+ <html>
149
+ <head>
150
+ <meta charset="utf-8" />
151
+ <style>
152
+ body { margin: 0; }
153
+ #stage { position: relative; width: 1920px; height: 1080px;
154
+ background: #15151A; overflow: hidden; font-family: Inter, sans-serif; }
155
+ #badge { position: absolute; left: 960px; top: 540px;
156
+ width: 420px; height: 160px; margin: -80px 0 0 -210px;
157
+ background: #E11D48; border-radius: 28px;
158
+ display: flex; align-items: center; justify-content: center;
159
+ opacity: 0; transform: scale(0); }
160
+ #badge span { color: #fff; font-size: 88px; font-weight: 800; letter-spacing: 6px; }
161
+ </style>
162
+ </head>
163
+ <body>
164
+ <div id="stage">
165
+ <div id="badge"><span>NEW</span></div>
166
+ </div>
167
+ <script src="./gsap.min.js"></script>
168
+ <script>
169
+ const tl = gsap.timeline({ delay: 0.2 });
170
+ tl.to("#badge", { opacity: 1, duration: 0.15, ease: "power1.out" })
171
+ .to("#badge", { scale: 1.18, duration: 0.28, ease: "power2.out" }, "<")
172
+ .to("#badge", { scale: 1, duration: 0.18, ease: "power1.inOut" })
173
+ .to("#badge", { y: 180, opacity: 0, duration: 0.35, ease: "power2.in" }, "+=1.6");
174
+
175
+ gsap.to("#badge", { rotation: 2.5, duration: 0.625, yoyo: true, repeat: -1,
176
+ ease: "sine.inOut" });
177
+ </script>
178
+ </body>
179
+ </html>
180
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.20",
3
+ "version": "0.6.22",
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",
@@ -19,7 +19,15 @@ runtime needs ffmpeg on PATH and a one-time `npx playwright install chromium`
19
19
  Scenes must be pure functions of time: no `Math.random()`/`Date` — use
20
20
  `wiggle` with a seed. Give every node a meaningful stable `id` and label
21
21
  the key timeline moments — those names are addresses for everything below.
22
- 3. Render and verify: `npx -y reframe-video render <name>.ts` `out/<name>.mp4`.
22
+ 3. Iterate on the cheap commands; full-render once at the end:
23
+ - `npx -y reframe-video compile <name>.ts` — validate eDSL → IR in ~1s, no
24
+ browser, no ffmpeg. Fix the classified error it prints, repeat. Catch every
25
+ syntax/validation error here before launching anything heavier.
26
+ - `npx -y reframe-video frame <name>.ts --t <sec> -o frame.png` — render ONE
27
+ PNG at a key moment in ~1s (chromium only, no mp4 mux) and LOOK at it. This
28
+ is your visual check; sample a few times across the timeline.
29
+ - `npx -y reframe-video render <name>.ts` → `out/<name>.mp4` — the full mp4 is
30
+ ~10x slower; run it once the frames look right, not per edit.
23
31
 
24
32
  ## Directing a high-end piece (cinematic / reference-faithful)
25
33
 
@@ -66,6 +74,9 @@ to handle explicitly:
66
74
  - **Batch**: `npx -y reframe-video batch scene.ts data.json` — one mp4 per
67
75
  data row; row keys are overlay addresses (`nodes.<id>.<prop>`,
68
76
  `timeline.<label>.duration`, ...). CSV works too (headers = addresses).
77
+ - **HTML/GSAP scenes**: `render` also accepts a self-contained `.html` scene and
78
+ captures it deterministically via a virtual clock — read
79
+ `npx -y reframe-video guide --html` before writing one.
69
80
  - **Preview editor**: `npx -y reframe-video preview` — scrub/play/knobs for
70
81
  scenes in the current directory; the user's knob edits export as an overlay
71
82
  JSON they can pass to render.
@@ -86,6 +97,8 @@ to handle explicitly:
86
97
 
87
98
  ## Verification habits
88
99
 
89
- Render after every change. For visual checks, extract a few frames with
90
- ffmpeg and look at them. Same input renders byte-identically, so "it changed"
91
- or "it didn't change" is always provable.
100
+ Verify on the cheap commands, not by full-rendering. `compile` (validate, ~1s)
101
+ then `frame --t <sec>` (one PNG, ~1s) is the inner loop; `render` is for the
102
+ final mp4. Don't pull frames out of an mp4 with ffmpeg just to look — `frame`
103
+ writes the PNG directly and skips the mux. Same input renders byte-identically,
104
+ so "it changed" or "it didn't change" is always provable from a single frame.