@bycrux/montaj-skills 0.1.0 → 0.3.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/contract.md +4 -2
- package/package.json +1 -1
- package/skills/image-search.md +4 -0
- package/skills/overlay.md +18 -12
- package/skills/select-takes.md +11 -5
- package/skills/write-overlay.md +148 -48
package/contract.md
CHANGED
|
@@ -42,9 +42,11 @@ Never save from a stale cached body. The operator may be editing the project con
|
|
|
42
42
|
|
|
43
43
|
These verbs are sufficient for the Phase-1 domain skills:
|
|
44
44
|
|
|
45
|
-
- **select-takes** — run step `crop_spec`, run step `virtual_to_original`; read the project.
|
|
45
|
+
- **select-takes** — run step `crop_spec`, run step `virtual_to_original`; read the project; write a file (the trim spec comes back inline from `waveform_trim` and must be on disk before `crop_spec` can take it as `--input`).
|
|
46
46
|
- **overlay** — read the project; save the project (delta); load skill `write-overlay`.
|
|
47
|
-
- **write-overlay** — save the project (delta); write a file (the JSX); reference asset paths via written/read files.
|
|
47
|
+
- **write-overlay** — save the project (delta); write a file (the JSX); run step `sample_overlay` (the end-of-authoring measure pass, required for any overlay that declares `googleFonts`); reference asset paths via written/read files.
|
|
48
48
|
- **image-search** — run step `search_images`, run step `fetch_image`; write a file / read a file.
|
|
49
49
|
|
|
50
50
|
If a future domain skill needs an operation not on this list, extend this contract first — do not let a domain skill invent its own transport-specific phrasing.
|
|
51
|
+
|
|
52
|
+
**This list is a starting index, not the final word.** It has drifted from the skill bodies before: `select-takes`'s "write a file" and `write-overlay`'s `sample_overlay` were both required by their skills and missing here. Anyone deriving the operations a workflow needs must confirm against the skill body itself — a set derived from this list alone can silently omit something the skill genuinely requires.
|
package/package.json
CHANGED
package/skills/image-search.md
CHANGED
|
@@ -44,6 +44,10 @@ Run step `fetch_image` with the following args. Downloads one HTTPS URL to a wor
|
|
|
44
44
|
|
|
45
45
|
Output: `{ "path": "/abs/path/to/file.jpg" }`. Some hosts return `403` to non-browser fetches — if one fails, run step `fetch_image` with the next candidate instead of fighting it.
|
|
46
46
|
|
|
47
|
+
> To register a fetched image into Hub Media (R2-backed, survives in the library), call
|
|
48
|
+
> `hub.ingest_media({ projectId, items: [{ workspacePath: "<absolute fetch_image out path>" }] })`.
|
|
49
|
+
> Do not hand-roll `create_media` + presigned PUT.
|
|
50
|
+
|
|
47
51
|
## Picking the right image
|
|
48
52
|
|
|
49
53
|
Search returns more than you need. Before committing:
|
package/skills/overlay.md
CHANGED
|
@@ -64,22 +64,25 @@ See skill `write-overlay` for the full authoring reference.
|
|
|
64
64
|
|
|
65
65
|
### 5. Save overlays to the project
|
|
66
66
|
|
|
67
|
-
Overlays live in `tracks[1+]` — overlay tracks in the unified tracks array. Each
|
|
67
|
+
Overlays live in `tracks[1+]` — overlay tracks in the unified tracks array. Each track is an object (`{id, items, ...}`); its `items` array holds the track's clips/overlays. Items in the same track cannot overlap in time; items in different tracks are z-ordered (higher indexes render on top). `tracks[0]` is always the primary footage track.
|
|
68
68
|
|
|
69
69
|
```json
|
|
70
70
|
{
|
|
71
71
|
"tracks": [
|
|
72
|
-
[],
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
{ "id": "trk-0", "items": [] },
|
|
73
|
+
{
|
|
74
|
+
"id": "trk-1",
|
|
75
|
+
"items": [
|
|
76
|
+
{
|
|
77
|
+
"id": "ov-0",
|
|
78
|
+
"type": "overlay",
|
|
79
|
+
"src": "/abs/path/to/project/overlays/hook.jsx",
|
|
80
|
+
"props": { "text": "The source code got leaked" },
|
|
81
|
+
"start": 0.0,
|
|
82
|
+
"end": 3.0
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
83
86
|
]
|
|
84
87
|
}
|
|
85
88
|
```
|
|
@@ -88,6 +91,8 @@ For multiple non-overlapping overlays, add them to the same track. For simultane
|
|
|
88
91
|
|
|
89
92
|
Follow save discipline: **read the project**, merge the updated `tracks` array into the fresh state, then **save the project (delta)**.
|
|
90
93
|
|
|
94
|
+
When this is the last editorial pass before the render, the project must be `final` before the render will run — see skill `native` → "Project lifecycle — status, and the render gate".
|
|
95
|
+
|
|
91
96
|
## Rules
|
|
92
97
|
|
|
93
98
|
- **Use icons, not emojis** — `Ph.*` (Phosphor) or `FaIcon` with `FaSolid`/`FaBrands` (Font Awesome). Both are available as globals — no imports needed. Only use emojis if the prompt asks.
|
|
@@ -100,6 +105,7 @@ Follow save discipline: **read the project**, merge the updated `tracks` array i
|
|
|
100
105
|
- **Keep text short** — 2–6 words for lower-thirds, 4–8 for hooks. Short + large beats long + small
|
|
101
106
|
- **Leave `offsetX`, `offsetY`, `scale` at defaults** (`0`, `0`, `1`) — the human positions overlays via the UI drag tool after preview
|
|
102
107
|
- **Use assets from `project.assets`** — pass asset `src` paths as `props`, don't hardcode paths inside JSX
|
|
108
|
+
- **Expose text styling as props** — a text overlay you want editable in the editor's properties panel must READ its font, size, weight, style, color, alignment, transform, and background from `props` (the nine standard text props) with sensible defaults, not hardcode them in the JSX. A hardcoded style shows no control in the panel. See skill `write-overlay` → "Make text overlays editable in the properties panel"
|
|
103
109
|
|
|
104
110
|
## Render Constraints
|
|
105
111
|
|
package/skills/select-takes.md
CHANGED
|
@@ -6,7 +6,7 @@ step: true
|
|
|
6
6
|
|
|
7
7
|
# Select Takes
|
|
8
8
|
|
|
9
|
-
`montaj/select_takes` is an agent-authored task — no CLI step, no API call. You reason across all clip transcripts and make editorial decisions. The output is a set of cropped trim specs ready for `rm_fillers` and
|
|
9
|
+
`montaj/select_takes` is an agent-authored task — no CLI step, no API call. You reason across all clip transcripts and make editorial decisions. The output is a set of cropped trim specs ready for `rm_fillers`. Nothing joins them into a file: the surviving keeps become `tracks[0]` items with their own `inPoint`/`outPoint`, and the render engine assembles them in one pass at the end.
|
|
10
10
|
|
|
11
11
|
## Core Purpose
|
|
12
12
|
|
|
@@ -82,11 +82,15 @@ For each flagged seam, fix it by trimming the crop window of whichever section i
|
|
|
82
82
|
|
|
83
83
|
**This check is required before writing any spec files.** Seam problems cannot be caught by any automated step downstream.
|
|
84
84
|
|
|
85
|
-
### 9. Crop the trim specs —
|
|
85
|
+
### 9. Crop the trim specs — never encode an intermediate
|
|
86
86
|
|
|
87
|
-
For each selected take,
|
|
87
|
+
For each selected take, the trim spec JSON comes back **inline** (on stdout / in the step result) from the preceding `waveform_trim` step — it is NOT written to disk automatically. The `crop_spec` step requires an on-disk file (its `--input` argument). Before calling `crop_spec`, write the inline spec to the project scratch dir using the `write_file` tool (e.g. save it as `<clip>_spec.json`), then pass that path as `crop_spec`'s `input`.
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Crop the written spec to the selected take's virtual-timeline window using the `crop_spec` step.
|
|
90
|
+
|
|
91
|
+
**Never encode an intermediate video file here.** There is no `trim` step to call, and reaching for `materialize_cut` at this point would break the single-encode chain for no gain. Cropping the spec keeps `tracks[0].items[*].src` pointing at the original source all the way through to the final render, which is what lets the operator re-trim any cut later without losing quality — and what keeps each item's `proxySrc` valid, since a proxy covers the original file.
|
|
92
|
+
|
|
93
|
+
**Note:** the `input` path in all `crop_spec` calls below must point to a spec file you've written to disk via `write_file` first (see above).
|
|
90
94
|
|
|
91
95
|
Run step `crop_spec` with `{"input": "/path/IMG_4893_spec.json", "keeps": [[8.5, 34.1]]}` → returns `{"path": "/path/IMG_4893_spec_cropped.json"}` (single window).
|
|
92
96
|
|
|
@@ -116,7 +120,9 @@ Run step `virtual_to_original` with `{"input": "spec.json", "inverse": true, "ti
|
|
|
116
120
|
|
|
117
121
|
### 10. Output
|
|
118
122
|
|
|
119
|
-
An ordered list of `_selected.json` trim spec paths — one per selected section, in narrative order. These become the inputs to `rm_fillers
|
|
123
|
+
An ordered list of `_selected.json` trim spec paths — one per selected section, in narrative order. These become the inputs to `rm_fillers`, and the keeps that survive it become the `tracks[0]` items the render engine assembles.
|
|
124
|
+
|
|
125
|
+
When this is the last editorial pass before the render, the project must be `final` before the render will run — see skill `native` → "Project lifecycle — status, and the render gate".
|
|
120
126
|
|
|
121
127
|
## What to Log
|
|
122
128
|
|
package/skills/write-overlay.md
CHANGED
|
@@ -7,6 +7,8 @@ description: "Write a custom JSX overlay component and add it to the project's o
|
|
|
7
7
|
|
|
8
8
|
An overlay is a React component rendered frame-by-frame by Puppeteer, composited over the footage at a specific timestamp. All overlays are custom JSX — there are no built-in templates.
|
|
9
9
|
|
|
10
|
+
> **Read [MOTION.md](MOTION.md) alongside this file whenever the overlay actually moves.** This file covers getting an overlay on screen correctly; MOTION.md covers making it move like someone designed it — the easing catalog (`interpolate` is strictly linear and has no easing option, which is why untutored overlays look flat), directional motion blur, per-character stagger, the no-dead-air rule, and how to verify motion by measurement instead of by eye. Every technique in it was rendered through the real sandbox before it was written down.
|
|
11
|
+
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
## Execution context
|
|
@@ -28,6 +30,7 @@ Custom overlay JSX runs in a sandboxed evaluator. All identifiers below are inje
|
|
|
28
30
|
| `THREE` | namespace | All [Three.js](https://threejs.org) primitives — `THREE.Vector3`, `THREE.MathUtils`, etc. Only reach for it when you genuinely need 3D — see "3D / Three.js" section. |
|
|
29
31
|
| `Canvas` | component | [@react-three/fiber](https://r3f.docs.pmnd.rs) Canvas. **Always pass `frameloop="never"`** and mount a `useThreeFrame()` child — see "3D / Three.js" section. |
|
|
30
32
|
| `useThreeFrame` | hook | Bridges r3f to Montaj's frame-stepped renderer. Mount exactly once inside any `<Canvas>`. |
|
|
33
|
+
| `useCanvas2DFrame` | hook | Drives a plain `CanvasRenderingContext2D` from `frame` — for pixel-level 2D drawing HTML/CSS can't do (per-pixel effects, arbitrary paths, `drawImage` compositing). See "2D Canvas" section. |
|
|
31
34
|
|
|
32
35
|
**No imports.** All `import` statements are stripped before evaluation. Do not import anything — use the globals above instead.
|
|
33
36
|
|
|
@@ -66,7 +69,7 @@ export default function List() {
|
|
|
66
69
|
|
|
67
70
|
## Writing the JSX
|
|
68
71
|
|
|
69
|
-
> **Carousel text overlays follow a stricter
|
|
72
|
+
> **Expose text styling as props to make an overlay editable.** A text overlay is only restyleable in the editor's properties panel for the props it declares — see "Make text overlays editable in the properties panel" below, which applies to video overlays too. **Carousel text overlays follow a stricter, required version of that contract:** every text-bearing overlay must accept its font size, family, weight, style, color, alignment, transform, and background as props with string defaults — see skill `editable-text`. The "go large — for video" guidance below, and the hardcoded-style `Hook` example just under it, **do not apply** to carousel editable-text overlays.
|
|
70
73
|
|
|
71
74
|
The default aesthetic is **plain bold text directly on video** — no card, no background, just a text shadow for legibility. Big text (96–160px) that covers the footage, including the speaker's face if needed.
|
|
72
75
|
|
|
@@ -74,8 +77,8 @@ The default aesthetic is **plain bold text directly on video** — no card, no b
|
|
|
74
77
|
// overlays/hook.jsx — plain text on video, no background
|
|
75
78
|
|
|
76
79
|
export default function Hook() {
|
|
77
|
-
const progress = interpolate(frame, [0, 8], [0, 1]
|
|
78
|
-
const slideY = interpolate(frame, [0, 10], [40, 0]
|
|
80
|
+
const progress = interpolate(frame, [0, 8], [0, 1])
|
|
81
|
+
const slideY = interpolate(frame, [0, 10], [40, 0])
|
|
79
82
|
|
|
80
83
|
return (
|
|
81
84
|
<div style={{
|
|
@@ -111,6 +114,58 @@ Only add a card or background when the prompt explicitly asks, or when a specifi
|
|
|
111
114
|
|
|
112
115
|
---
|
|
113
116
|
|
|
117
|
+
## Make text overlays editable in the properties panel
|
|
118
|
+
|
|
119
|
+
The editor's right-hand properties panel (and the floating text toolbar) can restyle a text overlay **only for the props it actually declares**. It reads the nine standard text props off the item's `props` object:
|
|
120
|
+
|
|
121
|
+
`text`, `fontSize`, `fontFamily`, `fontWeight`, `fontStyle`, `color`, `textAlign`, `textTransform`, `bgColor`
|
|
122
|
+
|
|
123
|
+
A control appears for each of those that is present (non-null) on `props`; anything the overlay **hardcodes** in its JSX style instead of reading from `props` shows **no control at all**, and the operator can't change it. This is exactly why an overlay that writes `textTransform: 'uppercase'` straight into its style has no text-transform control in the editor.
|
|
124
|
+
|
|
125
|
+
**So a text overlay you want a human to be able to restyle must READ its text styling from `props`, with sensible defaults — not hardcode it.** Same house style (big, bold, plain on video), just sourced from props:
|
|
126
|
+
|
|
127
|
+
```jsx
|
|
128
|
+
// Editable Hook — every text property is adjustable in the panel.
|
|
129
|
+
export default function Hook() {
|
|
130
|
+
const progress = interpolate(frame, [0, 8], [0, 1])
|
|
131
|
+
const slideY = interpolate(frame, [0, 10], [40, 0])
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div style={{ position: 'absolute', bottom: 180, left: 48, right: 48, opacity: progress, transform: `translateY(${slideY}px)` }}>
|
|
135
|
+
<div style={{
|
|
136
|
+
fontFamily: props.fontFamily ?? 'Anton, Impact, sans-serif',
|
|
137
|
+
fontSize: props.fontSize ?? 120,
|
|
138
|
+
fontWeight: props.fontWeight ?? 900,
|
|
139
|
+
fontStyle: props.fontStyle ?? 'normal',
|
|
140
|
+
color: props.color ?? '#fff',
|
|
141
|
+
textAlign: props.textAlign ?? 'left',
|
|
142
|
+
textTransform: props.textTransform ?? 'uppercase',
|
|
143
|
+
background: props.bgColor ?? 'transparent',
|
|
144
|
+
lineHeight: 1.05, letterSpacing: '-1px',
|
|
145
|
+
textShadow: '0 2px 24px rgba(0,0,0,0.9), 0 0 60px rgba(0,0,0,0.5)',
|
|
146
|
+
}}>
|
|
147
|
+
{props.text}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Declare those same values in the item's `props` too, so the panel opens on the real values rather than blank controls:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
"props": {
|
|
158
|
+
"text": "She built an AI employee",
|
|
159
|
+
"fontSize": 120, "fontFamily": "Anton, Impact, sans-serif",
|
|
160
|
+
"fontWeight": 900, "fontStyle": "normal", "color": "#ffffff",
|
|
161
|
+
"textAlign": "left", "textTransform": "uppercase", "bgColor": "transparent"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
This now applies to **video** text overlays, not just carousel slides — the video editor has the same properties panel. For the full contract (types and validation) see skill `editable-text`. A non-text overlay (logo, image card, chart) has no text props to expose; the same principle still holds for whatever a human would want to tweak — pass it through `props`, don't bury it in the JSX.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
114
169
|
## Splitting background from content across tracks
|
|
115
170
|
|
|
116
171
|
The most reliable way to use frosted-glass / blurred card backgrounds is to **put the background on a separate, lower track** and the animated content on a higher track. The render pipeline composites tracks in order, so the content renders on top.
|
|
@@ -129,25 +184,31 @@ The most reliable way to use frosted-glass / blurred card backgrounds is to **pu
|
|
|
129
184
|
```json
|
|
130
185
|
{
|
|
131
186
|
"tracks": [
|
|
132
|
-
[],
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
187
|
+
{ "id": "trk-0", "items": [] },
|
|
188
|
+
{
|
|
189
|
+
"id": "trk-1",
|
|
190
|
+
"items": [
|
|
191
|
+
{
|
|
192
|
+
"id": "ov-card-bg",
|
|
193
|
+
"type": "overlay",
|
|
194
|
+
"src": "/path/overlays/card-bg.jsx",
|
|
195
|
+
"start": 2.0,
|
|
196
|
+
"end": 6.0
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "trk-2",
|
|
202
|
+
"items": [
|
|
203
|
+
{
|
|
204
|
+
"id": "ov-card-content",
|
|
205
|
+
"type": "overlay",
|
|
206
|
+
"src": "/path/overlays/card-content.jsx",
|
|
207
|
+
"start": 2.0,
|
|
208
|
+
"end": 6.0
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
151
212
|
]
|
|
152
213
|
}
|
|
153
214
|
```
|
|
@@ -157,7 +218,7 @@ The most reliable way to use frosted-glass / blurred card backgrounds is to **pu
|
|
|
157
218
|
```jsx
|
|
158
219
|
// overlays/card-bg.jsx
|
|
159
220
|
// Just a frosted card that fades in. No children that animate opacity.
|
|
160
|
-
const opacity = interpolate(frame, [0, 8], [0, 1]
|
|
221
|
+
const opacity = interpolate(frame, [0, 8], [0, 1])
|
|
161
222
|
|
|
162
223
|
export default function CardBg() {
|
|
163
224
|
return (
|
|
@@ -221,10 +282,12 @@ const fadeOut = interpolate(frame, [duration - 15, duration], [1, 0])
|
|
|
221
282
|
const opacity = Math.min(fadeIn, fadeOut)
|
|
222
283
|
|
|
223
284
|
// Slide in from left
|
|
224
|
-
const x = interpolate(frame, [0, 20], [-200, 0]
|
|
285
|
+
const x = interpolate(frame, [0, 20], [-200, 0])
|
|
225
286
|
```
|
|
226
287
|
|
|
227
|
-
|
|
288
|
+
One option: `extrapolate` — `'clamp'` (default) or `'extend'`. That is the whole options object; the runtime destructures `{ extrapolate = 'clamp' }` and ignores everything else. Earlier versions of this file used `extrapolateRight`, which does not exist and was silently dropped — harmless only because the default was already `'clamp'`.
|
|
289
|
+
|
|
290
|
+
**`interpolate` is strictly linear** — `interpolate(5, [0,10], [0,1])` is exactly `0.5`. There is no easing parameter. For eased motion, ease the normalised value first: see **[MOTION.md](MOTION.md)** for the easing catalog, directional motion blur, per-character stagger, and the no-dead-air rule.
|
|
228
291
|
|
|
229
292
|
### `spring({ frame, fps, mass?, stiffness?, damping?, initialVelocity? })`
|
|
230
293
|
|
|
@@ -417,6 +480,35 @@ Three.js + r3f add ~250 KB to an overlay segment's bundle after esbuild tree-sha
|
|
|
417
480
|
|
|
418
481
|
---
|
|
419
482
|
|
|
483
|
+
## 2D Canvas
|
|
484
|
+
|
|
485
|
+
For plain 2D drawing HTML/CSS can't do — per-pixel effects, arbitrary paths, `drawImage` compositing — overlay JSX can drive a `CanvasRenderingContext2D` via `useCanvas2DFrame`. Unlike Three.js, this needs no library and behaves identically in preview and render — there's no internal render loop to disable.
|
|
486
|
+
|
|
487
|
+
```jsx
|
|
488
|
+
export default function CanvasExample() {
|
|
489
|
+
const draw = (ctx, { frame, fps, duration }) => {
|
|
490
|
+
ctx.fillStyle = '#EFE3CE'
|
|
491
|
+
ctx.fillRect(0, 0, 1080, 1080)
|
|
492
|
+
|
|
493
|
+
const x = interpolate(frame, [0, duration], [0, 1080 - 120])
|
|
494
|
+
ctx.fillStyle = '#3b82f6'
|
|
495
|
+
ctx.fillRect(x, 480, 120, 120)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const ref = useCanvas2DFrame(draw, frame, fps, duration)
|
|
499
|
+
|
|
500
|
+
return <canvas ref={ref} width={1080} height={1080} style={{ position: 'absolute', inset: 0 }} />
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**`useCanvas2DFrame(draw, frame, fps, duration)` — always pass `frame`/`fps`/`duration` explicitly**, the same as `interpolate`/`spring`. It returns a ref to attach to a `<canvas>` element; `draw(ctx, { frame, fps, duration })` runs synchronously every time that ref is attached, which happens on every frame.
|
|
505
|
+
|
|
506
|
+
**Draw the complete frame from scratch every call** — same rule as everywhere else in overlay JSX: same `frame` in, same pixels out. Don't rely on anything painted by a previous call still being there (start with a fill/clear, as in the example above).
|
|
507
|
+
|
|
508
|
+
**One `<canvas>` per `useCanvas2DFrame` call.** Each call returns its own ref; don't share one ref across multiple canvases or call the hook conditionally.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
420
512
|
## Charts / Recharts
|
|
421
513
|
|
|
422
514
|
Overlay JSX can use SVG-based charts via [Recharts](https://recharts.org). Available globals: `BarChart`, `Bar`, `LineChart`, `Line`, `PieChart`, `Pie`, `Cell`, `XAxis`, `YAxis`, `CartesianGrid`, `Tooltip`, `Legend`, `ResponsiveContainer`.
|
|
@@ -446,29 +538,32 @@ Place overlay items in `tracks[1+]` in `project.json`. Each item must have `type
|
|
|
446
538
|
```json
|
|
447
539
|
{
|
|
448
540
|
"tracks": [
|
|
449
|
-
[],
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
"
|
|
541
|
+
{ "id": "trk-0", "items": [] },
|
|
542
|
+
{
|
|
543
|
+
"id": "trk-1",
|
|
544
|
+
"items": [
|
|
545
|
+
{
|
|
546
|
+
"id": "ov-hook",
|
|
547
|
+
"type": "overlay",
|
|
548
|
+
"src": "/abs/path/to/project/overlays/hook.jsx",
|
|
549
|
+
"start": 0.0,
|
|
550
|
+
"end": 3.0,
|
|
551
|
+
"props": {
|
|
552
|
+
"text": "She built an AI employee"
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
"id": "ov-logo",
|
|
557
|
+
"type": "overlay",
|
|
558
|
+
"src": "/abs/path/to/project/overlays/logo.jsx",
|
|
559
|
+
"start": 0.0,
|
|
560
|
+
"end": 999.0,
|
|
561
|
+
"props": {
|
|
562
|
+
"logoSrc": "/abs/path/to/project/assets/logo.png"
|
|
563
|
+
}
|
|
459
564
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
"id": "ov-logo",
|
|
463
|
-
"type": "overlay",
|
|
464
|
-
"src": "/abs/path/to/project/overlays/logo.jsx",
|
|
465
|
-
"start": 0.0,
|
|
466
|
-
"end": 999.0,
|
|
467
|
-
"props": {
|
|
468
|
-
"logoSrc": "/abs/path/to/project/assets/logo.png"
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
]
|
|
565
|
+
]
|
|
566
|
+
}
|
|
472
567
|
]
|
|
473
568
|
}
|
|
474
569
|
```
|
|
@@ -576,7 +671,12 @@ Common overlay set for a social reel:
|
|
|
576
671
|
|
|
577
672
|
**Write all of your overlay JSX first. Then sample them in a single batch pass — do not sample after each file.** Per-file sampling stalls authoring and spins up a fresh Puppeteer process each time; one pass at the end over the finished set is faster and just as safe, since nothing downstream consumes an overlay until you save the project with the whole batch.
|
|
578
673
|
|
|
579
|
-
Once every JSX file is written, loop over them in one pass — for each JSX file, **run step `sample_overlay`** with args `{
|
|
674
|
+
Once every JSX file is written, loop over them in one pass — for each JSX file, **run step `sample_overlay`** with args `{ overlay, out, measure: true, google_fonts, props }`:
|
|
675
|
+
|
|
676
|
+
- `overlay` — absolute path to the JSX file
|
|
677
|
+
- `out` — absolute path where the step writes the sample PNG (required)
|
|
678
|
+
- `google_fonts` — the overlay's declared `googleFonts` value (snake_case step arg)
|
|
679
|
+
- `props` — representative props for the overlay
|
|
580
680
|
|
|
581
681
|
Pass each overlay's declared `googleFonts` (and representative `props`) so the step measures with the real render-time font — see the Syne case study below.
|
|
582
682
|
|