reelkit-cli 0.3.1 → 0.5.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.
Files changed (47) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/skill/SKILL.md +11 -2
  4. package/skill/reference/beat-sync.md +45 -0
  5. package/skill/reference/captions.md +18 -2
  6. package/skill/reference/clips.md +1 -0
  7. package/skill/reference/continuity.md +99 -0
  8. package/skill/reference/kit.md +18 -1
  9. package/skill/reference/motion-design.md +9 -0
  10. package/skill/reference/references.md +43 -0
  11. package/skill/reference/remotion-composition.md +2 -1
  12. package/skill/reference/scene-treatments.md +1 -1
  13. package/skill/reference/scriptwriting.md +39 -5
  14. package/skill/reference/sound-design.md +11 -0
  15. package/skill/reference/styles.md +9 -0
  16. package/src/cli.ts +14 -2
  17. package/src/commands/assets.ts +38 -8
  18. package/src/commands/auth.ts +1 -1
  19. package/src/commands/build.ts +73 -8
  20. package/src/commands/plan.ts +26 -4
  21. package/src/commands/ref.ts +315 -0
  22. package/src/contract/index.ts +25 -1
  23. package/src/pipeline/beatsnap.ts +52 -0
  24. package/src/pipeline/review.ts +28 -1
  25. package/src/pipeline/schema.ts +14 -0
  26. package/src/project/beats.ts +130 -0
  27. package/src/project/chromakey.ts +14 -4
  28. package/src/project/manifest.ts +22 -1
  29. package/src/project/music.ts +25 -0
  30. package/src/project/project.ts +1 -1
  31. package/src/project/refmeasure.ts +192 -0
  32. package/src/remotion/Root.tsx +4 -2
  33. package/src/remotion/kit/Camera.tsx +22 -0
  34. package/src/remotion/kit/Captions.tsx +25 -8
  35. package/src/remotion/kit/Carry.tsx +38 -0
  36. package/src/remotion/kit/Music.tsx +19 -0
  37. package/src/remotion/kit/beat.ts +23 -0
  38. package/src/remotion/kit/caption-groups.ts +65 -0
  39. package/src/remotion/kit/docs.ts +18 -1
  40. package/src/remotion/kit/index.ts +7 -0
  41. package/src/remotion/kit/media.ts +15 -0
  42. package/src/remotion/kit/motion-math.ts +113 -0
  43. package/src/remotion/kit/music-math.ts +42 -0
  44. package/src/render/continuity.ts +87 -0
  45. package/src/render/validate.ts +27 -0
  46. package/src/testing/conformance.ts +107 -1
  47. package/src/testing/fake-api.ts +44 -6
package/README.md CHANGED
@@ -62,7 +62,7 @@ reelkit render
62
62
  | `reelkit assets gen clip` | Generate a scene's video clip, or a green-screen one keyed to a transparent video, or one with the subject cut out on the server |
63
63
  | `reelkit plan check` | Validate `plan.json` |
64
64
  | `reelkit check` | Check the composition without rendering |
65
- | `reelkit preview` | Two test frames per scene |
65
+ | `reelkit preview` | Preview frames, and a report of which scene changes carry something across |
66
66
  | `reelkit render` | Render `out/video.mp4` |
67
67
 
68
68
  ## What is shared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelkit-cli",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "CLI and Claude skill for making short-form video with a shared asset library.",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Livshin",
package/skill/SKILL.md CHANGED
@@ -25,9 +25,15 @@ For each file the user gives, look at it, then register it with a description of
25
25
  `reelkit assets upload ./logo.png --describe "Acme logo, white wordmark on blue"`
26
26
  Add `--footage` for a video the motion design should be laid over. User files stay on this machine. A video of someone or something on a plain background can be made transparent: `--green` keys a green background locally for free, and `--cutout` removes any background on the server (the video is sent to Reelkit, so ask first; see `reference/clips.md`).
27
27
 
28
+ If the user points at an existing video ("make one like this", a link or a file), it is a reference: you learn how it is built and make something new in that spirit. Read `reference/references.md`, then `reelkit ref download <url-or-file>` and `reelkit ref analyze <id>`, and look at the frames it saves. Take its structure, pace and motion; never its footage, music or words. A link is fetched on the user's machine and they are responsible for the right to download it; ask before running `reelkit ref analyze` without `--no-transcript`, because the audio (never the video) is sent to Reelkit to be transcribed.
29
+
28
30
  ### 2. Look
29
31
  Read `reference/styles.md` and agree the video's look with the user: one of its looks, or their own. If they already described what they want, match it and confirm in one sentence. Ask anything still open in one message, each question with a default. If any on-screen text will be Hebrew, read `reference/hebrew-rtl.md` now: it changes how words may enter and how lines are written.
30
32
 
33
+ Ask whether they want captions, and which kind: none, one word at a time (`word`), or a phrase at a time (`phrase`, the default). Write the answer into the plan as `captions`.
34
+
35
+ Then read `reference/continuity.md` and offer the user three concepts for the video, each one sentence about the picture (not the product) with a different central idea, and say what carries each scene change. They pick one; write it into the first scene's `notes` beside the look.
36
+
31
37
  ### 3. Plan
32
38
  Read `reference/scriptwriting.md` and `reference/scene-treatments.md` (and `reference/clips.md` if any scene might be a video clip). Write the chosen look into the first scene's `notes`. Run `reelkit assets voices` and choose a voice that fits the idea, audience and language.
33
39
 
@@ -63,6 +69,8 @@ Write `plan.json`:
63
69
  - `clipPrompt` is set only for `clip` scenes (a generated or reused video clip is the scene's picture; the rest of the scene uses `imageTags` and `shareable` as an illustration does). Clips are scarce: most videos have none or one or two.
64
70
  - `userAssetIds` lists the ids of the user's files shown in that scene.
65
71
  - `pace` is `slow`, `normal` or `fast`.
72
+ - `captions` is `none`, `word` or `phrase`, as the user chose; leave it out for `phrase`. The manifest carries it: `<Captions group={manifest.captions} />`.
73
+ - When the video follows a reference, add `"reference": { "id": "<id>", "take": ["fast cuts every ~1.2s"] }` (1 to 6 notes on what you took).
66
74
 
67
75
  Run `reelkit plan check`; it prints the estimated length to tell the user. Fix everything under "Fix these". Act on "Worth improving" unless you have a good reason not to.
68
76
 
@@ -79,15 +87,16 @@ Each result starts with a match percentage: how likely it is good enough to reus
79
87
  For each clip scene, read `reference/clips.md`, then search `reelkit assets search "<what the clip shows>" --kind clip` and pull a 60% match (`reelkit assets pull <id> --scene <sceneId>`), or generate: `reelkit assets gen clip --scene <sceneId>` (add `--green` for a green-screen subject). It waits for the clip, which takes minutes; check what is left with `reelkit whoami`.
80
88
 
81
89
  ### 6. Composition
82
- Read `reference/kit.md`, `reference/remotion-composition.md`, `reference/motion-design.md` and `reference/captions.md`.
90
+ Read `reference/kit.md`, `reference/remotion-composition.md`, `reference/motion-design.md`, `reference/continuity.md`, `reference/captions.md` and `reference/beat-sync.md`.
83
91
 
92
+ - Choose the music before you write the composition, and read `reference/beat-sync.md`: `reelkit assets search "<mood and tempo>" --kind music`, then `reelkit assets pull <id> --music`. Scene changes then land on its beat by themselves; bring each element in on a beat inside a scene as that file shows.
84
93
  - Search the library before writing a component: `reelkit assets search "<what it shows>" --kind component`. To use one, `reelkit assets pull <id>`: it lands in `src/` and the command prints the import line and an example.
85
94
  - Read `reference/sound-design.md`, then find the few sounds the video needs: `reelkit assets search "whoosh" --kind sfx`, `reelkit assets pull <id>`. The pull prints how to reference the file.
86
95
  - Before writing a new component, read `reference/component-authoring.md`.
87
96
  - Write `src/Video.tsx` and any component files beside it. `Video.tsx` may import only `react`, `remotion`, `reelkit/kit` and sibling components (`./Name`).
88
97
  - `manifest.json` is the exact object passed to `Video` as the `manifest` prop. Media is referenced as `urls[path]`, where `path` is the file's path in the project, such as `urls[scene.voiceoverKey]` or `urls["assets/lib/<id>/clip.mp3"]`.
89
98
 
90
- Run `reelkit check` and fix every error until it passes. Then `reelkit preview` (the first preview on a machine downloads a browser once and can take a minute) and look at every frame in `out/preview/`: each scene has two, `early` (30% into the scene) and `late` (90%), so look at both frames of each scene. Look for: text cut off or overflowing, text overlapping other text or the captions, text too small or too low-contrast for a phone, an empty or broken frame, content hidden behind another layer, and any number, price or quote on screen that is not in the plan. A frame is one moment: an element mid-animation is not a problem, but anything that should be fully on screen by the late frame and is not is. Fix real problems and preview again. Go round at least twice, and finish with the studio test in `reference/motion-design.md`.
99
+ Run `reelkit check` and fix every error until it passes. Then `reelkit preview` (the first preview on a machine downloads a browser once and can take a minute) and look at every frame in `out/preview/`: each scene has two, `early` (30% into the scene) and `late` (90%), so look at both frames of each scene. Look for: text cut off or overflowing, text overlapping other text or the captions, text too small or too low-contrast for a phone, an empty or broken frame, content hidden behind another layer, and any number, price or quote on screen that is not in the plan. A frame is one moment: an element mid-animation is not a problem, but anything that should be fully on screen by the late frame and is not is. Fix real problems and preview again. Go round at least twice, and finish with the studio test in `reference/motion-design.md`. `reelkit preview` also reports continuity: how many scene changes carry something across. Fix every `cut` it names, or keep it as a deliberate choice (a burst of fast hits, the end card); see `reference/continuity.md`.
91
100
 
92
101
  You only ever see frames, never the moving video, so the frames are your eyes: look at every one. And whoever built a video is the worst judge of it. If you can start a separate agent, give it only the user's original request and the preview frames (not your explanations) and ask for a score out of 100 per scene, a list of flaws, and a concrete fix for each, in numbers ("raise the title 60 px", "hold the label 0.4 s longer"). Fix, preview, and have the same reviewer look again until every scene passes 90. If two rounds leave a scene under 70, stop and show the user the gap instead of spending more rounds.
93
102
 
@@ -0,0 +1,45 @@
1
+ # Putting the video on the beat
2
+
3
+ A video with music feels edited when things happen on the beat. The CLI puts the scene changes on the beat for you; inside a scene it is your job.
4
+
5
+ ## 1. Choose the track before the composition
6
+
7
+ `reelkit assets search "<mood and tempo>" --kind music`, for example "calm warm piano, slow" or "driving electronic, 120 BPM". Pull the best fit with `reelkit assets pull <id> --music`. That makes it the video's one track (a second `--music` replaces the first), measures its tempo here, and writes `assets/music.json`. Never use a reference video's own music; it is not yours to use.
8
+
9
+ Then `manifest.json` has `music: { key, bpm, beatFrames }`. `beatFrames` are the beats as composition frames. If a track has no clear tempo there is no `bpm`, `beatFrames` is empty, and nothing below applies: the scenes stay where the narration puts them.
10
+
11
+ ## 2. Scene changes are on the beat already
12
+
13
+ When the track has a tempo, each scene is held a little at its end, by less than one beat, so the next scene starts exactly on a beat. The narration and its word times do not move. `reelkit preview` says how many scene changes land on the beat. Do not shift `startFrame` yourself.
14
+
15
+ ## 3. Inside a scene: bring each element in on a beat
16
+
17
+ `beatFrames` are composition frames and a scene's clock starts at 0, so subtract the scene's `startFrame`. For an element that belongs to a word, take the beat nearest the frame where the word starts, then turn it into a `delay`:
18
+
19
+ - the word starts at `wordFrame = s.startFrame + Math.round(s.words[2].startSec * fps)`
20
+ - `beat = nearestBeat(manifest.music.beatFrames, wordFrame)`
21
+ - `delay = beat - s.startFrame`
22
+
23
+ Use `nextBeat` when the element must not come before the word. Prefer the beat nearest the word it belongs to over one that is merely free. Put the biggest move (the hero, the number, the reveal) on a strong beat: every fourth beat counts from the scene's first beat. `beatPulse(beatFrames, frame)` is 1 on a beat and falls to 0; use it sparingly, for a small scale or glow on the hero element only, never on everything.
24
+
25
+ Sound effects go on beats too: the `at` of an `Sfx` is a frame from the scene start, so use the same `delay` arithmetic.
26
+
27
+ ```tsx
28
+ import { Entrance, Music, nearestBeat, SceneFrame, Voiceover } from "reelkit/kit";
29
+
30
+ const beats = manifest.music?.beatFrames ?? [];
31
+ // Frames from the scene start to the beat nearest the word at index i.
32
+ const onBeat = (s: { startFrame: number; words: { startSec: number }[] }, i: number) => {
33
+ const word = s.startFrame + Math.round((s.words[i]?.startSec ?? 0) * manifest.fps);
34
+ return (nearestBeat(beats, word) ?? word) - s.startFrame;
35
+ };
36
+
37
+ <SceneFrame from={s.startFrame} durationInFrames={s.durationFrames}>
38
+ <Entrance delay={onBeat(s, 2)}><Card /></Entrance>
39
+ {s.voiceoverKey ? <Voiceover src={urls[s.voiceoverKey]} /> : null}
40
+ </SceneFrame>
41
+ // Once, outside the scenes:
42
+ {manifest.music ? <Music src={urls[manifest.music.key]} /> : null}
43
+ ```
44
+
45
+ `Music` ducks under the voice by itself and fades out over the last second; do not set its volume per scene.
@@ -7,6 +7,22 @@ description: Use when choosing and placing the spoken-word captions for a video
7
7
 
8
8
  Most short video is watched with the sound off, so the captions carry the message. Captions are drawn by the kit's `<Captions>` component from the scene's word timings (`s.words`); nothing is burned in afterwards.
9
9
 
10
+ ## First: does the video have captions?
11
+
12
+ The plan's `captions` field says: `"none"`, `"word"` (one word at a time) or `"phrase"` (a phrase at a time, the default when the field is absent). The user chose it at the Look step. It is in the manifest as `manifest.captions`, so pass it straight through and the composition follows the plan:
13
+
14
+ ```tsx
15
+ <Captions words={s.words} group={manifest.captions} mode="pop" highlight={palette.hero} />
16
+ ```
17
+
18
+ With `"none"` the component draws nothing; better still, leave `<Captions>` out of the composition altogether. `reelkit check` notes a composition that renders `<Captions>` when the plan says `"none"`, and one that renders none when the plan asks for words.
19
+
20
+ ## How words are grouped (`group`)
21
+ - `"word"`: exactly one word on screen at a time. Larger text, the most energy.
22
+ - `"phrase"`: words are grouped the way they are spoken, never by a fixed count that cuts a sentence. A group ends at the end of a sentence, at a comma or a dash once it has three words, and at a pause of 0.35 s or more between words. It never holds more than 6 words or about 32 characters; when it must break it does so after a comma or before a short joining word ("and", "of", "to") where it can, and a lone last word is joined to the line before it. Hebrew and Arabic sentence marks work the same way.
23
+ - Without `group`, `perLine` counts the words as it always did. Use `group` for every new video.
24
+ - `mode` (below) still decides how the words inside the group look.
25
+
10
26
  ## Pick one style for the whole video
11
27
 
12
28
  | mode | What it does | Use for |
@@ -22,8 +38,8 @@ Most short video is watched with the sound off, so the captions carry the messag
22
38
  Do not mix modes between scenes. Set `highlight` to the palette's hero colour (captions are the one exception to the one-hero-element rule) or to a high-contrast yellow on busy footage.
23
39
 
24
40
  ## Words per screen
25
- - `pop`: 1 to 3 words (`perLine` 1 to 3). Short words can share a screen.
26
- - `highlight` and `karaoke`: 3 to 5 words for vertical video, up to 6 for landscape. In Hebrew, at most 4.
41
+ - With `group="phrase"` the groups are at most 6 words, and about 32 characters, by themselves. With `group="word"` it is one.
42
+ - With `perLine` instead: `pop` 1 to 3 words; `highlight` and `karaoke` 3 to 5 words for vertical video, up to 6 for landscape; in Hebrew at most 4.
27
43
  - Never show a full sentence at once. Word-level timing reads better than sentence captions.
28
44
 
29
45
  ## Timing
@@ -35,6 +35,7 @@ A clip is a few seconds of generated video. It is the most expensive thing Reelk
35
35
  A green-screen clip is a subject filmed on a flat pure green background. It is for a person or an object that you place over something of your own: a presenter pointing at the user's screenshot, a character beside your title, a product over a gradient.
36
36
  - Generate it with `reelkit assets gen clip --scene <sceneId> --green`. The original is saved as `assets/clip-<sceneId>.mp4` and the green is keyed out locally into `assets/clip-<sceneId>.webm`, a video with a transparent background. The manifest has both: `s.clipKey` and `s.clipKeyedKey`. Use the `.webm`.
37
37
  - Prompt it as a subject, not a scene: "A woman in a blue jacket, waist up, waves and then points to her left. Flat, evenly lit, pure green background (#00FF00)." Keep the whole subject inside the frame with room around it; no green anywhere on the subject (clothes, props, eyes); no shadows, floor or reflections on the background; no camera move, because keying a moving frame edge flickers.
38
+ - A video model rarely produces a true chroma green; it usually gives a soft sage. When the green is too dull to key cleanly, the command does not key it: it cuts the subject out on the server instead (see "No green screen" below), says so, and uses cutout seconds from the quota. The result is used the same way.
38
39
  - If the keyed edges look green or ragged in the preview, the prompt was at fault: regenerate once with a flatter green and an evenly lit subject, using `--redo`. A clip left unkeyed (the command says so) is keyed again for free by running the same command again.
39
40
  - Layer it above the scene's background and below the captions:
40
41
 
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: continuity
3
+ description: Use before the plan and again when writing the composition - how to keep a video from feeling like a slideshow, with three concepts to offer the user, what to carry across every scene change, how to vary rhythm, and how to read the continuity and rhythm numbers the CLI reports.
4
+ ---
5
+
6
+ # Continuity and rhythm
7
+
8
+ A slideshow is a row of separate pictures: each scene replaces the one before it, and every scene lasts about the same time. Two things cure it. Continuity means that at every scene change something on screen survives and visibly moves, grows or turns into the next scene. Rhythm means shot lengths differ a lot, the picture sometimes rests, and not everything moves all the time.
9
+
10
+ ## Three concepts before a plan
11
+
12
+ A concept is one sentence about the picture, not about the product. Before you write `plan.json`, offer the user three concepts that differ in their central idea, and let them pick. For each one say the opening frame, what carries each scene change, and what it rules out.
13
+
14
+ Ideas to draw from (invent your own when the topic asks for it):
15
+
16
+ - **One element that transforms through the whole video.** A single card is the opening frame; it becomes the list, then the chart, then the button. Rules out unrelated pictures per scene.
17
+ - **One continuous camera move through scale.** The opening frame is a whole screen; the camera pushes into one detail until that detail is the next scene, and so on. Rules out cuts and flat full-frame layouts.
18
+ - **A chain of cause and effect.** Each event starts the next: a tap sends a message, the message lands as a notification, the notification opens the app. Rules out scenes that merely follow each other in a list.
19
+ - **One surface the whole video lives on.** A desk, a phone screen, a page, a map. Everything is placed on it and the camera or the objects move across it. Rules out backgrounds that change per scene.
20
+ - **A before-and-after split.** The frame is divided from the start; the dividing line moves, and what was on one side becomes the other. Rules out stories that have no contrast to show.
21
+
22
+ Write each concept as: "Opens on a plain card with the price. The card slides up and becomes the header of the comparison; the comparison's winning row grows into the call to action. No stock backgrounds." The user picks; write the choice into the first scene's `notes`, next to the look.
23
+
24
+ ## Carry at every scene change
25
+
26
+ For each boundary between two scenes, write down in that scene's `notes` what survives and how it moves. "Scene 2 to 3: the price card shrinks to the top-left and stays as the badge." If you cannot name it, the change is a cut.
27
+
28
+ Ways to do it with the kit:
29
+
30
+ - An element held in a `Carry` layer outside the scenes. It is drawn once, above the `SceneFrame`s, so it does not fade with them, and its keys say where it must be at which frame.
31
+ - A `Camera` pushing into an element until it fills the frame. The next scene starts with that element as its whole picture.
32
+ - A container that grows into the next scene's background. The card of one scene becomes the full-frame ground of the next.
33
+ - A shared colour field that one scene's object expands into. The new scene is the colour the old object grew to.
34
+
35
+ A hard cut is a choice. Use it for a burst of fast hits, or for the end card, and know that you are doing it. It is not the default.
36
+
37
+ `SceneFrame` fades each scene in and out over its first and last few frames. Anything that must stay on screen through a change therefore lives outside it, in a `Carry` layer (or inside a `Camera` that wraps all the scenes). This example holds a price card through two scenes: it is large in the first, then shrinks into a badge that the second scene is built around.
38
+
39
+ ```tsx
40
+ import React from "react";
41
+ import { AbsoluteFill } from "remotion";
42
+ import { BgMesh, Carry, Entrance, fonts, palettes, SceneFrame } from "reelkit/kit";
43
+ import type { VideoProps } from "reelkit/kit";
44
+
45
+ export const Video: React.FC<VideoProps> = ({ manifest }) => {
46
+ const palette = palettes.darkTech;
47
+ const [a, b] = manifest.scenes;
48
+ return (
49
+ <AbsoluteFill>
50
+ <BgMesh bg={palette.bg} hero={palette.hero} accent={palette.accent} />
51
+ <SceneFrame from={a.startFrame} durationInFrames={a.durationFrames}>
52
+ <Entrance delay={4}><div style={{ color: palette.dim, fontFamily: fonts.body, fontSize: 48 }}>Today only</div></Entrance>
53
+ </SceneFrame>
54
+ <SceneFrame from={b.startFrame} durationInFrames={b.durationFrames}>
55
+ <Entrance delay={10}><div style={{ color: palette.ink, fontFamily: fonts.display, fontSize: 96, fontWeight: 800, marginTop: 360 }}>Half the price</div></Entrance>
56
+ </SceneFrame>
57
+ <Carry
58
+ keys={[
59
+ { frame: 6, x: 0.5, y: 0.45, width: 0.7, height: 0.22, radius: 0.03, opacity: 1 },
60
+ { frame: b.startFrame + 6, x: 0.25, y: 0.12, width: 0.3, height: 0.08, radius: 0.02 },
61
+ ]}
62
+ >
63
+ {(box) => (
64
+ <div style={{ width: "100%", height: "100%", background: palette.hero, display: "flex", alignItems: "center", justifyContent: "center", color: palette.ink, fontFamily: fonts.display, fontWeight: 800, fontSize: box.heightPx * 0.5 }}>
65
+ $49
66
+ </div>
67
+ )}
68
+ </Carry>
69
+ </AbsoluteFill>
70
+ );
71
+ };
72
+ ```
73
+
74
+ The move toward a key starts 12 frames before the key's frame and lands on it; give the card a key early enough in the first scene that it has settled before the change, and a key just after the change for where the next scene wants it.
75
+
76
+ ## Rhythm
77
+
78
+ - Vary shot length at least fourfold: a hit of a quarter of a second beside a hold of two seconds or more. A plan whose scenes all last about the same time is a slideshow.
79
+ - Put a rest before the biggest moment: for about a second little or nothing moves, so that the moment lands.
80
+ - Never the same exit twice in a row. If one scene's content slides out to the left, the next one scales or wipes.
81
+ - Fewer sounds than changes. Sound a change that matters; leave the others silent.
82
+
83
+ ## The cause is visible
84
+
85
+ When something reacts, show what caused it: a pointer that presses before the button changes, an object that lands before the splash, a word that is spoken before the number appears. A result with no visible cause looks like a cut, even when everything is moving.
86
+
87
+ ## Small elements, big ground
88
+
89
+ For product pictures, the default is a small element on a big ground: the card, phone or button takes a third of the frame and the ground (a colour field, a surface, a blurred scene) fills the rest. A full-frame screenshot is for a held shot, where the viewer needs time to read it. Small elements are what a `Carry` or a `Camera` can move; full-frame pictures can only be replaced.
90
+
91
+ ## Reading the numbers
92
+
93
+ `reelkit preview` also saves the two frames around each scene change (`b01-end-<scene>.jpg`, then `b01-start-<scene>.jpg`, and so on) and prints a continuity report: a line such as "3 of 5 scene changes carry something across", and a line for each change where nothing does. The frames are the last and first ones at full strength, because `SceneFrame` fades the very last and first. The measure compares edges, the outlines of what is on screen: when at least a quarter of the earlier frame's outlines are still there, nearby, in the later frame, the change is `carried`; otherwise it is a `cut`. A change where the earlier frame is nearly empty is not counted.
94
+
95
+ Read it as a smoke alarm, not a grade. A `cut` is a prompt to fix the change or to say in the notes why it is a cut. A `carried` change can still be wrong (an element that stays but means nothing), so look at the pair of frames. The report is advice and never makes `preview` fail. Aim for most changes carried, with the cuts that remain on purpose.
96
+
97
+ `reelkit plan check` reads the rhythm from the narration: with four scenes or more, it adds a line under "Worth improving" when the scenes are about equally long, or when none is shorter than half the average. It quotes the shortest and the longest. Its data holds `sceneSeconds` for each scene and `lengthVariation`, the spread of the lengths over their mean; below 0.25 is too even. The cure is in the line itself: make one or two scenes much shorter, a hit of a few words, and let one run long.
98
+
99
+ When the video follows a reference, `reelkit ref analyze` gives targets to match. `pacing.shotLengthVariation` is the same spread for the reference's shots (left out when it has fewer than three), and `stillness` holds `share`, the part of the time in which the picture barely changes from frame to frame, and `longestSec`, the longest such hold. If the reference's shots vary by 0.8 and it is still a third of the time, plan scene lengths that vary about that much, and let the picture rest about that often. A video that moves every frame, against a reference that holds, will feel busier than the reference, whatever else you copy.
@@ -15,9 +15,10 @@ LowerThird { title: string; subtitle?: string; accent?: string }
15
15
  Name/label strip that slides in from the left, low on the screen.
16
16
  <LowerThird title="Step 1" subtitle="Connect your account" />
17
17
 
18
- Captions { words: WordTiming[]; mode?: "highlight" | "pop" | "karaoke"; highlight?: string; color?: string; perLine?: number; uppercase?: boolean; bottom?: number; face?: string; rtl?: boolean }
18
+ Captions { words: WordTiming[]; mode?: "highlight" | "pop" | "karaoke"; highlight?: string; color?: string; perLine?: number; group?: "none" | "word" | "phrase"; uppercase?: boolean; bottom?: number; face?: string; rtl?: boolean }
19
19
  Word-timed captions near the bottom. Pass the scene's words from the manifest. Pick one mode for the whole video:
20
20
  "highlight" (a line, spoken word coloured), "pop" (1-3 words popping in as spoken), "karaoke" (a line filling with colour).
21
+ group decides which words share the screen: "word" is one word at a time, "phrase" groups words as they are spoken (a group ends at a sentence end, at a comma after three words, at a pause of 0.35 s, and never holds more than 6 words or about 32 characters), "none" draws nothing. Pass the plan's choice straight through: group={manifest.captions}. Without group, perLine counts the words as before.
21
22
  bottom is the distance from the bottom edge as a fraction of the height (default 0.16).
22
23
  For Hebrew narration pass face={font("heebo")} and rtl.
23
24
  <Captions words={s.words} mode="pop" highlight={palette.hero} uppercase />
@@ -60,6 +61,14 @@ WordReveal { text: string; delay?: number; per?: number; highlight?: string; hig
60
61
  Headline that rises in word by word from behind a mask. highlight colours one word.
61
62
  <WordReveal text="Stretch first, phone second" highlight="first" highlightColor={palette.hero} style={{ fontFamily: fonts.display, fontWeight: 800, fontSize: width * 0.09, color: palette.ink }} />
62
63
 
64
+ Carry { keys: { frame: number; x: number; y: number; width: number; height: number; radius?: number; opacity?: number; rotate?: number }[]; lead?: number; stiffness?: number; damping?: number; children | (box) => children }
65
+ One element held through several scenes. Place it once, beside the SceneFrames and above them, so it is still on screen when a scene changes. Each key says where the box must have arrived by an absolute composition frame: x, y are its centre and width, height are fractions of the frame (0 to 1); radius is the corner radius as a fraction of the frame width; rotate is in degrees. The move toward a key starts lead frames before the key's frame (default 12, or at the previous key's frame when the keys are closer) on a spring (default springs.smooth) and lands on the key's frame exactly; between moves the box holds. Before the first key nothing is drawn unless that key sets an opacity; after the last the box holds. Children fill the box; a function child gets { x, y, width, height, radius, opacity, rotate, widthPx, heightPx } for the current moment, so the content can change as the box does.
66
+ <Carry keys={[{ frame: a.startFrame + 6, x: 0.5, y: 0.45, width: 0.8, height: 0.3 }, { frame: b.startFrame, x: 0.5, y: 0.5, width: 1, height: 1, radius: 0 }]}>{(box) => <Card compact={box.width < 0.5} />}</Carry>
67
+
68
+ Camera { keys: { frame: number; x?: number; y?: number; zoom?: number; rotate?: number }[]; drift?: number; lead?: number; stiffness?: number; damping?: number; children }
69
+ Moves the whole picture. Wrap all the scenes for one camera that never cuts, or wrap one scene's content. x, y are the point of the content, as fractions, that sits at the middle of the frame (default 0.5, 0.5); zoom 1 shows the content as it fits; rotate is in degrees. A key that leaves a value out keeps the one before it. The timing rule is the same as Carry's: the move toward a key starts lead frames before its frame and lands on it. drift is a very slow continuous push, as a fraction of the zoom per second (0.02 is two percent a second), so a held shot is never perfectly still.
70
+ <Camera keys={[{ frame: 0, zoom: 1 }, { frame: b.startFrame, x: 0.7, y: 0.4, zoom: 4 }]} drift={0.01}>{scenes}</Camera>
71
+
63
72
  fonts { display: string; body: string }
64
73
  Loaded font families. Use fonts.display (weights 600-800) for headlines and numbers, fonts.body for supporting text. Never leave hero text on a default font.
65
74
 
@@ -102,6 +111,14 @@ Sfx { src: string; at?: number; volume?: number }
102
111
  Plays one sound effect from the shared library, starting at the frame given by "at", counted from the start of the enclosing scene. Pull one with: reelkit assets search "<description>" --kind sfx, then reelkit assets pull <id>.
103
112
  <Sfx src={urls["assets/lib/<id>/clip.mp3"]} at={10} volume={0.35} />
104
113
 
114
+ Music { src: string; volume?: number; duckTo?: number }
115
+ The video's one music track (pull it with: reelkit assets pull <id> --music). Place it once, outside the scenes. It sits at duckTo (default 0.12) while the voiceover is heard and comes up to volume (default 0.5) in gaps longer than 0.6 s, loops if the video is longer than the track, and fades out over the last second. It follows the manifest, so nothing else is passed.
116
+ <Music src={urls[manifest.music.key]} />
117
+
118
+ nearestBeat(beatFrames, frame): number | undefined nextBeat(beatFrames, frame): number | undefined beatPulse(beatFrames, frame, decayFrames = 10): number
119
+ Beat helpers; beatFrames is manifest.music.beatFrames (composition frames). nearestBeat is the closest beat, nextBeat the first at or after the frame, beatPulse a number from 0 to 1 that is 1 on a beat and falls to 0 after it. Inside a scene, subtract s.startFrame from a beat to get a frame for that scene's own clock.
120
+ const b = nextBeat(manifest.music?.beatFrames ?? [], s.startFrame + 20) ?? s.startFrame + 20; // <Entrance delay={b - s.startFrame}>
121
+
105
122
  Voiceover { src: string; volume?: number }
106
123
  The scene's narration audio. One per narrated scene, inside its SceneFrame. Guard it with s.voiceoverKey so a scene without a recording still renders.
107
124
  {s.voiceoverKey ? <Voiceover src={urls[s.voiceoverKey]} /> : null}
@@ -72,6 +72,15 @@ Hook (first 1.5 s: the boldest visual and claim) → context (one line, one visu
72
72
  - Do not fake blur on fast motion with a blur filter on everything. Keep a short blur (6 to 10 pixels for a few frames) for content swapping inside a container and for depth.
73
73
  - A whole word sliding hundreds of pixels in a frame leaves separate ghost copies. Give it a directional blur that follows its speed, or shorten the travel.
74
74
 
75
+ ## Product shots
76
+ - **One object, then the push.** A product shot holds one thing in the middle: a field, a card, a device, a number. Space around it reads as confidence, as long as the camera group is moving in on it.
77
+ - **Screens live in space.** A screenshot laid flat is a slide; set at 8 to 15 degrees of turn on a card with a deep soft shadow it is an object. Bring it in from a steeper angle and let it settle; turn it to face the viewer only when its content must be read.
78
+ - **Change the shot through the object.** The field grows into the card, the icon opens into the screen, the tile becomes the number. When nothing can be carried across, the old object leaves in 4 to 6 frames with a directional blur and the new one lands on a heavy spring.
79
+ - **Interface text is typed, tapped and sent, never faded in.** A field gets a caret and keystrokes, a button gets a press (down 4 to 8 percent, back on a snappy spring) and a visible result, a switch travels. The viewer should be able to tell what was touched.
80
+ - **A pointer needs a reason.** Move it on an eased path, slow it into the target, press, and let something change within two frames of the press.
81
+ - **Two tones of text.** Set a sentence in a muted tone and bring only its key word to full ink or the hero colour as it is spoken.
82
+ - **End smaller than you began.** After the payoff, the logo sits small and centred on the clean ground, with at most one line under it, held at least 1.5 seconds.
83
+
75
84
  ## Weight on the page
76
85
  - Keep a scene under roughly 1,500 elements; beyond that draw on one canvas and redraw it each frame.
77
86
  - Compute heavy geometry once, outside the frame function, and only position it per frame.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: references
3
+ description: Use when the user says "make one like this" or gives a link or file of an existing video to match - how to bring it in, measure it, look at it, and turn how it is built into a plan, without copying anything from it.
4
+ ---
5
+
6
+ # References: "make one like this"
7
+
8
+ A reference is an existing video the user wants the new one to feel like. You learn how it is built (its structure, pace and motion) and make something new in that spirit. Nothing of it is ever reused.
9
+
10
+ ## Bring it in
11
+ 1. Ask for the video as a file if the user has it. A file on this machine is the clean path: `reelkit ref download ./example.mp4` copies it. A link works too, but it is fetched on the user's machine with yt-dlp (which they must have installed; the command says how if not), and the user is responsible for having the right to download that video. Say so in one sentence when you use a link. A reference is at most 10 minutes.
12
+ 2. `reelkit ref analyze <id>` measures it on this machine: scenes, cut times, pace, colours, loudness and tempo. It writes `refs/<id>/breakdown.json` and two sample frames per scene in `refs/<id>/frames/`. `reelkit ref list` shows what is in the project.
13
+ 3. Ask before it sends anything. The transcript is made on the Reelkit server, so the reference's audio (never the video) leaves the user's machine and is deleted there when the job ends. Ask "I can also transcribe its audio on the Reelkit server to understand the structure: OK?" and run `reelkit ref analyze <id> --no-transcript` if they say no. `reelkit whoami` shows the monthly transcription seconds left. If the transcript is refused (quota, not logged in), the breakdown is still saved; `reelkit ref analyze <id> --redo` adds the transcript later.
14
+
15
+ ## Look, then read
16
+ Open the frames in `refs/<id>/frames/` and look at them one scene after another: how big the type is, where it sits, how much of the screen is picture and how much is colour, how busy each scene is. Then read `breakdown.json`: `pacing.averageShotSec`, `pacing.firstCutSec`, `pacing.cutsPerSecond`, the `palette`, and the `transcript` if there is one.
17
+
18
+ ## What to take, and what never to
19
+ Take:
20
+ - the structure: how it opens, how many beats it has, how it ends
21
+ - the pace and the cut rhythm
22
+ - how type and layout are used (big words on colour, small captions over footage, one idea per screen)
23
+ - the kind of motion (snappy, floaty, hard cuts, slow pushes)
24
+ - the mood of its music, as a description to search the library with
25
+
26
+ Never take its footage, its music, its exact words, its logos or its faces. The transcript is for understanding the structure (where the hook ends, where the point lands), never for copying the script: write your own words about the user's subject.
27
+
28
+ ## Several references from one maker
29
+ When the user points at an account or a handful of videos rather than one, bring in three to six of them and look for what repeats, because that is the style; what appears once is only that video.
30
+ - **The ground:** what is behind everything (white, near-black, a colour field, footage) and whether it ever changes inside a video.
31
+ - **How full the frame is:** one small object in a lot of space, or type edge to edge. Measure it roughly: the main object's width as a share of the frame.
32
+ - **The order of shots:** most makers have one. Write it as a line, such as "a line of type, the logo, a field being typed into, three interface shots, a number, the logo".
33
+ - **How things arrive and leave:** on a spring, with a blur, with a hard cut, by changing shape. Step through a transition a frame at a time in the saved frames.
34
+ - **How long a shot holds,** from `pacing.averageShotSec` across the set, and how long the last one does.
35
+ - **Type:** how many sizes, how many weights, whether one word is coloured, and how words enter.
36
+ - **Sound:** whether the music runs without a break, and whether moves have their own sounds.
37
+ Write those seven lines down and match them to the nearest look in `reference/styles.md`; they become the plan's `reference.take` notes. The maker's subjects, logos, screens and layouts stay theirs: build the user's video from the library's components and the user's own material.
38
+
39
+ ## Turn the pace into the plan
40
+ - Scene count and length come from `pacing.averageShotSec`: a video of 30 seconds with shots of about 1.5 seconds is many quick scenes; since a plan has 3 to 8 scenes, group several reference shots into one scene that changes its picture inside (stacked text, quick swaps) rather than stretching it.
41
+ - The hook's length comes from `pacing.firstCutSec`: if the reference holds its first shot for 1.2 seconds, your first scene is one short line.
42
+ - Write the plan's `reference`: `{ "id": "<id>", "take": ["fast cuts every ~1.2s", "big type on colour fields"] }`, one to six short notes of what you took. `reelkit plan check` needs the analysis to exist and says when your scenes are much slower or faster than the reference.
43
+ - When the reference cuts on the beat (`pacing.cutsOnBeat` high, say 0.7 or more, with `audio.tempoBpm`), plan the new video's scene changes on a regular grid at a similar tempo, and search the library for music of a similar tempo and mood. The reference's own music is never used.
@@ -58,7 +58,8 @@ Each `scenes` entry:
58
58
  2. Images: `<KenBurnsImage>` inside its scene. Every still image moves; alternate `direction` between consecutive image scenes.
59
59
  3. Clips: `<ClipLayer>` inside its scene (a clip scene's picture); a green-screen `<KeyedClip>` above the scene's background and below its type and captions. See `reference/clips.md`.
60
60
  4. Graphics and type, inside each scene's SceneFrame.
61
- 5. `<Grade>`, then `<Grain>`, then `<Vignette>`, once, at the very top of the video outside the scenes. In footage mode skip Grade.
61
+ 5. Anything that must survive a scene change: a `<Carry>` layer beside the scenes and above them (outside the SceneFrame fades), and `<Camera>` around the scenes when one camera moves through all of them. See `reference/continuity.md`.
62
+ 6. `<Grade>`, then `<Grain>`, then `<Vignette>`, once, at the very top of the video outside the scenes. In footage mode skip Grade.
62
63
 
63
64
  ## Shared overlays (optional)
64
65
  - The library has ready-made overlay clips (`reelkit assets search "<description>" --kind overlay`): film marks, HUD frames, corner marks. They are white on black and are laid over the video with `<ScreenOverlay>`.
@@ -7,7 +7,7 @@ description: Use when choosing each scene's visual treatment and writing image p
7
7
 
8
8
  ## The four treatments
9
9
  - **motion-graphic** - animated text, numbers, shapes and icons drawn in code. The default. Best for hooks, lists, statistics, steps and calls to action.
10
- - **illustration** - one generated picture behind the text. Use when a concrete image carries the point better than type (an object, a place, a mood). At most half the scenes; each one costs money.
10
+ - **illustration** - one generated picture behind the text. Use when a concrete image carries the point better than type (an object, a place, a mood). Plan a picture or a clip for a third to half of the scenes, the opening first: a video of only text and shapes reads as a slideshow. Search the library before generating, since a reused picture costs nothing.
11
11
  - **clip** - a few seconds of generated or reused video as the scene's picture (B-roll), or a green-screen subject over your own graphics. Needs a `clipPrompt`. Rare and costly: see `reference/clips.md` before using it.
12
12
  - **footage-overlay** - text and graphics over the user's own video. Required for every scene when footage was supplied, and never used otherwise.
13
13
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: scriptwriting
3
- description: Use when writing the spoken script for a short social video - hooks, pacing, structure and length.
3
+ description: Use when writing the spoken script for a short social video - the opening, hook patterns, story shape by kind of video, pacing, and holding attention to the end.
4
4
  ---
5
5
 
6
6
  # Scriptwriting for short video
@@ -8,12 +8,46 @@ description: Use when writing the spoken script for a short social video - hooks
8
8
  ## Length and pace
9
9
  - Narration is spoken at about 2.5 words per second. A 30 second video is about 75 words; 60 seconds is about 150.
10
10
  - Without footage, aim for 30 to 60 seconds in total. With footage, never exceed the word limit that `reelkit plan check` reports.
11
- - One idea per scene. A scene is one or two sentences, 8 to 30 words.
11
+ - One idea per scene. Scenes should not all be the same length: let one or two be a hit of three to six words and one run to two sentences. Even scenes read as slides.
12
+
13
+ ## The opening decides the video
14
+ Most of what a short video achieves is decided in its first three seconds, and nearly all of it by ten. Write the opening first and spend the most care on it.
15
+
16
+ - **The first frame is a picture, not a title.** Open on the subject, the result or the problem itself: a generated picture, a clip, the user's own screenshot or footage, or one striking object. Text alone is never the first shot. Something must move within the first second.
17
+ - **It works with the sound off.** The picture and a few words on screen carry the promise without the voice; many viewers never turn the sound on.
18
+ - **The spoken hook is twelve words or fewer** and starts at once. No greeting, no logo sting, no "in this video", no name of the product before the reason to care.
19
+ - **Picture, voice and text each do a different job.** The picture shows the claim, the voice adds the tension, the on-screen text is the keyword. Never have all three say the same sentence.
20
+ - **The promise is made in the first sentence and kept.** By three seconds the viewer knows what they will get; for a product, it is on screen by five. The video then delivers exactly that. A hook the body does not pay off loses the viewer and their trust.
21
+
22
+ ### Hook patterns
23
+ Pick the one that fits what is true about the idea. Each needs its picture as much as its words.
24
+ - **Result first.** Show the finished thing, then how. "Four hundred invoices, sent before coffee."
25
+ - **The wrong belief.** Name what people assume, then turn it. "Your starter is not dying. It is hungry."
26
+ - **The problem, shown.** Open on the mess with no explanation for a second. A calendar with forty meetings in one week.
27
+ - **A specific number.** "Three taps replace your morning checklist." Only a number the user gave you.
28
+ - **The odd image.** Something that should not be there, then the question it raises. A paper map on a car dashboard: "Why is this still in your glove box?"
29
+ - **The open question.** Ask what the last scene answers, and do not answer it until then.
30
+ - **Before and after.** Cut between the two states before saying a word about the product.
31
+ - **What it costs.** What the viewer loses every week by not knowing this. Only when it is true.
32
+ - **Start in the middle.** Open at the moment of trouble, then go back. "At two in the morning the site was down and I was the only one awake."
33
+ - **Already happening.** Start mid-action in the interface: the cursor is already drawing the chart.
12
34
 
13
35
  ## Structure
14
- 1. **Hook (scene 1):** a question, a surprising claim or a named pain. No greetings, no "in this video".
15
- 2. **Body (1 to 6 scenes):** one point per scene, in an order that builds. Number the points when the idea is a list.
16
- 3. **Takeaway (last scene):** one concrete action or line to remember.
36
+ Choose the shape by what the video is.
37
+ - **Product or app demo:** the problem in one beat, the product doing the thing for real, the result. Show the action, not a description of it.
38
+ - **How something works:** a surprising claim, then cause and effect step by step, then the answer to the opening.
39
+ - **Tips or a list:** the strongest or most surprising item first, or build up to it. Never open with the weakest.
40
+ - **A story:** setup, a turn, what changed because of it.
41
+ - **An announcement:** the news in the first sentence, then why it matters to the viewer, then what to do.
42
+
43
+ Whatever the shape:
44
+ - **Every scene follows from the one before with "but" or "so", never "and then".** If two scenes could swap places, one of them is not needed.
45
+ - **Raise a question early and answer it last.** The answer is the payoff; put the biggest picture of the video there.
46
+ - **Re-hook twice.** Around forty percent and seventy percent of the way through, give the viewer a new reason to stay: a turn, a number, a reveal, a second question.
47
+ - **Be concrete.** At least half the scenes name a real number, a real object or a real moment. "Faster" and "better" are not content.
48
+ - **Something new on screen every two to three seconds:** a new shot, a new element, a camera move. The plan's `notes` should say what changes.
49
+ - **Mix what the scenes are made of.** A picture, a clip, the interface, type: never more than two scenes in a row of the same kind. A video of only text and shapes reads as a slideshow, so plan a picture or a clip for a third to half of the scenes, the opening first (see `reference/scene-treatments.md` and `reference/clips.md`).
50
+ - **End on the payoff, then one action** in five words or fewer. Where it fits, return to the opening image, changed.
17
51
 
18
52
  ## Voice
19
53
  - Write for the ear: short sentences, contractions, plain words. Read it aloud in your head.
@@ -34,6 +34,17 @@ A few well-placed sounds make motion feel real. Too many make a video tiring. Th
34
34
  - Never let two loud sounds overlap. Stagger them.
35
35
  - When the same kind of sound repeats, grade it: the first at full level, the rest quieter.
36
36
 
37
+ ## Background music
38
+ - A video with little or no voice needs a track; a voice-led video usually gains from a quiet one. Search by mood and use: `reelkit assets search "clean airy product launch" --kind music`, `"lo-fi calm"`, `"driving phonk"`, `"epic cinematic build"`, `"minimal tech under a voiceover"`. Each result says its mood and tempo.
39
+ - `reelkit assets pull <id>`; the pull prints the path. Place it once, outside the scenes, so it runs across the cuts: `<Sfx src={urls["<path the pull printed>"]} at={0} volume={0.12} />`.
40
+ - Under a voiceover the music sits at 0.08 to 0.15. With no voice it carries the video at 0.4 to 0.6, and the effects come down to 0.2 to 0.35 so they sit inside it.
41
+ - Library tracks are about 30 seconds. For a longer video place the track again on a scene change, where a transition sound covers the join.
42
+ - One track for the whole video. Pick the tempo first: scene changes on a grid of the beat (60 / bpm seconds, or two or four of them) make ordinary cuts feel designed.
43
+ - Never use the music of a reference or of any video the user points at. Describe its mood and tempo and search the library with that.
44
+
45
+ ## The interface pack
46
+ The library holds a matched set made for interface motion; search these words with `--kind sfx`: "soft ui whoosh" and "fast whip" for moves, "bubble pop" and "glass tap" for things appearing and being pressed, "keyboard typing" under a typed field, "toggle switch", "message sent", "notification ping", "success chime" on a tick, "sparkle shimmer" on an AI result, "counter ticks" under a number counting up, "soft impact" and "sub bass drop" for the landing and the payoff, "short riser" into a reveal, "camera shutter" on a freeze. Sounds from one set sit together; mix sets only when one is missing what you need.
47
+
37
48
  ## Fit
38
49
  - Match the sound to the look: soft pops and gentle whooshes for calm, friendly videos; glitches and bass hits for tech and high energy; camera and tape sounds for retro.
39
50
  - Skip anything startling (gunshots, screams, alarms) unless the topic is literally about it.
@@ -56,6 +56,15 @@ Each look is built from what the kit and the shared library already hold, so mos
56
56
  ### 10. Logo sting (either mode)
57
57
  **When:** an opener or end card, from the user's own logo file. **How:** build toward the logo from its own shapes and colours and land on it exactly as supplied, held at least 1.5 seconds. **Forbidden:** redrawing, recolouring or stretching the logo; anyone else's logo or trademark.
58
58
 
59
+ ### 11. Launch film (restrained)
60
+ **When:** a product, app or feature launch for a tech, software or AI brand: the short film that opens a launch post. **How:** a clean off-white or near-black ground with one soft pool of the hero colour at an edge (search "glow backdrop"); one object in the middle of each shot and a lot of empty space around it. Open on a single line of small, calm type that comes into focus word by word (search "blur reveal"), then one idea per shot, each 1.5 to 2.5 seconds: a field being typed into (search "search bar", "prompt box"), the real screenshot on a card floating at a slight tilt (search "tilt card", "laptop mockup", "phone mockup"), tiles circling a short line (search "orbit"), a feature grid (search "bento"), a pointer that clicks through it (search "cursor click"), the one real number as the payoff (search "statistic counter"), and the logo small and centred to close (search "logo reveal", "brand splash"). Between shots the outgoing object leaves fast with a short directional blur and the next one arrives on a heavy spring; the camera pushes in 2 to 4 percent on every shot. Six to eight shots in 12 to 20 seconds, music without a voice or with very few words. This look opens small on purpose: the first object may fill only a fifth of the frame, as long as the push carries it larger. **Forbidden:** two objects competing in one shot, a busy or textured background, a second accent colour, an invented screen, a shot that holds still, and any brand's mark but the user's own.
61
+
62
+ ### 12. Social native (showreel)
63
+ **When:** the video should feel like it was made inside the app it is posted to: a reply to a comment, a reaction, proof from the user's own account. **How:** the picture is footage or a generated image and the graphics are the platform's own furniture, used honestly: the comment being answered at the top (search "comment sticker"), a post or a story in its frame (search "post frame", "story frame"), the reel's own buttons over the picture (search "reel overlay"), a profile with a Follow button to close (search "profile card"), reactions floating up on the payoff (search "emoji reactions", "emoji pop"), and words punching in one at a time on the voice (search "punch words"). Emoji are allowed in this look, as reactions and stickers only. **Forbidden:** an invented comment, post, follower count or like count; a platform's logo when the video is not about that platform; emoji standing in for a logo or an icon.
64
+
65
+ ### 13. AI at work (restrained)
66
+ **When:** an AI product, an assistant or an automation: the viewer should watch it think and deliver. **How:** near-black ground; the request typed into a glowing field and sent (search "prompt box", "assistant chat window"); the work shown as steps that spin and tick (search "agent steps") or as a placeholder that resolves into the result (search "shimmer skeleton"); the answer streaming in (search "streaming answer"); a calm orb when it listens or speaks (search "glow orb"). One glow, on the field or the orb, never both in one frame. Every answer on screen is the product's real output, supplied by the user. **Forbidden:** an answer the product did not give, a second glowing element, sparkles as decoration, text that streams faster than it can be read.
67
+
59
68
  ## Your own
60
69
  Ask what the message is, what happens on screen, the colours, the length and shape, and for an example if they have one. Choose the nearest look above as the technical base and write a four-line brief in the same shape (when, how, forbidden, structure). Show the brief with the plan at the checkpoint.
61
70
 
package/src/cli.ts CHANGED
@@ -7,6 +7,7 @@ import { init } from "./commands/init";
7
7
  import { install } from "./commands/install";
8
8
  import { openInBrowser } from "./open";
9
9
  import { planCheck } from "./commands/plan";
10
+ import { refAnalyze, refAudio, refDownload, refList } from "./commands/ref";
10
11
  import type { Ctx, Result } from "./context";
11
12
  import { readFileSync } from "node:fs";
12
13
 
@@ -76,7 +77,7 @@ assets.command("upload <file>").description("Add one of your own files to this p
76
77
  .action(run((ctx, file: string, opts: Parameters<typeof assetsUpload>[2]) => assetsUpload(ctx, file, opts)));
77
78
  assets.command("search <query>").description("Search the shared library by meaning; each result shows how well it fits").option("--kind <kind>", "image, overlay, sfx, music, component or clip").option("--limit <n>", "how many results")
78
79
  .action(run(assetsSearch));
79
- assets.command("pull <id>").description("Download a library item into this project").option("--scene <sceneId>", "use it as this scene's image or clip").option("--force", "replace a component file that already exists")
80
+ assets.command("pull <id>").description("Download a library item into this project").option("--scene <sceneId>", "use it as this scene's image or clip").option("--force", "replace a component file that already exists").option("--music", "make this music item the video's track (its tempo is measured and scene changes land on its beat)")
80
81
  .action(run((ctx, id, opts) => assetsPull(ctx, id, opts)));
81
82
  assets.command("voices").description("List the narration voices").action(run(assetsVoices));
82
83
  assets.command("voiceover").description("Record the narration from plan.json").option("--scene <sceneId>", "one scene").option("--all", "every scene").option("--redo", "record again even if it exists")
@@ -90,11 +91,22 @@ gen.command("clip [prompt]").description("Generate a scene's video clip (takes m
90
91
  .option("--share", "also add it to the shared library (only a generic clip)").option("--redo", "generate a new clip even if the scene has one").option("--resume <id>", "keep waiting for a clip already started, without paying again")
91
92
  .action(run((ctx, prompt, opts) => assetsGenClip(ctx, prompt, opts)));
92
93
 
94
+ const ref = program.command("ref").description("Use an existing video as a reference for how a new one should feel (it is measured here; nothing of it goes into the output)");
95
+ ref.command("download <url-or-file>").description("Bring a video into the project as a reference: a link is fetched on this machine with yt-dlp (you need the right to download it), a file is copied")
96
+ .option("--redo", "download a link again even if it is already in the project")
97
+ .action(run((ctx, input: string, opts: { redo?: boolean }) => refDownload(ctx, input, opts)));
98
+ ref.command("audio <id>").description("Save the reference's audio as refs/<id>/audio.mp3 (on this machine)").action(run((ctx, id: string) => refAudio(ctx, id)));
99
+ ref.command("analyze <id>").description("Measure how the reference is built: scenes, cuts, pacing, colours, sound and tempo, with sample frames. Its audio (never the video) is sent to the Reelkit server to be transcribed unless --no-transcript")
100
+ .option("--no-transcript", "do not send the audio to the Reelkit server; everything else is measured here")
101
+ .option("--redo", "measure again even if a breakdown exists")
102
+ .action(run((ctx, id: string, opts: { transcript?: boolean; redo?: boolean }) => refAnalyze(ctx, id, opts)));
103
+ ref.command("list").description("List the references in this project").action(run((ctx) => refList(ctx)));
104
+
93
105
  program.command("plan").description("Work with plan.json").command("check").description("Validate plan.json and list what to fix or improve").action(run(planCheck));
94
106
 
95
107
  program.command("check").description("Check the composition in src/ without rendering").action(run(check));
96
108
  program.command("install").description("Install the Reelkit skill into your coding agents").option("--agent <id>", "claude, codex, cursor, gemini, agents or all").option("--force", "reinstall even when up to date").action(run((ctx, opts) => install(ctx, opts)));
97
- program.command("preview").description("Render two test frames per scene (at 30% and 90%) into out/preview/").action(run((ctx) => preview(ctx)));
109
+ program.command("preview").description("Render preview frames into out/preview/: two per scene (at 30% and 90%) and the frames either side of each scene change, with a report of what carries across").action(run((ctx) => preview(ctx)));
98
110
  program.command("render").description("Render the video to out/video.mp4").action(run((ctx) => render(ctx)));
99
111
 
100
112
  // Parses argv and runs the chosen command. Importing this module parses nothing; bin/reelkit.mjs calls main.