@bycrux/montaj-skills 0.2.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/overlay.md +2 -0
- package/skills/select-takes.md +2 -0
- package/skills/write-overlay.md +41 -7
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/overlay.md
CHANGED
|
@@ -91,6 +91,8 @@ For multiple non-overlapping overlays, add them to the same track. For simultane
|
|
|
91
91
|
|
|
92
92
|
Follow save discipline: **read the project**, merge the updated `tracks` array into the fresh state, then **save the project (delta)**.
|
|
93
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
|
+
|
|
94
96
|
## Rules
|
|
95
97
|
|
|
96
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.
|
package/skills/select-takes.md
CHANGED
|
@@ -122,6 +122,8 @@ Run step `virtual_to_original` with `{"input": "spec.json", "inverse": true, "ti
|
|
|
122
122
|
|
|
123
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
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".
|
|
126
|
+
|
|
125
127
|
## What to Log
|
|
126
128
|
|
|
127
129
|
Before writing specs, log your decisions clearly:
|
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
|
|
|
@@ -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={{
|
|
@@ -124,8 +127,8 @@ A control appears for each of those that is present (non-null) on `props`; anyth
|
|
|
124
127
|
```jsx
|
|
125
128
|
// Editable Hook — every text property is adjustable in the panel.
|
|
126
129
|
export default function Hook() {
|
|
127
|
-
const progress = interpolate(frame, [0, 8], [0, 1]
|
|
128
|
-
const slideY = interpolate(frame, [0, 10], [40, 0]
|
|
130
|
+
const progress = interpolate(frame, [0, 8], [0, 1])
|
|
131
|
+
const slideY = interpolate(frame, [0, 10], [40, 0])
|
|
129
132
|
|
|
130
133
|
return (
|
|
131
134
|
<div style={{ position: 'absolute', bottom: 180, left: 48, right: 48, opacity: progress, transform: `translateY(${slideY}px)` }}>
|
|
@@ -215,7 +218,7 @@ The most reliable way to use frosted-glass / blurred card backgrounds is to **pu
|
|
|
215
218
|
```jsx
|
|
216
219
|
// overlays/card-bg.jsx
|
|
217
220
|
// Just a frosted card that fades in. No children that animate opacity.
|
|
218
|
-
const opacity = interpolate(frame, [0, 8], [0, 1]
|
|
221
|
+
const opacity = interpolate(frame, [0, 8], [0, 1])
|
|
219
222
|
|
|
220
223
|
export default function CardBg() {
|
|
221
224
|
return (
|
|
@@ -279,10 +282,12 @@ const fadeOut = interpolate(frame, [duration - 15, duration], [1, 0])
|
|
|
279
282
|
const opacity = Math.min(fadeIn, fadeOut)
|
|
280
283
|
|
|
281
284
|
// Slide in from left
|
|
282
|
-
const x = interpolate(frame, [0, 20], [-200, 0]
|
|
285
|
+
const x = interpolate(frame, [0, 20], [-200, 0])
|
|
283
286
|
```
|
|
284
287
|
|
|
285
|
-
|
|
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.
|
|
286
291
|
|
|
287
292
|
### `spring({ frame, fps, mass?, stiffness?, damping?, initialVelocity? })`
|
|
288
293
|
|
|
@@ -475,6 +480,35 @@ Three.js + r3f add ~250 KB to an overlay segment's bundle after esbuild tree-sha
|
|
|
475
480
|
|
|
476
481
|
---
|
|
477
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
|
+
|
|
478
512
|
## Charts / Recharts
|
|
479
513
|
|
|
480
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`.
|