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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bin.js +15 -5
- package/guides/html-guide.md +180 -0
- package/package.json +1 -1
- package/skills/reframe/SKILL.md +3 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe",
|
|
3
|
-
"version": "0.1.
|
|
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|--
|
|
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
|
|
3190
|
-
|
|
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.
|
|
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",
|
package/skills/reframe/SKILL.md
CHANGED
|
@@ -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.
|