reelkit-cli 0.6.0 → 0.10.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 (144) hide show
  1. package/README.md +5 -3
  2. package/package.json +52 -9
  3. package/skill/SKILL.md +40 -19
  4. package/skill/THIRD_PARTY.md +104 -2
  5. package/skill/commands/launch-film.md +7 -0
  6. package/skill/reference/art-styles.md +70 -0
  7. package/skill/reference/asset-reuse.md +13 -2
  8. package/skill/reference/backgrounds.md +63 -0
  9. package/skill/reference/beat-sync.md +25 -19
  10. package/skill/reference/brand-motion.md +62 -0
  11. package/skill/reference/captions.md +11 -5
  12. package/skill/reference/clips.md +3 -3
  13. package/skill/reference/component-authoring.md +1 -1
  14. package/skill/reference/continuity.md +26 -7
  15. package/skill/reference/delivery-review.md +40 -0
  16. package/skill/reference/hebrew-rtl.md +3 -4
  17. package/skill/reference/{remotion-composition.md → hyperframes-composition.md} +18 -11
  18. package/skill/reference/kit.md +144 -12
  19. package/skill/reference/launch-film.md +194 -0
  20. package/skill/reference/motion-design.md +18 -16
  21. package/skill/reference/scene-treatments.md +20 -0
  22. package/skill/reference/scriptwriting.md +4 -1
  23. package/skill/reference/sound-design.md +34 -12
  24. package/skill/reference/studio-editing.md +55 -0
  25. package/skill/reference/styles.md +9 -6
  26. package/skill/reference/three-d.md +135 -0
  27. package/skill/reference/voice-sync.md +108 -0
  28. package/src/agents.ts +23 -12
  29. package/src/api/client.ts +4 -1
  30. package/src/cli.ts +18 -7
  31. package/src/commands/assets.ts +314 -30
  32. package/src/commands/build.ts +148 -35
  33. package/src/commands/init.ts +1 -1
  34. package/src/commands/install.ts +1 -1
  35. package/src/commands/plan.ts +8 -5
  36. package/src/commands/ref.ts +5 -2
  37. package/src/contract/index.ts +5 -3
  38. package/src/hyperframes/Root.tsx +1 -0
  39. package/src/hyperframes/fonts.ts +54 -0
  40. package/src/hyperframes/frame.tsx +46 -0
  41. package/src/hyperframes/host.tsx +38 -0
  42. package/src/hyperframes/kit/Assemble3D.tsx +92 -0
  43. package/src/hyperframes/kit/BrandTransform3D.tsx +12 -0
  44. package/src/hyperframes/kit/BrowserFrame.tsx +83 -0
  45. package/src/{remotion → hyperframes}/kit/Camera.tsx +7 -5
  46. package/src/{remotion → hyperframes}/kit/Captions.tsx +34 -18
  47. package/src/hyperframes/kit/Card3D.tsx +211 -0
  48. package/src/{remotion → hyperframes}/kit/Carry.tsx +1 -1
  49. package/src/hyperframes/kit/ChapterFrame.tsx +68 -0
  50. package/src/{remotion → hyperframes}/kit/ClipLayer.tsx +1 -1
  51. package/src/{remotion → hyperframes}/kit/Counter.tsx +1 -1
  52. package/src/hyperframes/kit/CounterRoll.tsx +75 -0
  53. package/src/{remotion → hyperframes}/kit/Entrance.tsx +1 -1
  54. package/src/{remotion → hyperframes}/kit/FootageLayer.tsx +1 -1
  55. package/src/hyperframes/kit/GlassPanel.tsx +43 -0
  56. package/src/hyperframes/kit/Grounds.tsx +177 -0
  57. package/src/hyperframes/kit/Headline.tsx +97 -0
  58. package/src/hyperframes/kit/Hero3D.tsx +197 -0
  59. package/src/hyperframes/kit/HudOverlay.tsx +52 -0
  60. package/src/hyperframes/kit/ImageLayers.tsx +48 -0
  61. package/src/{remotion → hyperframes}/kit/KenBurnsImage.tsx +1 -1
  62. package/src/{remotion → hyperframes}/kit/KeyedClip.tsx +1 -1
  63. package/src/{remotion → hyperframes}/kit/Layers.tsx +1 -1
  64. package/src/{remotion → hyperframes}/kit/LowerThird.tsx +1 -1
  65. package/src/hyperframes/kit/Music.tsx +19 -0
  66. package/src/hyperframes/kit/NamedCursor.tsx +54 -0
  67. package/src/hyperframes/kit/Orbit3D.tsx +49 -0
  68. package/src/hyperframes/kit/Particles3D.tsx +74 -0
  69. package/src/hyperframes/kit/Place.tsx +12 -0
  70. package/src/hyperframes/kit/PromptBox.tsx +84 -0
  71. package/src/hyperframes/kit/Scene3D.tsx +70 -0
  72. package/src/hyperframes/kit/SceneFrame.tsx +96 -0
  73. package/src/{remotion → hyperframes}/kit/ScreenOverlay.tsx +1 -1
  74. package/src/{remotion → hyperframes}/kit/Sfx.tsx +1 -1
  75. package/src/hyperframes/kit/SoundCues.tsx +22 -0
  76. package/src/hyperframes/kit/TerminalLog.tsx +98 -0
  77. package/src/hyperframes/kit/Text3D.tsx +78 -0
  78. package/src/hyperframes/kit/TextOnImage.tsx +41 -0
  79. package/src/{remotion → hyperframes}/kit/TitleCard.tsx +1 -1
  80. package/src/{remotion → hyperframes}/kit/Voiceover.tsx +1 -1
  81. package/src/hyperframes/kit/Warp3D.tsx +59 -0
  82. package/src/hyperframes/kit/bg-math.ts +179 -0
  83. package/src/hyperframes/kit/brand-transform.ts +25 -0
  84. package/src/{remotion → hyperframes}/kit/caption-groups.ts +7 -3
  85. package/src/hyperframes/kit/caption-style.ts +45 -0
  86. package/src/hyperframes/kit/docs.ts +249 -0
  87. package/src/hyperframes/kit/image-layers-math.ts +115 -0
  88. package/src/hyperframes/kit/index.ts +74 -0
  89. package/src/hyperframes/kit/inter-bold-typeface.ts +3 -0
  90. package/src/{remotion → hyperframes}/kit/motion-math.ts +36 -2
  91. package/src/hyperframes/kit/music-math.ts +59 -0
  92. package/src/hyperframes/kit/quiet-three.ts +11 -0
  93. package/src/hyperframes/kit/sample-text.ts +55 -0
  94. package/src/hyperframes/kit/scene3d-context.ts +5 -0
  95. package/src/hyperframes/kit/seeded.ts +13 -0
  96. package/src/hyperframes/kit/sound-cues.ts +89 -0
  97. package/src/hyperframes/kit/sound-kinds.ts +135 -0
  98. package/src/{remotion → hyperframes}/kit/theme.ts +43 -39
  99. package/src/hyperframes/kit/three-fx-math.ts +192 -0
  100. package/src/hyperframes/kit/three-math.ts +145 -0
  101. package/src/hyperframes/kit/transition-math.ts +116 -0
  102. package/src/hyperframes/kit/ui-math.ts +145 -0
  103. package/src/hyperframes/kit/ui-theme.ts +25 -0
  104. package/src/hyperframes/kit/word-anchor.ts +107 -0
  105. package/src/hyperframes/math.ts +62 -0
  106. package/src/hyperframes/three.tsx +10 -0
  107. package/src/pipeline/beatsnap.ts +72 -0
  108. package/src/pipeline/review.ts +44 -10
  109. package/src/pipeline/schema.ts +51 -4
  110. package/src/pipeline/timing.ts +27 -1
  111. package/src/project/background.ts +33 -0
  112. package/src/project/chromakey.ts +1 -1
  113. package/src/project/layers.ts +60 -0
  114. package/src/project/manifest.ts +59 -14
  115. package/src/project/music.ts +19 -5
  116. package/src/project/project.ts +4 -1
  117. package/src/project/serve.ts +2 -2
  118. package/src/project/soundreport.ts +347 -0
  119. package/src/project/svgcheck.ts +21 -0
  120. package/src/render/component-preview.ts +11 -55
  121. package/src/render/contact-sheet.ts +39 -0
  122. package/src/render/continuity.ts +14 -4
  123. package/src/render/deps.ts +15 -3
  124. package/src/render/render.ts +62 -57
  125. package/src/render/serve.ts +31 -0
  126. package/src/render/sound-notes.ts +106 -0
  127. package/src/render/static-check.ts +15 -4
  128. package/src/render/validate.ts +4 -4
  129. package/src/render/word-check.ts +181 -0
  130. package/src/render/worker.ts +71 -0
  131. package/src/testing/conformance.ts +12 -0
  132. package/src/testing/fake-api.ts +4 -4
  133. package/src/testing/fixtures.ts +4 -1
  134. package/src/remotion/Root.tsx +0 -31
  135. package/src/remotion/kit/Music.tsx +0 -19
  136. package/src/remotion/kit/SceneFrame.tsx +0 -19
  137. package/src/remotion/kit/docs.ts +0 -124
  138. package/src/remotion/kit/index.ts +0 -29
  139. package/src/remotion/kit/music-math.ts +0 -42
  140. /package/src/{remotion → hyperframes}/kit/Icon.tsx +0 -0
  141. /package/src/{remotion → hyperframes}/kit/beat.ts +0 -0
  142. /package/src/{remotion → hyperframes}/kit/brand-icons.ts +0 -0
  143. /package/src/{remotion → hyperframes}/kit/media.ts +0 -0
  144. /package/src/{remotion → hyperframes}/types.ts +0 -0
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: backgrounds
3
+ description: Use when choosing what sits behind the whole film - a flat or mesh ground, an animated ground drawn in code, a looping video, a picture, or pictures that change from scene to scene - and how to get and use one.
4
+ ---
5
+
6
+ # Grounds
7
+
8
+ The ground is what is under everything. Choose one for the whole film and keep it: a film with a new look in every scene reads as a collage.
9
+
10
+ ## Which ground
11
+
12
+ - A flat colour or `BgMesh`: for interface and data films, where the product is the picture and the ground must stay out of the way.
13
+ - An animated ground drawn in code (`BgAurora`, `BgBeams`, and for a technical film `BgGrid`; `BgGrain` for a still, filmic ground): when the frame would otherwise be empty. No media is needed and they are cheap to render.
14
+ - A video ground (`BgVideo`): for mood and brand films. Always dim it (0.3 to 0.5) or blur it, and never put small text over it.
15
+ - Pictures that change (`BgSequence`): a set of related pictures that change on scene starts, never in the middle of a sentence and no more than once every few seconds. Keep one `dim` and one `tint` across all of them so the film stays one film. Use "fade" by default and "zoom" or "blur" for an energetic film.
16
+
17
+ ## Grade and Vignette darken a ground
18
+
19
+ `Grade` and `Vignette` laid over the film darken everything under them, and a ground drawn in code loses the most: a ground that looked right alone can go muddy under them, and on a near-black base it can all but vanish. `BgAurora`, `BgBeams`, `BgGrid` and `BgGrain` take `intensity` (0 to 1, default 0.6, which reads on a phone on its own). Raise it to 0.8 when Grade and Vignette sit on top, or when the base is near-black; lower it to 0.3 to 0.4 when type sits right on the colour fields and has to stay readable. Look at a preview frame with the whole film's layers on, not the ground alone.
20
+
21
+ ## Getting one
22
+
23
+ Search the library first: `reelkit assets search "<mood> abstract background" --kind clip`, and also `--kind overlay` and `--kind image`. Pull a good fit as the film's ground with `reelkit assets pull <id> --background`; for a picture that is one scene's ground, add `--scene <id>`. A video is only ever the ground of the whole film.
24
+
25
+ Generate only when nothing fits, because a clip uses the clip quota: `reelkit assets gen clip --background "<look>"` makes an abstract, slow, seamless clip with no people or text, and `reelkit assets gen image --background --scene <id>` a picture. For a set of pictures, give every prompt the same style sentence so that they look related. The user's own file: `reelkit assets upload <file> --background`.
26
+
27
+ All of these record the ground in `assets/background.json`. The composition reads it as `manifest.background` (`key` and, for a video, `durationSec`) and `manifest.scenes[i].background` (`key`).
28
+
29
+ ## Keeping the accent
30
+
31
+ Tint the ground with the film's one accent colour (`tint={palette.hero}`), so that a clip you did not make still belongs to the film.
32
+
33
+ ## Readable type
34
+
35
+ `reelkit check` notes a `BgVideo` with a `dim` under 0.2 when the plan has on-screen text, and a recorded ground the composition never draws. Preview the frames: if type is hard to read anywhere, raise `dim`, add `blur`, or move the type.
36
+
37
+ ## Example
38
+
39
+ ```tsx
40
+ import React from "react";
41
+ import { AbsoluteFill } from "reelkit/frame";
42
+ import { BgImage, BgSequence, BgVideo, SceneFrame, bgPerScene, palettes } from "reelkit/kit";
43
+ import type { VideoProps } from "reelkit/kit";
44
+
45
+ export const Video: React.FC<VideoProps> = ({ manifest, urls }) => {
46
+ const palette = palettes.darkTech;
47
+ const scenesWithPictures = manifest.scenes.some((s) => s.background);
48
+ return (
49
+ <AbsoluteFill>
50
+ {scenesWithPictures ? (
51
+ <BgSequence items={bgPerScene(manifest.scenes, manifest.scenes.map((s) => (s.background ? urls[s.background.key] : undefined)))} tint={palette.hero} dim={0.4} />
52
+ ) : manifest.background ? (
53
+ <BgVideo src={urls[manifest.background.key]} durationSec={manifest.background.durationSec} dim={0.4} tint={palette.hero} />
54
+ ) : (
55
+ <BgImage src={urls["assets/bg-film.png"]} dim={0.4} drift="left" />
56
+ )}
57
+ {manifest.scenes.map((s) => (
58
+ <SceneFrame key={s.id} from={s.startFrame} durationInFrames={s.durationFrames}>{null}</SceneFrame>
59
+ ))}
60
+ </AbsoluteFill>
61
+ );
62
+ };
63
+ ```
@@ -8,47 +8,53 @@ A video with music feels edited when things happen on the beat. The CLI puts the
8
8
 
9
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
10
 
11
- ## 2. Scene changes are on the beat already
11
+ ## 2. Scene changes: the voice first, the beat when it is close
12
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.
13
+ With a narrator, the narration sets where a scene ends: the next scene starts after the plan's `gap` of silence (`tight` 0.2 s, `normal` 0.3 s, `relaxed` 0.5 s; the last scene keeps 0.7 s after its last word). When the track has a tempo, a scene change moves to the nearest beat only if that is at most 4 frames away either way and leaves at least 2 frames after the last word; the next scene's voice starts with it. Every other change stays where the voice puts it. `reelkit assets voiceover` and `reelkit preview` say how many changes are on the beat ("3 of 5 scene changes land on the beat; the others follow the voice"): that is the truth, not a fault to fix. Do not shift `startFrame` yourself, and do not pad a scene to reach a beat: that is dead air. A film with no voice puts its cuts on the beat (`reference/launch-film.md`).
14
14
 
15
- ## 3. Inside a scene: bring each element in on a beat
15
+ ## 3. Inside a scene: the voice is the clock, the beat is for decoration
16
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`:
17
+ With a narrator, anything that shows or names a word goes on that word: a number, an icon, a list item, a headline, a banner. The voice is the clock for it, and a beat never moves it more than a few frames. The beat is the clock for what has no word: scene changes, a decorative pulse, a sound under a move, and every element of a film with no voice. Do not count words by hand and do not write your own index helper: the kit finds the word for you (`reference/voice-sync.md` has the whole method).
18
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`
19
+ - `wordFrame(s, "tasks")` is the frame inside the scene where the word starts (a phrase such as "whole week" gives its first word; `{ nth: 2 }` the second time it is said; `{ edge: "end" }` where it ends; `{ absolute: true }` adds `s.startFrame`). A word the scene does not say throws and lists the scene's words, so a typo stops the render and `reelkit check` reports it before then.
20
+ - `onWord(s, "tasks")` is the frame to START an entrance so that it is seen landing on the word: `wordFrame` minus 3 (`lead`), never below 0.
21
+ - `onWordBeat(s, "tasks", manifest.music)` is the same, with one rule for beat and word together: the entrance lands on a beat only if a beat falls within 3 frames AFTER the word starts (`window`); otherwise the word wins. It is never earlier than `onWord`, so a thing is never on screen more than 3 frames before the word that names it. Use it for the hero element when a track is set; use plain `onWord` for everything that must be exact.
22
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.
23
+ Nearest-beat snapping is wrong for an element that illustrates a word: the nearest beat is as often before the word as after it, and a banner that appears before "meeting" is said is a mistake. `nearestBeat`, `nextBeat` and `beatPulse` stay for the things the beat owns: `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. Put a decorative move on a strong beat (every fourth beat counts from the scene's first beat).
24
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.
25
+ Sound effects: the `at` of an `Sfx` is a frame from the scene start. A sound for a word's visual goes at that visual's frame (`onWord(s, "tasks") + 2`), under the voice at 0.3 or less (see "Sound levels" below).
26
26
 
27
27
  ```tsx
28
- import { Entrance, Music, nearestBeat, SceneFrame, Voiceover } from "reelkit/kit";
28
+ import { Entrance, Music, onWord, onWordBeat, SceneFrame, sceneById, Voiceover } from "reelkit/kit";
29
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
- };
30
+ const s = sceneById(manifest, "meet");
36
31
 
37
32
  <SceneFrame from={s.startFrame} durationInFrames={s.durationFrames}>
38
- <Entrance delay={onBeat(s, 2)}><Card /></Entrance>
33
+ {/* exact: the list item lands on its word */}
34
+ <Entrance delay={onWord(s, "tasks")}><Card /></Entrance>
35
+ {/* the hero: lands on a beat when one is within 3 frames after the word, otherwise on the word */}
36
+ <Entrance delay={onWordBeat(s, "builds", manifest.music)}><Card /></Entrance>
39
37
  {s.voiceoverKey ? <Voiceover src={urls[s.voiceoverKey]} /> : null}
40
38
  </SceneFrame>
41
39
  // Once, outside the scenes:
42
40
  {manifest.music ? <Music src={urls[manifest.music.key]} /> : null}
43
41
  ```
44
42
 
43
+ `onBeat(s, i)` with a hand-counted word index (the old way: `nearestBeat` of the word at index `i`) still works if you wrote it yourself, but do not start new work with it.
44
+
45
45
  `Music` ducks under the voice by itself and fades out over the last second; do not set its volume per scene.
46
46
 
47
+ ## Hits go where the eye sees the change
48
+
49
+ A `SceneFrame` transition (a zoom-through, blur, push or whip) is complete on the scene boundary, but the picture changes most half the transition's frames earlier: 4 frames before the boundary for the default 8, 3 for `transitionFrames={6}`. A hit on the boundary lands after the change and reads as late; the sound report measures it as a miss. Put the hit on the visual peak: `cuesOnChanges(scenes, { impact, whoosh })` does it for every change (the impact on the peak, the whoosh so that it ends there), and `changeFrame(scene)` gives the peak for one. The scene's `startFrame` still sits on the beat, so a hit 3 or 4 frames before it is within a tenth of a second of the beat too.
50
+
51
+ A camera zoom, a `zoomTo` and a punch are picture changes as well: put a hit on each with `cueOnCamera(frame)` (for a key: about 9 frames before the key's frame) or `cueOnCamera(frame, { punch: true })` (just after the punch's frame). A `Camera` inside a `SceneFrame` counts frames from the scene's start, so add the scene's `startFrame` for the absolute frame a cue needs.
52
+
47
53
  ## Sound levels
48
54
 
49
55
  Every sound you pull (`sfx` and `music`) is measured when it is pulled and levelled: a sound effect shorter than 3 seconds to a peak of -3 dBFS, a longer sound and the music to about -18 LUFS, never more than 18 dB either way. The gain is in `assets/library.json` (`gainDb`) and in the manifest's `soundGain`, and `Sfx` and `Music` apply it by themselves, so the same `volume` number sounds equally loud for every file. Then:
50
56
 
51
57
  - the voiceover is `1` (the default);
52
- - a sound effect is `0.2` to `0.45`; the default `0.35` is right for most, and a small tick can go lower;
53
- - the music is `0.5` when it plays alone and drops to `0.12` under the voice by itself (`volume` and `duckTo` on `Music`); leave both alone unless a preview of the sound is clearly wrong;
58
+ - a sound effect is `0.2` to `0.45`; the default `0.35` is right for most, and a small tick can go lower (a film with no voice uses the levels in `reference/launch-film.md` instead);
59
+ - the music is `0.5` when it plays alone and sits at `0.18` under the voice by itself (`volume` and `duckTo` on `Music`). It stays steady across the 0.2 to 0.5 s between sentences and comes up only in a real pause of 1.2 s or more (rising over 0.4 s, back down 0.25 s before the next word), so it does not pump; leave both alone unless `reelkit sound` says the music under the voice is outside 10 to 22 dB below it;
54
60
  - the render is mastered at the end to -14 LUFS with peaks under -1 dBTP, so do not try to make the whole video louder: balance the parts against each other.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: brand-motion
3
+ description: Choose branded transformations and 3D reveals by what the shot communicates, then pair their landing frames with sound.
4
+ ---
5
+
6
+ # Brand motion: what changes, when, and why
7
+
8
+ Use the user's actual name, logo, colours and product material. A branded transition should connect two ideas: a mark becomes the product surface, a product turns toward the viewer, or separate parts resolve into one name. Write that connection in the scene's notes before choosing the effect. Keep the settled identity readable; an effect is the journey, not the brand.
9
+
10
+ ## Choose the move by its purpose
11
+
12
+ | What the viewer needs to understand | Use | Why it fits | Sound |
13
+ |---|---|---|---|
14
+ | Recognise a product or wordmark as it faces them | `BrandTransform3D mode="turn"` | A restrained turn exposes the front, then stops | Soft sweep into one lock hit |
15
+ | Follow the same object from one position or idea to another | `mode="arc"`, or `Carry` / `Screen3D` across scenes | The curved arrival preserves the object as the visual thread | A short sweep, with a landing only if the new state matters |
16
+ | Feel the step from setup into the launch or payoff | `mode="launch"` | An object approaches from depth and settles at its final size | A rising sweep into an impact; duck the music around the hit |
17
+ | See parts becoming a whole | `Assemble3D` | Construction explains the relationship, rather than hiding it in a cut | Sparse build ticks and one lock at `assembleEnd` |
18
+ | Read a detailed screen, a sentence or a Hebrew wordmark | A 2D image or text with `Entrance` / `Carry` | Detail remains sharp and faces the viewer | Usually a small tap or silence |
19
+ | Notice a factual before/after or a new chapter | A clear cut or `SceneFrame` transition | Separation makes the change easier to compare | `cuesOnChanges`, or silence for a deliberate break |
20
+
21
+ Choose 3D when depth explains the product, reveals its shape, or makes the requested brand moment distinctive. A flat change is often better for reading, comparison and rapid instructions. More 3D shots are appropriate when the user requests a brand film or showreel; give them different jobs instead of repeating the same spin.
22
+
23
+ ## Keep the identity intact
24
+
25
+ - Use the supplied logo image without stretching, recolouring or replacing it with a guessed text logo. `Card3D src={urls[key]}` can carry the image in space; match its card proportions to the asset, and end with the actual mark facing the viewer. A flat `Img` is the best closing hold when the card frame would distract.
26
+ - `Text3D` and `Hero3D kind="logo"` are Latin word treatments, not faithful reconstructions of a supplied logo. Use them for a requested word treatment, and keep Hebrew or other unsupported scripts in 2D.
27
+ - Choose the light mood and surface finish from the agreed look: studio for a clear product, night for restrained premium depth, neon for an explicit tech look. Keep the brand palette across all shots.
28
+ - Finish the move before the reading hold. Budget the move plus about 1.5 seconds for a short name; longer copy needs longer. If that cannot fit, shorten the move, simplify the copy or choose 2D.
29
+ - Avoid moving the camera and object in opposite directions at once. Start with a fixed camera; add a small push only when it improves the shot.
30
+
31
+ ## A move and its sounds use one clock
32
+
33
+ `BrandTransform3D` goes **inside** `Scene3D`. It transforms its children and ends in a neutral pose; it does not generate sound or invent a logo. `brandTransformFrames` returns the same local `start` and `settle` used by the component. `cuesFor([start, settle], "transform")` places a sweep before the landing and a lock on it. Add the scene's `startFrame` exactly once when the cues are rendered outside that scene.
34
+
35
+ ```tsx
36
+ import { BrandTransform3D, Scene3D, SceneFrame, SoundCues, Text3D, brandTransformFrames, cuesFor } from "reelkit/kit";
37
+
38
+ const timing = { from: 6, frames: 32 };
39
+ const event = brandTransformFrames(timing);
40
+ const cues = cuesFor([event.start, event.settle], "transform", {
41
+ offset: s.startFrame, voice: true, sweepFrames: 10,
42
+ });
43
+
44
+ // These elements sit beside each other on the video's timeline.
45
+ <SceneFrame from={s.startFrame} durationInFrames={s.durationFrames}>
46
+ <Scene3D mood="studio">
47
+ <BrandTransform3D mode="turn" {...timing}>
48
+ <Text3D text="Reelkit" color={palette.hero} fit={0.72} />
49
+ </BrandTransform3D>
50
+ </Scene3D>
51
+ </SceneFrame>
52
+ <SoundCues cues={cues} sounds={{
53
+ "transform-sweep": urls["assets/lib/<whoosh-id>/clip.mp3"],
54
+ "transform-lock": urls["assets/lib/<impact-id>/clip.mp3"],
55
+ }} />
56
+ ```
57
+
58
+ Search and pull real sound files first. `sweepFrames` is the time from the file's start to its audible landing: listen to the chosen file and adjust it, instead of assuming every whoosh peaks at ten frames. Use `voice: true` while narration is heard. A build, a transformation and the final resolve are different events; do not stack all their hits on one landing.
59
+
60
+ ## Before changing an existing shot
61
+
62
+ Identify the observable problem: the logo is unreadable, the result arrives before its spoken word, the movement has no relationship to the next shot, or the sound lands late. Change that problem first. Keep a deliberate cut, an existing brand treatment and the user's chosen look when they already work. Render the start, middle, landing and held state, then listen to the final video. A sound report helps find missed hits; its density targets are guidance, not a reason to add noise to every movement.
@@ -17,10 +17,16 @@ The plan's `captions` field says: `"none"`, `"word"` (one word at a time) or `"p
17
17
 
18
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
19
 
20
+ ## What the user's answer means
21
+
22
+ - "No subtitles": `"none"`.
23
+ - "A few words at a time", "a phrase at a time", "a line", or no preference: `"phrase"` (the default). This is the group mode: a few words, never a whole sentence, never one word.
24
+ - "One word at a time", "word by word", "like the big pop captions": `"word"`. Only then is a single word shown. Do not pick `"word"` for "a few words": one word at a time is hard to read on a busy picture and the user did not ask for it.
25
+
20
26
  ## How words are grouped (`group`)
21
27
  - `"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.
28
+ - `"phrase"`: words are grouped the way they are spoken. A group ends at the end of a sentence (`.`, `?` or `!`: a group never spans two sentences), at a comma or a dash once it has three words, and at a pause of 0.35 s or more between words. It holds at most 3 words by default (`maxWords={4}` for more, up to 6) and 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. Hebrew and Arabic sentence marks work the same way.
29
+ - Without `group` (and without `perLine`) the groups are the same as `"phrase"`: 3 words, never one. Only `group="word"` (the plan's `"captions": "word"`) shows one word. `perLine` alone counts the words as it always did; use `group` for every new video.
24
30
  - `mode` (below) still decides how the words inside the group look.
25
31
 
26
32
  ## Pick one style for the whole video
@@ -35,10 +41,10 @@ With `"none"` the component draws nothing; better still, leave `<Captions>` out
35
41
  <Captions words={s.words} mode="pop" highlight={palette.hero} uppercase />
36
42
  ```
37
43
 
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.
44
+ 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): any accent works, because the word is on a chip with its own ink; leave it out for a yellow.
39
45
 
40
46
  ## Words per screen
41
- - With `group="phrase"` the groups are at most 6 words, and about 32 characters, by themselves. With `group="word"` it is one.
47
+ - With `group="phrase"` (or no `group`) the groups are at most 3 words, and about 32 characters, by themselves; `maxWords` raises it to at most 6. With `group="word"` it is one.
42
48
  - 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.
43
49
  - Never show a full sentence at once. Word-level timing reads better than sentence captions.
44
50
 
@@ -61,7 +67,7 @@ Do not mix modes between scenes. Set `highlight` to the palette's hero colour (c
61
67
 
62
68
  ## Legibility
63
69
  - Heavy sans-serif weight, large: about 5.5 percent of the width for line captions, 7.5 percent for pop. The component sets these.
64
- - Contrast of at least 4.5 to 1 against whatever is behind. The component adds a dark outline and shadow; on very bright footage also dim the footage.
70
+ - Contrast of at least 4.5 to 1 against whatever is behind, on any ground, with no colour chosen by you: the component puts the words on a dark rounded plate (about 58% opaque) in white, and the spoken word sits on a chip of `highlight` (the accent) with white or dark ink, whichever reads better on it (a `karaoke` fill has no chip: it uses the accent mixed toward white until it reads on the plate). Measured on the render: white ink on the plate is about 5:1 over a cream ground, 5:1 over a bright busy picture and 20:1 over a dark one. Do not set `color` to anything but white, and do not draw your own outline or box.
65
71
  - `uppercase` suits `pop` and short hooks; avoid it for long lines.
66
72
 
67
73
  ## Emphasis effects, with limits
@@ -28,7 +28,7 @@ A clip is a few seconds of generated video. It is the most expensive thing Reelk
28
28
  ## B-roll rules
29
29
  - The clip shows exactly what the sentence says, at the moment it says it. If the sentence is abstract, the clip is the wrong tool.
30
30
  - Never the same clip twice in a video, and no two clip scenes in a row that look alike.
31
- - Speech never runs more than 3 seconds without something changing on screen: a new clip, a new text beat, a cut to a graphic. A 5-second clip under a 9-second scene needs a text change or a second visual in the middle.
31
+ - Hold a clip while its action or emotion carries the speech. Add another visual when meaning or readability needs it; do not interrupt a useful hold to satisfy a change quota.
32
32
  - Text over a clip must stay legible. Use `<ClipLayer src={urls[s.clipKey]} dim={0.25} />` to darken it, keep headlines in the calm part of the picture, and look at the preview frames for contrast on both the early and the late frame.
33
33
 
34
34
  ## Green screen
@@ -40,7 +40,7 @@ A green-screen clip is a subject filmed on a flat pure green background. It is f
40
40
  - Layer it above the scene's background and below the captions:
41
41
 
42
42
  ```tsx
43
- import { Img } from "remotion";
43
+ import { Img } from "reelkit/frame";
44
44
  import { Captions, KeyedClip, SceneFrame } from "reelkit/kit";
45
45
 
46
46
  <SceneFrame from={s.startFrame} durationInFrames={s.durationFrames}>
@@ -82,7 +82,7 @@ A keyed clip is what makes real depth possible: things can sit behind the subjec
82
82
  - Check both preview frames for a green fringe on the edges and for the subject hiding something that must be read.
83
83
 
84
84
  ```tsx
85
- import { AbsoluteFill } from "remotion";
85
+ import { AbsoluteFill } from "reelkit/frame";
86
86
  import { BgMesh, Captions, KeyedClip, SceneFrame, fonts } from "reelkit/kit";
87
87
 
88
88
  <SceneFrame from={s.startFrame} durationInFrames={s.durationFrames}>
@@ -10,7 +10,7 @@ Run `reelkit assets search "<description>" --kind component`. If a library compo
10
10
 
11
11
  ## Rules for a component file
12
12
  - One component per file. File `NumberBadge.tsx` exports `export const NumberBadge: React.FC<NumberBadgeProps>`.
13
- - Imports only from `react`, `remotion` and `reelkit/kit`. A component never imports another component file.
13
+ - Imports only from `react`, `reelkit/frame` and `reelkit/kit`. A component never imports another component file.
14
14
  - Everything specific comes in through props: text, numbers, colours, timing. No baked-in copy, brand names, user details or asset paths.
15
15
  - Give every visual prop a sensible default so `<NumberBadge value={1} />` works on its own.
16
16
  - Size relative to `useVideoConfig()`, never fixed pixels.
@@ -1,15 +1,15 @@
1
1
  ---
2
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.
3
+ description: Use before the plan and again when writing the composition - how to keep a video from feeling like a slideshow, with concepts chosen from the brief, what to carry across every scene change, how to vary rhythm, and how to read the continuity and rhythm numbers the CLI reports.
4
4
  ---
5
5
 
6
6
  # Continuity and rhythm
7
7
 
8
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
9
 
10
- ## Three concepts before a plan
10
+ ## A concept before a new plan
11
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.
12
+ A concept is one sentence about the picture, not about the product. Before a new `plan.json`, recommend the strongest concept that fits the brief; offer alternatives if the user wants a comparison. Keep an already chosen concept for revisions. For each one say the opening frame, what carries each scene change, and what it rules out.
13
13
 
14
14
  Ideas to draw from (invent your own when the topic asks for it):
15
15
 
@@ -19,7 +19,7 @@ Ideas to draw from (invent your own when the topic asks for it):
19
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
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
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.
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." Honor the chosen direction; write it into the first scene's `notes`, next to the look.
23
23
 
24
24
  ## Carry at every scene change
25
25
 
@@ -32,13 +32,13 @@ Ways to do it with the kit:
32
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
33
  - A shared colour field that one scene's object expands into. The new scene is the colour the old object grew to.
34
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.
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. Say so in the plan: give the scene that begins on the cut `"cutIn": true`. `reelkit preview` then reports that change as `cut (intended)` and leaves it out of the score.
36
36
 
37
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
38
 
39
39
  ```tsx
40
40
  import React from "react";
41
- import { AbsoluteFill } from "remotion";
41
+ import { AbsoluteFill } from "reelkit/frame";
42
42
  import { BgMesh, Carry, Entrance, fonts, palettes, SceneFrame } from "reelkit/kit";
43
43
  import type { VideoProps } from "reelkit/kit";
44
44
 
@@ -90,10 +90,29 @@ For product pictures, the default is a small element on a big ground: the card,
90
90
 
91
91
  ## Reading the numbers
92
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.
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. The later scene is sampled twice, at its first fully visible frame and about six frames on, and the better of the two is taken, so a plate that settles into a card is not called a cut. A scene with `cutIn: true` is reported as `cut (intended)` and not counted.
94
94
 
95
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
96
 
97
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
98
 
99
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.
100
+
101
+ ## Transitions
102
+
103
+ `SceneFrame` fades by default. Choose another transition with `enter` and `exit` (the same kind on both sides of a change), `transitionFrames` (8 by default, at most 20) and, for a zoom, `origin` (where it aims, as fractions of the frame). Each is complete exactly at the scene boundary, so the cut stays on the beat. A transition is a choice with a meaning:
104
+
105
+ - `zoom-through`: going INTO something. The old scene rushes toward a point and the new one arrives from there: into a screen, a detail, a product.
106
+ - `push-left`, `push-right`, `push-up`, `push-down`: moving along a sequence, such as the next step or the next card in a row.
107
+ - `whip-left`, `whip-right`: a quick list, one item after another, with a smear that carries the eye.
108
+ - `turn`: a before and after, or two sides of the same thing.
109
+ - `zoom-in`, `zoom-out`, `blur`: a soft change of subject or mood.
110
+ - `cut`: a burst of hits, where speed matters more than smoothness (mark it with `cutIn` in the plan).
111
+ - `fade`: the default, for a calm change.
112
+
113
+ Rules: use at most two kinds in one video. Never the same exit twice in a row for different meanings: a push that always means "next" is a rhythm, but a push for one thing and a push for another is noise. A carried element (`Carry`, `Camera`) beats any transition: if something stays on screen and moves into the next scene, the change needs no transition at all, so leave it `fade` or `cut`. `reelkit preview` takes its boundary frames clear of a literal `transitionFrames={N}` in the code.
114
+
115
+ ```tsx
116
+ <SceneFrame from={a.startFrame} durationInFrames={a.durationFrames} exit="zoom-through" origin={{ x: 0.5, y: 0.45 }}>...</SceneFrame>
117
+ <SceneFrame from={b.startFrame} durationInFrames={b.durationFrames} enter="zoom-through" origin={{ x: 0.5, y: 0.45 }}>...</SceneFrame>
118
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: delivery-review
3
+ description: Verify a finished Reelkit export: layout, word and sound timing, phone playback, platform margins, media integrity and honest delivery reporting.
4
+ ---
5
+
6
+ # Verify the deliverable
7
+
8
+ Review against the original request and actual source material. A render completing proves that a file was encoded; it does not prove that the edit works. Fix observed defects and inspect the changed moments. Stop when the requested result is achieved, without mandatory review rounds or a made-up quality score.
9
+
10
+ ## Before a full render
11
+
12
+ Run `reelkit check` and `reelkit preview`. Inspect `out/preview/sheet.jpg`, then the important individual frames: opening, both sides of cuts, spoken triggers, transformation landing, readable hold and ending. The normal early and late samples can miss a brief overlap, so inspect the moving export or extract additional frames around a suspicious event. Do not invent a preview timestamp flag.
13
+
14
+ - Does each moment have a clear focal point and a reason to be there?
15
+ - Are text, captions and supplied logos readable, correctly spelled and clear of faces or controls?
16
+ - Does each claim match the user's facts and source? Are illustrative surfaces distinguishable from the real product?
17
+ - Do objects settle when their words or events happen? Do old labels leave before the replacement is readable?
18
+ - Are empty states, media edges and transparency intentional? Is the ending held long enough to read?
19
+
20
+ Preview holds as well as entrances. For a loop, compare the first frame with the loop boundary and check audio continuity; matching stills alone does not prove a seamless moving loop.
21
+
22
+ ## Phone and destination layout
23
+
24
+ Default a new short-form video to 9:16 unless the destination or user specifies otherwise. Keep critical content clear of likely platform chrome. For 1080x1920, a conservative working region leaves about 250px at the top, 350px at the bottom, 80px on the sides and extra room on the lower right. These are starting margins, not a guarantee for every app, device or caption expansion. Scale with the output dimensions and inspect the named destination when available. Do not add two platform versions automatically.
25
+
26
+ If variants are requested, change the layout in the source and render each variant; moving a finished frame can crop another element. Keep filenames explicit and retain earlier deliverables when revising. Do not publish or upload the result as part of a local export request.
27
+
28
+ ## Check the finished file
29
+
30
+ Confirm the file exists. Use `ffprobe` when available to check actual codecs, dimensions, frame rate and duration against the requested result; decode with `ffmpeg` when corruption or missing media is suspected. Reelkit normally renders H.264/AAC MP4 and masters the mix near -14 LUFS. Inspect the reported result before applying another loudness pass. A duration mismatch may be a short audio tail; investigate before changing scene timing.
31
+
32
+ Play the export when playback is available, and listen to its speech, music and effects. Speech must be intelligible; intended music and cues must actually be present. Check the first hit, a middle word, the reveal landing and the ending for sync, unexpected silence, truncated tails and clipping. Check the final mix, not only the source stems. Avoid certifying subjective audio quality when you only ran measurements; state what was verified and what could not be auditioned.
33
+
34
+ `reelkit sound --detail` reports measurable hits, swells and nearby picture changes. Use it to locate a suspected mismatch, not to fill a quota. A calm hold can have no effect, and repeated music beats can inflate hit counts. Audition a small-speaker or reduced-bass mix when available; if only analysis is possible, look for music that vanishes without its bass and report the limitation. Do not treat a fixed energy percentage or a stem-correlation threshold as proof that the mix sounds good.
35
+
36
+ If a speed change or cut shifted timing, correct the source event map and rerender. Do not offset the whole audio track to repair one bad cue. If clipping exists before mastering, lower overlapping source levels; loudness normalization cannot restore an already clipped waveform.
37
+
38
+ ## Deliver
39
+
40
+ Link or display the final video and give its project path. Briefly explain the main creative choices and why they fit, including the music when used. Report checks actually performed, material limitations and generator usage only when known. Do not claim credits were measured when the provider only exposes a remaining allowance. Keep the plan, source and media together so the edit can be revised.
@@ -15,7 +15,7 @@ Most broken Hebrew videos break in the same few ways. These rules are for text d
15
15
 
16
16
  ## Direction
17
17
  - Give every Hebrew text container `direction: "rtl"`. Without it the full stop, comma and exclamation mark jump to the wrong end of the line.
18
- - Digits and Latin words inside a Hebrew sentence order themselves when the base direction is RTL. A label that is entirely Latin or digits gets `direction: "ltr"`.
18
+ - Isolate Latin terms, URLs and technical identifiers in their own LTR span or bidi-isolated element inside the RTL container. A label that is entirely Latin or digits gets `direction: "ltr"`. Check numbers and punctuation in the rendered sentence, not just the source.
19
19
  - Everything laid out in reading order runs right to left: the first word on the right, the first list item on the right or top, tabs, chat bubbles, steps, and charts over time (the first day on the right).
20
20
  - **The exception:** progress bars, sliders, loading bars and media timelines still fill left to right, as every player does.
21
21
  - A typing effect adds characters in reading order; the caret sits at the left end of the text, where it grows.
@@ -29,8 +29,7 @@ Most broken Hebrew videos break in the same few ways. These rules are for text d
29
29
  - This overrides the masked rise that `reference/motion-design.md` recommends for Latin headlines.
30
30
 
31
31
  ## Hyphens and punctuation
32
- - **Never join two Hebrew words with a hyphen on screen**, in headlines, labels or interface text. Write the two words with a space.
33
- - No em dash and no en dash on screen. Use a comma, a full stop or a new line.
32
+ - For newly written display copy, prefer clear punctuation and breaks rather than joining words with decorative dashes. Preserve the user's exact supplied wording; do not silently remove a hyphen or punctuation from a quote.
34
33
  - A prefix letter before a number or a Latin word ("ב-2026") is correct Hebrew, but on screen prefer wording that avoids it.
35
34
  - Quote the user's text exactly, letter for letter. Never "fix" spelling, add vowel points or swap a final letter.
36
35
 
@@ -45,7 +44,7 @@ Most broken Hebrew videos break in the same few ways. These rules are for text d
45
44
 
46
45
  ## Check before you show
47
46
  1. Every word in the right order, right to left, spelled exactly as given.
48
- 2. No hyphen between words, no long dash.
47
+ 2. Punctuation matches the supplied wording and renders at the correct end of the phrase.
49
48
  3. No letter cut by the frame edge or by a mask, in the early frame or the late one.
50
49
  4. Progress bars fill left to right; everything else runs right to left.
51
50
  5. The full stop and exclamation mark sit at the left end of the last word.
@@ -1,14 +1,16 @@
1
1
  ---
2
- name: remotion-composition
2
+ name: hyperframes-composition
3
3
  description: Use when writing Video.tsx - the file rules, timing model and media rules every composition must follow.
4
4
  ---
5
5
 
6
6
  # Writing the composition
7
7
 
8
+ Reelkit uses HyperFrames with a seekable React adapter. Author TSX in `src/Video.tsx`; `reelkit/frame` supplies the frame clock, sequences, media, and animation math. The generated HTML exposes HyperFrames' seek protocol for capture and rendering.
9
+
8
10
  ## Files
9
11
  - `Video.tsx` is required and must contain `export const Video: React.FC<VideoProps> = ({ manifest, urls }) => ...`.
10
12
  - Extra component files are flat siblings, PascalCase, one component per file: `NumberBadge.tsx` exports `NumberBadge`. Import them in Video.tsx as `./NumberBadge`.
11
- - Video.tsx may import only `react`, `remotion`, `reelkit/kit` and sibling components. No other packages, no network, no file access, no `process`.
13
+ - Video.tsx may import only `react`, `reelkit/frame`, `reelkit/kit` and sibling components. No other packages, no network, no file access, no `process`.
12
14
 
13
15
  ## What the composition receives
14
16
  `Video` gets two props, `{ manifest, urls }` (type `VideoProps`, exported by `reelkit/kit`). `manifest` is the content of `manifest.json` in the project, rewritten by `reelkit assets voiceover`, `check`, `preview` and `render` from `plan.json` and the recorded voiceovers. It is not hand-edited. `urls` is separate: it is built at render time and is not in `manifest.json`.
@@ -22,13 +24,13 @@ description: Use when writing Video.tsx - the file rules, timing model and media
22
24
 
23
25
  Each `scenes` entry:
24
26
  - `id`: the scene id from `plan.json`.
25
- - `startFrame`, `durationFrames`: frames on the whole video's timeline. A scene lasts its narration plus 0.4 s of breathing room, rounded up to a whole frame.
27
+ - `startFrame`, `durationFrames`: frames on the whole video's timeline. A scene lasts until its last word has ended plus the plan's `gap` (0.3 s by default; the next scene's voice starts on the scene's first frame), rounded up to a whole frame; the last scene keeps 0.7 s. A change may move up to 4 frames to reach a beat.
26
28
  - `voiceoverKey`: the project path of the narration audio.
27
- - `words`: one entry per spoken word, `{ word, startSec, endSec }`, in seconds from the start of that scene (not of the video, and not frames). Convert with `Math.round(sec * fps)` for a frame inside the scene; `<Captions words={s.words} />` takes them as they are.
29
+ - `words`: one entry per spoken word, `{ word, startSec, endSec }`, in seconds from the start of that scene (not of the video, and not frames). Do not convert them yourself: `wordFrame(s, "word")` and `onWord(s, "word")` give the frame inside the scene (reference/voice-sync.md); `<Captions words={s.words} />` takes them as they are.
28
30
  - `imageKey`: only on an illustration scene that has its image; the project path of the image.
29
31
  - `userAssetKeys`: project paths of the user's own files that the plan assigned to this scene (an empty array when none).
30
32
 
31
- `urls` maps a project path (every file under `assets/`, except `.json` files) to the address Remotion can load it from. Pass paths from the manifest, or one you pulled, such as `urls["assets/lib/<id>/clip.mp3"]`; never build an address yourself.
33
+ `urls` maps a project path (every file under `assets/`, except `.json` files) to the address HyperFrames can load it from. Pass paths from the manifest, or one you pulled, such as `urls["assets/lib/<id>/clip.mp3"]`; never build an address yourself.
32
34
 
33
35
  ```json
34
36
  {
@@ -49,13 +51,14 @@ Each `scenes` entry:
49
51
 
50
52
  ## Media
51
53
  - Reference media only as `urls[path]`, where `path` is the file's project path, using the paths in the manifest: `s.voiceoverKey`, `s.imageKey`, `s.clipKey`, `s.clipKeyedKey`, `s.userAssetKeys`, `manifest.footageKey`. Never hard-code a URL.
52
- - A scene with narration has `s.voiceoverKey`: give it exactly one `<Voiceover src={urls[s.voiceoverKey]} />` inside its SceneFrame, and `<Captions words={s.words} />` unless the notes say otherwise.
54
+ - A scene with narration has `s.voiceoverKey`: give it exactly one `<Voiceover src={urls[s.voiceoverKey]} />` inside its SceneFrame, and `<Captions words={s.words} />` unless the notes say otherwise. In a plan with `voice: "none"` no scene has a `voiceoverKey` and `s.words` is empty, so render no `<Voiceover>` and no `<Captions>` (`manifest.captions` is `"none"`); keep the guard `s.voiceoverKey ? ... : null` so the same code works either way.
53
55
  - Footage mode (`manifest.footageKey` is set): one `<FootageLayer>` at the bottom, outside the scenes.
54
- - User images: `<Img src={urls[key]} />` from remotion.
56
+ - User images: `<Img src={urls[key]} />` from reelkit/frame.
57
+ - Write a media path as one plain string, so that `reelkit check` can verify the file exists. A path built with a template string or by joining parts (`` `assets/lib/${id}/hit.mp3` ``) cannot be checked and is reported as missing. Write `"assets/lib/sfx-ab12/hit.mp3"` in full, or read the key from the manifest.
55
58
 
56
59
  ## Layer stack (every video, bottom to top)
57
- 1. Background: `<BgMesh>` from the kit, or `<FootageLayer>` in footage mode. Never a flat solid colour.
58
- 2. Images: `<KenBurnsImage>` inside its scene. Every still image moves; alternate `direction` between consecutive image scenes.
60
+ 1. Background: a chosen field, picture, `<BgMesh>` or `<FootageLayer>` in footage mode. A flat colour is valid when it serves the design.
61
+ 2. Images: `<KenBurnsImage>` when a camera move helps, or a stable image for reading and comparison. Keep important details in frame.
59
62
  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
63
  4. Graphics and type, inside each scene's SceneFrame.
61
64
  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`.
@@ -69,9 +72,9 @@ Each `scenes` entry:
69
72
  - Run `reelkit assets pull <id>` before referencing an overlay. It lands in `assets/lib/<id>/`; reference it as `urls["assets/lib/<id>/<file>"]`, and read its duration from `assets/library.json`.
70
73
 
71
74
  ## Animation rules
72
- - **No linear motion.** Every `interpolate()` takes an `easing` from the kit's `ease` and both `extrapolateLeft: "clamp"` and `extrapolateRight: "clamp"`. Entrances use `spring()` with a kit `springs` preset.
75
+ - Choose easing or a spring to fit the move; linear interpolation is valid for steady motion. Clamp finite interpolation ranges with `extrapolateLeft: "clamp"` and `extrapolateRight: "clamp"` so elements do not drift beyond their intended pose.
73
76
  - **Entrances move two or three properties together** (opacity, position, scale). Wrap content in `<Entrance>` rather than fading alone.
74
- - **Stagger, never simultaneous.** Words 3 frames apart, list items and cards 4 to 5, big blocks 6.
77
+ - Stagger related items when that improves hierarchy; simultaneous motion is valid for one coordinated action. Spoken words follow their real timestamps rather than a fixed stagger.
75
78
  - **Exits exist and are faster than entrances** (about 10 frames against 20). Use `<Entrance exitAt={...}>` or the SceneFrame fade.
76
79
  - **A missing clamp shows elements before their entrance or after their exit.** Check for it.
77
80
  - Derive timing from `fps` in `useVideoConfig()`, not bare frame counts, when you express a duration in seconds.
@@ -90,3 +93,7 @@ Each `scenes` entry:
90
93
  1. `reelkit assets search "<description>" --kind component`, and `reelkit assets pull <id>` for any you plan to use (it lands in `src/`).
91
94
  2. Write each new component in `src/`, then `src/Video.tsx`.
92
95
  3. `reelkit check`. Fix every error it reports and check again until it passes.
96
+
97
+ ## Revising a shot in HyperFrames
98
+
99
+ Every pose must be computable from the current frame, including a direct seek to the landing or held state. Use `reelkit/frame` timing helpers; avoid CSS animations, wall-clock timers, and state accumulated by previous frames. Derive sound from the same event frames as the visual. Inside a `SceneFrame` timing is local; outside it add `startFrame` once. For a branded arrival use `BrandTransform3D` and `brandTransformFrames` (`reference/brand-motion.md`). Preview early, middle and held states after retiming, and listen to the rendered file after changing sound.