@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bycrux/montaj-skills",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
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.
@@ -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:
@@ -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], { extrapolateRight: 'clamp' })
78
- const slideY = interpolate(frame, [0, 10], [40, 0], { extrapolateRight: 'clamp' })
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], { extrapolateRight: 'clamp' })
128
- const slideY = interpolate(frame, [0, 10], [40, 0], { extrapolateRight: 'clamp' })
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], { extrapolateRight: 'clamp' })
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], { extrapolateRight: 'clamp' })
285
+ const x = interpolate(frame, [0, 20], [-200, 0])
283
286
  ```
284
287
 
285
- Options: `extrapolate`, `extrapolateLeft`, `extrapolateRight` — each `'clamp'` (default) or `'extend'`.
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`.