reframe-video 0.6.21 → 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;
@@ -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.21",
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",
@@ -74,6 +74,9 @@ to handle explicitly:
74
74
  - **Batch**: `npx -y reframe-video batch scene.ts data.json` — one mp4 per
75
75
  data row; row keys are overlay addresses (`nodes.<id>.<prop>`,
76
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.
77
80
  - **Preview editor**: `npx -y reframe-video preview` — scrub/play/knobs for
78
81
  scenes in the current directory; the user's knob edits export as an overlay
79
82
  JSON they can pass to render.