reelkit-cli 0.8.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/package.json +49 -10
- package/skill/.claude-plugin/plugin.json +7 -0
- package/skill/SKILL.md +44 -19
- package/skill/THIRD_PARTY.md +37 -5
- package/skill/commands/launch-film.md +1 -1
- package/skill/hooks/hooks.json +1 -0
- package/skill/hooks/player.ts +278 -0
- package/skill/hooks/project.ts +227 -0
- package/skill/hooks/register.tsx +533 -0
- package/skill/hooks/timeline.ts +218 -0
- package/skill/reference/art-styles.md +70 -0
- package/skill/reference/backgrounds.md +1 -1
- package/skill/reference/brand-motion.md +62 -0
- package/skill/reference/clips.md +3 -3
- package/skill/reference/component-authoring.md +3 -1
- package/skill/reference/continuity.md +5 -5
- package/skill/reference/delivery-review.md +40 -0
- package/skill/reference/hebrew-rtl.md +3 -4
- package/skill/reference/higgsfield-production.md +70 -0
- package/skill/reference/{remotion-composition.md → hyperframes-composition.md} +14 -8
- package/skill/reference/kit.md +7 -3
- package/skill/reference/launch-film.md +13 -9
- package/skill/reference/motion-design.md +26 -22
- package/skill/reference/sound-design.md +34 -12
- package/skill/reference/studio-editing.md +55 -0
- package/skill/reference/studio-pane.md +31 -0
- package/skill/reference/style-components.md +40 -0
- package/skill/reference/style-recipes.md +86 -0
- package/skill/reference/styles.md +17 -10
- package/skill/reference/three-d.md +8 -7
- package/skill/reference/voice-sync.md +1 -1
- package/skill/tests/studio.test.ts +111 -0
- package/skill/types/index.d.ts +65 -0
- package/src/commands/assets.ts +5 -1
- package/src/contract/index.ts +1 -1
- package/src/hyperframes/Root.tsx +1 -0
- package/src/hyperframes/fonts.ts +54 -0
- package/src/hyperframes/frame.tsx +46 -0
- package/src/hyperframes/host.tsx +38 -0
- package/src/{remotion → hyperframes}/kit/Assemble3D.tsx +1 -1
- package/src/hyperframes/kit/BrandTransform3D.tsx +12 -0
- package/src/{remotion → hyperframes}/kit/BrowserFrame.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Camera.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Captions.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Card3D.tsx +2 -2
- package/src/{remotion → hyperframes}/kit/Carry.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ChapterFrame.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ClipLayer.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Counter.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/CounterRoll.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Entrance.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/FootageLayer.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/GlassPanel.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Grounds.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Headline.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Hero3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/HudOverlay.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ImageLayers.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/KenBurnsImage.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/KeyedClip.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Layers.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/LowerThird.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Music.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/NamedCursor.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Orbit3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Particles3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Place.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/PromptBox.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Scene3D.tsx +2 -2
- package/src/{remotion → hyperframes}/kit/SceneFrame.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ScreenOverlay.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Sfx.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/SoundCues.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/TerminalLog.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Text3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/TextOnImage.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/TitleCard.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Voiceover.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Warp3D.tsx +1 -1
- package/src/hyperframes/kit/brand-transform.ts +25 -0
- package/src/{remotion → hyperframes}/kit/docs.ts +6 -2
- package/src/{remotion → hyperframes}/kit/index.ts +3 -0
- package/src/{remotion → hyperframes}/kit/sound-cues.ts +1 -1
- package/src/{remotion → hyperframes}/kit/sound-kinds.ts +14 -1
- package/src/{remotion → hyperframes}/kit/theme.ts +42 -40
- package/src/{remotion → hyperframes}/kit/ui-math.ts +1 -1
- package/src/hyperframes/math.ts +62 -0
- package/src/hyperframes/three.tsx +10 -0
- package/src/project/chromakey.ts +1 -1
- package/src/project/layers.ts +1 -1
- package/src/project/serve.ts +2 -2
- package/src/render/component-preview.ts +11 -55
- package/src/render/render.ts +62 -64
- package/src/render/serve.ts +31 -0
- package/src/render/static-check.ts +15 -4
- package/src/render/validate.ts +4 -4
- package/src/render/word-check.ts +1 -1
- package/src/render/worker.ts +71 -0
- package/src/testing/fixtures.ts +1 -1
- package/src/remotion/Root.tsx +0 -31
- /package/src/{remotion → hyperframes}/kit/Icon.tsx +0 -0
- /package/src/{remotion → hyperframes}/kit/beat.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/bg-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/brand-icons.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/caption-groups.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/caption-style.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/image-layers-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/inter-bold-typeface.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/media.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/motion-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/music-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/quiet-three.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/sample-text.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/scene3d-context.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/seeded.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/three-fx-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/three-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/transition-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/ui-theme.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/word-anchor.ts +0 -0
- /package/src/{remotion → hyperframes}/types.ts +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { Film } from '../types'
|
|
2
|
+
|
|
3
|
+
const CLIP_COLORS = ['#2563eb', '#7c3aed', '#db2777', '#ea580c', '#059669', '#0891b2']
|
|
4
|
+
const SVG_LIMIT = 120000
|
|
5
|
+
|
|
6
|
+
const xml = (text: string): string =>
|
|
7
|
+
text.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] ?? c)
|
|
8
|
+
|
|
9
|
+
// Text cut to what fits a width, at about 6 px a character for the 11 px face the timeline uses.
|
|
10
|
+
const fit = (text: string, width: number, per = 6.2): string => {
|
|
11
|
+
const room = Math.floor(width / per)
|
|
12
|
+
if (room < 2) return ''
|
|
13
|
+
return text.length <= room ? text : `${text.slice(0, Math.max(1, room - 1))}…`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const clock = (seconds: number): string => {
|
|
17
|
+
const whole = Math.max(0, seconds)
|
|
18
|
+
return `${Math.floor(whole / 60)}:${(whole % 60).toFixed(1).padStart(4, '0')}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A ruler step that leaves room between labels: the smallest of these that gives at most twelve of them.
|
|
22
|
+
const rulerStep = (seconds: number): number => [0.5, 1, 2, 5, 10, 15, 30, 60].find(step => seconds / step <= 12) ?? 120
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The film as an editor's timeline, one SVG: a ruler, the scenes as clips with a filmstrip, the on-screen text, the voice with a mark per
|
|
26
|
+
* spoken word, the music with a tick per beat, and a playhead at the selected scene. `thumbs` maps a scene id to its picture, base64 JPEG.
|
|
27
|
+
*/
|
|
28
|
+
export function timelineSvg(film: Film, selected: number, thumbs: Record<string, string>): string {
|
|
29
|
+
const W = 760
|
|
30
|
+
const X0 = 58
|
|
31
|
+
const TRACK = W - X0 - 10
|
|
32
|
+
const total = Math.max(1, film.totalFrames)
|
|
33
|
+
const seconds = total / film.fps
|
|
34
|
+
const x = (frame: number): number => X0 + (frame / total) * TRACK
|
|
35
|
+
const xs = (sec: number): number => X0 + (sec / seconds) * TRACK
|
|
36
|
+
const rows = { ruler: 8, video: 30, text: 92, voice: 118, music: 148 }
|
|
37
|
+
const H = 180
|
|
38
|
+
|
|
39
|
+
const build = (withThumbs: boolean): string => {
|
|
40
|
+
const out: string[] = []
|
|
41
|
+
out.push(
|
|
42
|
+
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" font-family="-apple-system,Segoe UI,Helvetica,Arial,sans-serif" font-size="11">`,
|
|
43
|
+
`<rect width="${W}" height="${H}" rx="10" fill="#15161a"/>`,
|
|
44
|
+
)
|
|
45
|
+
const defs: string[] = []
|
|
46
|
+
const body: string[] = []
|
|
47
|
+
|
|
48
|
+
// Track names and lanes.
|
|
49
|
+
for (const [label, y, h] of [
|
|
50
|
+
['Video', rows.video, 56],
|
|
51
|
+
['Text', rows.text, 20],
|
|
52
|
+
['Voice', rows.voice, 24],
|
|
53
|
+
['Music', rows.music, 20],
|
|
54
|
+
] as const) {
|
|
55
|
+
body.push(
|
|
56
|
+
`<text x="10" y="${y + h / 2 + 4}" fill="#8b8d98">${label}</text>`,
|
|
57
|
+
`<rect x="${X0}" y="${y}" width="${TRACK}" height="${h}" rx="4" fill="#1e2026"/>`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Ruler.
|
|
62
|
+
const step = rulerStep(seconds)
|
|
63
|
+
for (let t = 0; t <= seconds + 0.001; t += step) {
|
|
64
|
+
const at = xs(t).toFixed(1)
|
|
65
|
+
body.push(
|
|
66
|
+
`<line x1="${at}" y1="${rows.ruler + 12}" x2="${at}" y2="${rows.ruler + 18}" stroke="#4b4d57"/>`,
|
|
67
|
+
`<text x="${at}" y="${rows.ruler + 9}" fill="#8b8d98" font-size="10" text-anchor="${t === 0 ? 'start' : 'middle'}">${clock(t)}</text>`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
film.scenes.forEach((scene, i) => {
|
|
72
|
+
const left = x(scene.start) + 1
|
|
73
|
+
const width = Math.max(3, x(scene.start + scene.frames) - x(scene.start) - 2)
|
|
74
|
+
const color = CLIP_COLORS[i % CLIP_COLORS.length]
|
|
75
|
+
const isPicked = i === selected
|
|
76
|
+
const picture = withThumbs ? thumbs[scene.id] : undefined
|
|
77
|
+
|
|
78
|
+
// Video clip, with its picture repeated along it as a filmstrip.
|
|
79
|
+
defs.push(`<clipPath id="c${i}"><rect x="${left.toFixed(1)}" y="${rows.video}" width="${width.toFixed(1)}" height="56" rx="5"/></clipPath>`)
|
|
80
|
+
body.push(`<rect x="${left.toFixed(1)}" y="${rows.video}" width="${width.toFixed(1)}" height="56" rx="5" fill="${color}" opacity="0.9"/>`)
|
|
81
|
+
if (picture) {
|
|
82
|
+
const [aw, ah] = film.aspect.split(':').map(Number)
|
|
83
|
+
const tile = Math.max(18, Math.round((56 * (aw || 9)) / (ah || 16)))
|
|
84
|
+
defs.push(
|
|
85
|
+
`<image id="t${i}" width="${tile}" height="56" preserveAspectRatio="xMidYMid slice" xlink:href="data:image/jpeg;base64,${picture}"/>`,
|
|
86
|
+
)
|
|
87
|
+
const copies = Math.min(24, Math.ceil(width / tile))
|
|
88
|
+
const strip = Array.from({ length: copies }, (_, n) => `<use xlink:href="#t${i}" x="${(left + n * tile).toFixed(1)}" y="${rows.video}"/>`)
|
|
89
|
+
body.push(`<g clip-path="url(#c${i})">${strip.join('')}</g>`)
|
|
90
|
+
}
|
|
91
|
+
body.push(
|
|
92
|
+
`<g clip-path="url(#c${i})"><rect x="${left.toFixed(1)}" y="${rows.video + 40}" width="${width.toFixed(1)}" height="16" fill="#000" opacity="0.55"/>` +
|
|
93
|
+
`<text x="${(left + 5).toFixed(1)}" y="${rows.video + 52}" fill="#fff" font-weight="600">${xml(fit(`${i + 1} ${scene.id}`, width - 8))}</text></g>`,
|
|
94
|
+
)
|
|
95
|
+
if (isPicked) {
|
|
96
|
+
body.push(
|
|
97
|
+
`<rect x="${left.toFixed(1)}" y="${rows.video}" width="${width.toFixed(1)}" height="56" rx="5" fill="none" stroke="#fff" stroke-width="2"/>`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// On-screen text.
|
|
102
|
+
const first = scene.text[0]
|
|
103
|
+
if (first) {
|
|
104
|
+
body.push(
|
|
105
|
+
`<rect x="${left.toFixed(1)}" y="${rows.text}" width="${width.toFixed(1)}" height="20" rx="4" fill="#3b2a66"/>`,
|
|
106
|
+
`<text x="${(left + 5).toFixed(1)}" y="${rows.text + 14}" fill="#d6c9ff">${xml(fit(first, width - 8))}</text>`,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Voice: a block for the scene's narration and a mark at each word, drawn as a rough waveform.
|
|
111
|
+
if (scene.narration) {
|
|
112
|
+
body.push(`<rect x="${left.toFixed(1)}" y="${rows.voice}" width="${width.toFixed(1)}" height="24" rx="4" fill="#0f3d3a"/>`)
|
|
113
|
+
const marks = scene.words.map((sec, n) => {
|
|
114
|
+
const at = xs(scene.start / film.fps + sec)
|
|
115
|
+
const tall = 6 + ((n * 7 + i * 3) % 5) * 3
|
|
116
|
+
return `<rect x="${at.toFixed(1)}" y="${(rows.voice + 12 - tall / 2).toFixed(1)}" width="2" height="${tall}" rx="1" fill="#2dd4bf"/>`
|
|
117
|
+
})
|
|
118
|
+
body.push(marks.join(''))
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Music: the track for as long as it lasts, and its beats.
|
|
123
|
+
if (film.music) {
|
|
124
|
+
const length = film.music.seconds > 0 ? Math.min(film.music.seconds, seconds) : seconds
|
|
125
|
+
const end = xs(length)
|
|
126
|
+
body.push(
|
|
127
|
+
`<rect x="${X0}" y="${rows.music}" width="${(end - X0).toFixed(1)}" height="20" rx="4" fill="#1e2a5a"/>`,
|
|
128
|
+
`<text x="${X0 + 6}" y="${rows.music + 14}" fill="#aeb8ff">${xml(fit(`♪ ${film.music.title}${film.music.bpm ? ` · ${Math.round(film.music.bpm)} BPM` : ''}`, end - X0 - 10))}</text>`,
|
|
129
|
+
)
|
|
130
|
+
const every = film.music.beats.length > 160 ? 2 : 1
|
|
131
|
+
const ticks = film.music.beats
|
|
132
|
+
.filter((sec, n) => n % every === 0 && sec <= length)
|
|
133
|
+
.map(sec => `<line x1="${xs(sec).toFixed(1)}" y1="${rows.music + 15}" x2="${xs(sec).toFixed(1)}" y2="${rows.music + 20}" stroke="#7c8cff"/>`)
|
|
134
|
+
body.push(ticks.join(''))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Playhead at the start of the selected scene.
|
|
138
|
+
const picked = film.scenes[selected]
|
|
139
|
+
if (picked) {
|
|
140
|
+
const at = x(picked.start).toFixed(1)
|
|
141
|
+
body.push(
|
|
142
|
+
`<line x1="${at}" y1="${rows.ruler + 12}" x2="${at}" y2="${H - 8}" stroke="#fff" stroke-width="1.5"/>`,
|
|
143
|
+
`<path d="M${at} ${rows.ruler + 20} l-5 -8 h10 z" fill="#fff"/>`,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
out.push(`<defs>${defs.join('')}</defs>`, body.join(''), '</svg>')
|
|
148
|
+
return out.join('')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const rich = build(true)
|
|
152
|
+
return rich.length <= SVG_LIMIT ? rich : build(false)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type Cell = { text: string; tone: 'clip' | 'clipAlt' | 'picked' | 'text' | 'voice' | 'music' | 'empty' }
|
|
156
|
+
export type Lane = { label: string; cells: Cell[] }
|
|
157
|
+
|
|
158
|
+
// One lane of the terminal's timeline: a run of cells per scene, each run as wide as the scene is long.
|
|
159
|
+
const pad = (text: string, width: number): string => {
|
|
160
|
+
const cut = text.length > width ? `${text.slice(0, Math.max(0, width - 1))}…`.slice(0, width) : text
|
|
161
|
+
return cut.padEnd(width, ' ')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The same timeline in character cells, for the terminal: a ruler and one lane each for the scenes, the text, the voice and the music.
|
|
166
|
+
* `columns` is the room for the lanes themselves, the label column apart.
|
|
167
|
+
*/
|
|
168
|
+
export function timelineLanes(film: Film, selected: number, columns: number): { ruler: string; lanes: Lane[] } {
|
|
169
|
+
const total = Math.max(1, film.totalFrames)
|
|
170
|
+
const seconds = total / film.fps
|
|
171
|
+
const width = Math.max(20, columns)
|
|
172
|
+
const edge = (frame: number): number => Math.round((frame / total) * width)
|
|
173
|
+
const spans = film.scenes.map(scene => {
|
|
174
|
+
const from = edge(scene.start)
|
|
175
|
+
return { from, size: Math.max(1, edge(scene.start + scene.frames) - from) }
|
|
176
|
+
})
|
|
177
|
+
const lane = (label: string, cell: (index: number, size: number) => Cell): Lane => ({
|
|
178
|
+
label,
|
|
179
|
+
cells: spans.map((span, index) => cell(index, span.size)),
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const step = rulerStep(seconds)
|
|
183
|
+
let ruler = ''
|
|
184
|
+
for (let t = 0; t <= seconds; t += step) {
|
|
185
|
+
const at = Math.round((t / seconds) * width)
|
|
186
|
+
if (at < ruler.length) continue
|
|
187
|
+
ruler = ruler.padEnd(at, ' ') + clock(t)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const beats = film.music?.beats ?? []
|
|
191
|
+
const musicCells = Array.from({ length: width }, (_, column) => {
|
|
192
|
+
const from = (column / width) * seconds
|
|
193
|
+
const to = ((column + 1) / width) * seconds
|
|
194
|
+
return beats.some(sec => sec >= from && sec < to) ? '╵' : '─'
|
|
195
|
+
}).join('')
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
ruler: ruler.slice(0, width),
|
|
199
|
+
lanes: [
|
|
200
|
+
lane('Video', (i, size) => ({
|
|
201
|
+
text: pad(`${i + 1} ${film.scenes[i]?.id ?? ''}`, size),
|
|
202
|
+
tone: i === selected ? 'picked' : i % 2 ? 'clipAlt' : 'clip',
|
|
203
|
+
})),
|
|
204
|
+
lane('Text', (i, size) => ({ text: pad(film.scenes[i]?.text[0] ?? '', size), tone: film.scenes[i]?.text[0] ? 'text' : 'empty' })),
|
|
205
|
+
lane('Voice', (i, size) => {
|
|
206
|
+
const scene = film.scenes[i]
|
|
207
|
+
const marks = Array.from({ length: size }, (_, column) => {
|
|
208
|
+
if (!scene?.narration) return ' '
|
|
209
|
+
const from = (column / size) * (scene.frames / film.fps)
|
|
210
|
+
const to = ((column + 1) / size) * (scene.frames / film.fps)
|
|
211
|
+
return scene.words.some(sec => sec >= from && sec < to) ? '▮' : '·'
|
|
212
|
+
}).join('')
|
|
213
|
+
return { text: marks, tone: scene?.narration ? 'voice' : 'empty' }
|
|
214
|
+
}),
|
|
215
|
+
{ label: 'Music', cells: [{ text: film.music ? musicCells : ' '.repeat(width), tone: film.music ? 'music' : 'empty' }] },
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: art-styles
|
|
3
|
+
description: Use when the user wants a drawn, illustrated or art-directed film rather than a product or interface one, or names one of these fifteen styles (flat vector, line art, isometric, soft 3D, hand-drawn cel, collage, liquid, shape morph, Bauhaus, synthwave, aurora glass, variety captions, sticker explainer, pixel art, HUD) - each with when to use it, how it is built from the kit, its signature features, timing, sound, and what it forbids.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fifteen art styles
|
|
7
|
+
|
|
8
|
+
These looks continue the catalogue in `reference/styles.md` (they are numbered 14 to 28 so you can offer them in the same list). They come from mg-styles-15 by Vincentwei1021 (MIT): fifteen 10-second films, each the canonical form of one motion-design style, made entirely from code. The originals were web pages rendered frame by frame with synthesised sound; here each one is translated into reelkit's kit, HyperFrames at 30 frames a second, library components and library sounds. The film of each style is at `https://vincentwei1021.github.io/mg-styles-15/#<name>` (the name is in each card's heading), and when the reelkit repository is checked out, the full original prompt is at `mg-styles-15/prompts/<name>.md` and the source at `mg-styles-15/demos/<name>/`. Read the prompt's Creative seed and Signature features when the user picks one of these looks and you have them; the card is enough when you do not.
|
|
9
|
+
|
|
10
|
+
## What these looks share
|
|
11
|
+
|
|
12
|
+
- Declare the chosen visual system in the first scene's `notes` and one palette at the top of `Video.tsx`. A mixed-media film may change its material by meaning while carrying a consistent type, accent or object. Color hierarchy matters more than a fixed number of accented elements.
|
|
13
|
+
- Use the signature features that make the chosen treatment recognizable and serve this shot. Write those choices into its notes; `reelkit plan check` cannot judge visual style. Use `reference/style-recipes.md` for the matching action, sound and proof, and `reference/style-components.md` for available components.
|
|
14
|
+
- The picture is drawn in code: SVG, CSS transforms, or a canvas redrawn each frame inside a sibling component. Generated images appear only where a card asks for a photograph (collage, sticker explainer) and the user's own pictures and footage where the card says so. Every drawing is a pure function of the frame, with `random(seed)` from reelkit/frame for any variation.
|
|
15
|
+
- Reelkit currently runs at 30 fps; use the manifest clock rather than assuming a reference film's fps. "On twos" holds each drawing for two frames: `const f = Math.floor(frame / 2) * 2`; "on threes" uses 3. Use that clock for drawing changes, while semantic landings and sound still follow their real event frames. A stepped look (cel, collage, pixel) can hold still between samples.
|
|
16
|
+
- Where a card gives an easing as four numbers, use `Easing.bezier(...)` from reelkit/frame with those numbers in `interpolate`, both ends clamped. Where it gives a spring, use the kit presets.
|
|
17
|
+
- Each card ends with **Bends:** the rules in `reference/motion-design.md` this look is allowed to break (a flat ground, visible overshoot, glow, stepped motion). Everything else in that file still holds, and the camera still moves as a group.
|
|
18
|
+
- Sound comes from the library, never synthesised: `reelkit assets search "<words>" --kind sfx` and `--kind music` with the words in the card. Pops and clicks that repeat are graded quieter each time (`cuesFor(..., "pop")` does this).
|
|
19
|
+
- Inspect style-specific evidence: readable silhouette and type, correct pivots or projection, purposeful material, stable edges, reproducible sampled motion and sound on the intended event. Fix the largest observed defect and verify the final export with `reference/delivery-review.md`; a subjective quality score is not verification.
|
|
20
|
+
|
|
21
|
+
## The catalogue, continued
|
|
22
|
+
|
|
23
|
+
### 14. Flat vector (showreel) · `01-flat-vector`
|
|
24
|
+
**When:** an explainer or a brand story told by shapes and a simple character; the classic software explainer, pushed to showreel polish. **How:** a flat solid colour field as the ground (this look's exception), five or six solid colours from one palette, no gradients, no strokes, no shadows; everything is SVG or plain divs in palette colours. One hero moves at a time. A drop-in has three phases: three frames of anticipation the other way, the move with a stretch of about 10 percent along its direction, a squash on landing and a settle on `springs.snappy`. A scene builds piece by piece with a 2 to 4 frame stagger, each piece with its own small overshoot. A character is a rig of simple shapes turning about anchors (`transformOrigin` at the shoulder, the hip), with a head bob and a blink every 2 to 3 seconds by frame number. The whole film can be one shot: a `Camera` that pushes into a window (`zoomTo`) until it is the next scene. Scene changes are shape wipes: `SceneFrame` exit `"color-push"` with `shape="circle"` and `origin` at the element that caused it, or an element flies out and carries the cut. Close on the user's logo: everything collapses into it, settles, holds 1.5 seconds. Components in the library: `FlatDropIn` (search "flat drop"), `ShapeRig` (search "shape rig"), `FlatSkyline` (search "flat skyline"). **Timing:** an entrance 10 to 14 frames; stagger 2 to 4; squash and stretch about 10 percent. **Sound:** music "upbeat pop" around 120 BPM; "pop", "boing" and "whoosh" on entrances, on the frame. **Forbidden:** gradients, outlines thicker than a hairline, photographs, drop shadows, two heroes moving at once. **Bends:** a flat solid ground; visible overshoot on entrances.
|
|
25
|
+
|
|
26
|
+
### 15. Line art (restrained) · `02-line-art`
|
|
27
|
+
**When:** luxury, architecture, finance, a logo reveal: one continuous line tells the story. **How:** a near-black or deep midnight ground (`BgGrain` at low intensity) and one line colour, champagne or the hero, at one constant width of 3 px at 1080 wide with round caps. Each figure is one SVG path drawn on with `stroke-dasharray` and `stroke-dashoffset` from its `pathLength`, and the tail of each figure is the start of the next, so the pen never lifts: a seed grows into a sapling, its branches become the lines of a building, the building's edge runs into a horizon, the horizon loops into the user's logo outline (from their SVG). A soft glowing dot leads the pen tip: the one glow in the film. The draw eases into corners with `Easing.bezier(0.65, 0, 0.35, 1)` per segment. The `Camera` follows the tip with a slow drift, then pulls back at the end to show the whole drawing composed as a poster; segments the camera has left dim to about 35 percent. Line to fill: when the logo outline closes, the same path's fill grows from its anchor (a `clip-path` circle growing), then a thin letter-spaced wordmark fades up (the one plain fade allowed). Components in the library: `LinePath` (search "line path"), `LineToFill` (search "line fill"). **Timing:** a figure draws in 30 to 60 frames, the fill in 12, the end held 1.5 seconds or more. **Sound:** music "felt piano" or "soft strings"; "pen on paper" under the draw, its volume following the tip's speed (louder on long fast segments); a "chime" on the fill. **Forbidden:** a second line width, more than two colours, a filled shape before the fill moment, shadows, a line that lifts and starts somewhere else. **Bends:** a glow on the pen tip; an opacity fade for the wordmark.
|
|
28
|
+
|
|
29
|
+
### 16. Isometric (restrained) · `03-isometric`
|
|
30
|
+
**When:** a city, a campus, an office, a data centre, an architecture diagram, a product's parts in a tiny world. **How:** a pale pastel ground (lavender, mint, peach, sky, white; a flat field is right here). True isometric faces from CSS transforms, without three.js: every box is three divs (top, left, right) and each face takes the same formula, `scaleY(0.866)` then `skewX(±30deg)` then `rotate(∓30deg)`; or one SVG with those matrices. Depth is the screen's y: sort the children by grid row plus column and draw in that order. Anything that moves along the grid keeps a 2:1 pixel slope. Build in this order: ground tiles drop in as a wave from the centre, each with a tiny bounce and a delay by its distance (2 frames per ring); buildings grow: the base lands, the walls `scaleY` from 0 with overshoot (`transformOrigin` at the bottom), the roof caps 2 to 3 frames later; trees pop; windows light in sequence; then life: cars loop on the roads, a drone carries a packet, a data stream pulses between buildings, a turbine turns. The camera is the group translating: `Camera` keys with x and y only, never `rotate`, over layers at 1 : 0.8 : 0.6 parallax (foreground clouds fastest). End: the camera pulls back to the whole island floating in the pastel void, and the title is set on the plane, skewed into the same iso space, or the user's logo flat and 2D above it. Components in the library: `IsoGrid` (search "iso grid"), `IsoBox` (search "iso box"), `IsoTitle` (search "iso title"). **Timing:** tiles 1 to 2 seconds, growth 3, life 3, pull-back 2. **Sound:** music "plucky marimba" tech; a "soft click" or "pop" on every tile and building landing (`cuesFor(..., "pop")` grades them); an "airy whoosh" on the pull-back. **Forbidden:** any vanishing point, a camera that rotates, hard black shadows, more than six colours, a building that appears whole. **Bends:** a flat ground; overshoot on growth.
|
|
31
|
+
|
|
32
|
+
### 17. Soft 3D render (restrained) · `04-3d-render`
|
|
33
|
+
**When:** a product ident, the user's logo or wordmark as a candy object, a "satisfying" brand film. Read `reference/three-d.md` first. **How:** `Scene3D` with `mood="studio"` over a pale pastel ground (`BgAurora` at intensity 0.3, or a flat pale field seen through the transparent scene). The hero is `Hero3D` (`kind="logo"` with the user's word, or `"device"` with their screenshot) dropping in on `poses` that land with a jiggle: a pose at `scale` 1.06 on the impact frame, 0.97 four frames later, 1 after ten, so it reads as a soft body settling. Under it a cloner field: `Assemble3D` with `shape="grid"` of rounded blocks, or `Particles3D` from `"plane"`, rippling with a wave the hero's arrival starts (each block offset by a sine of its distance from the impact minus time). Materials come from the mood: clearcoat, two coloured rim lights, a soft floor (`floor="soft"`). The camera moves with a long tail, 80 percent of the move in the first 20 percent: `Scene3D camera={{ keys, lead }}` with a suitable lead. End on one clean hero composition held a second or more, hovering. Components in the library: `PastelCyc` (search "pastel cyc"), `SoftTitle` (search "soft title"). The example's picture is a Blender plate rendered from mg-styles-15/demos/04-3d-render (see the example's blender/README.md); the kit's 3D elements are the route when Blender is not available. **Sound:** "soft thud" and "squish" on the impact, "bubble pop" on the ripple, music "airy cinematic pad". **Forbidden:** hard shadows, saturated primaries, more than one hero object, extruded text beyond the one word, a logo that spins. **Bends:** none beyond the 3D layer itself; this is a restrained look.
|
|
34
|
+
|
|
35
|
+
### 18. Hand-drawn cel (either mode) · `05-cel-boil`
|
|
36
|
+
**When:** a playful, crafted, personal tone; a title that feels drawn by hand; a mascot. **How:** a warm paper ground (search "paper texture" `--kind image` as a `BgImage` with no dim, or `BgGrain` on a cream base) with `Grain` multiplied at 10 to 15 percent, one static layer on top. Every mark is a drawn line: SVG paths with two or three hand-drawn variants of each, switched by the frame (`variant = Math.floor(frame / 2) % 3`): that is the boil, and it is never random. Animate on twos; fast actions on ones; holds on threes. A fast move gets one smear frame: the shape stretched 150 to 300 percent along its path for exactly one frame. Cel effects over the action, each a cycle of 4 to 8 drawings: a smoke puff curling, sparks, speed lines, a star burst. Fills sit slightly off-register from the lines (2 to 3 px). Three or four colours: ink, one warm, one bright, paper. The title is hand-lettered (`font("permanentMarker")`, or `font("amaticSC")` for Hebrew) and keeps boiling on its hold. Components in the library: `BoilStroke` (search "boil stroke"), `CelFx` (search "cel fx"), `SmearMove` (search "smear move"), `PaperGround` (search "paper ground"). **Timing:** everything on twos; the end held a second or more on threes. **Sound:** music "jazzy pizzicato" or "xylophone"; cartoon effects "fwoosh", "pop", "poof", "sparkle", on the frame. **Forbidden:** smooth springs on the drawn lines (they move in steps), gradients, crisp geometric type, more than four colours, a boil driven by anything but the frame number. **Bends:** stepped motion instead of springs; a boil instead of a breathe.
|
|
37
|
+
|
|
38
|
+
### 19. Collage cut-out (showreel) · `06-collage`
|
|
39
|
+
**When:** music, street, editorial, a magazine-style surreal piece, a topic about ideas or the mind. **How:** a kraft or newsprint ground (search "newsprint" or "kraft paper" `--kind image` as a `BgImage` with no dim). The cut-outs are the user's own pictures with their subject cut out by `reelkit assets layers`, or public-domain pictures, each given a 2 to 4 px white scissor edge (four stacked `drop-shadow(0 0 0 white)` filters, or an SVG `feMorphology` dilate under the picture) and a halftone (an SVG dot pattern over it at 30 percent, multiply). Puppet joints: parts turn about anchors at the jaw, the shoulder, the elbow; a mouth is two or three replacement cut-outs swapped by frame. Motion in steps: action every third frame (10 a second), holds every fifth, with a ±2 px placement jitter per step from `random(seed)` so the pieces look hand-placed. Hands slap elements onto the page: a hand cut-out enters, the element appears under it, the hand leaves. Type is constructivist: `font("archivoBlack")` in red and black on a diagonal, torn-paper strips behind it. Scene changes: a hand slaps the next scene's element on, the page turns (`SceneFrame` `"turn"`), or a torn edge wipes. Layer order, bottom to top: paper, big colour blocks, cut-outs, scribbles and tape, grain. Components in the library: `CutoutSticker` (search "cutout sticker"), `PaperHand` (search "paper hand"), `TornStrip` (search "torn strip"). **Sound:** overlay "vinyl crackle" for the whole film; music "boom bap"; "paper rustle", "scissors", "slap", and a comic "pop" on the reveal. **Forbidden:** smooth motion, soft shadows, anyone's face but the user's, a cut-out without its edge, gradients. **Bends:** stepped motion; hard cuts carried by a hand.
|
|
40
|
+
|
|
41
|
+
### 20. Liquid (showreel) · `07-liquid`
|
|
42
|
+
**When:** a logo genesis, a drink or food brand, a music visual, a transition that floods the frame. **How:** a deep ground (plum, or the palette's base). Blobs are SVG circles under a gooey filter: `feGaussianBlur` with `stdDeviation` 20 or more, then `feColorMatrix` on alpha (`0 0 0 19 -9`) so blobs that come near fuse with a neck. Fake depth on each blob: a radial highlight and a rim of 20 percent white, so it reads as thick glossy liquid, never a flat disc. The beats: droplets fall and squash on landing (scaleX 1.3, scaleY 0.7 for 3 frames, then a rebound); blobs merge (the neck stretches, snaps, both ends rebound 2 to 3 frames); secondary droplets follow the main one 2 to 3 frames late; a wave floods the frame as the scene change (a path whose front has 3 to 5 bulges out of phase, sweeping across; `SceneFrame` `"color-push"` with `shape="diagonal"` behind it); the liquid gathers into the user's logo or a word in a heavy face placed under the same filter so it fuses with the blobs; a last drip falls from it and ripples; hold. Every move is flung, never uniform: `Easing.bezier(0.22, 1, 0.36, 1)`. Components in the library: `GooLayer` (search "goo layer"), `Droplet` (search "droplet"), `LiquidWave` (search "liquid wave"), `LiquidWord` (search "liquid word"). **Sound:** "gloop", "pour", "splash", "drip"; music "glossy synth" with a bass swell under the flood. **Forbidden:** a straight line or a corner on anything liquid, a uniform speed, blobs that overlap without fusing (that is a missing filter), particles. **Bends:** a highlight on every blob; strong overshoot.
|
|
43
|
+
|
|
44
|
+
### 21. Shape morph (restrained) · `08-morph`
|
|
45
|
+
**When:** a story told by one object becoming the next: a product's features, a journey, a chain of ideas, on the beat. **How:** a colour field ground that changes with each morph (`SceneFrame` `"color-push"` with `shape="bar"` or `"circle"` from the hero). One hero shape: write every icon in the chain as a polygon with the same number of points (64 is enough), the first point at the same clock position, and interpolate point by point. A complex A to B never goes direct: A closes into a circle or a capsule, then opens into B, 8 to 12 frames each half, with a slight turn and 10 to 15 percent squash and stretch through the morph; sub-parts morph in sync (steam into sun rays into waves). Ease `Easing.bezier(0.7, 0, 0.3, 1)` with 3-frame cushions at both ends. For a word, `Particles3D` from `"text"` to a shape does the same in light. Six to eight morphs, each landing on a beat (`nextBeat`). End: the last shape becomes the user's logo, traced as the same number of points, with the wordmark. Components in the library: `ShapeMorph` (search "shape morph"), `ColorFieldSwap` (search "color field"). **Sound:** music "plucky arpeggio"; a pitched "whoosh" per morph, ending on the landing frame. **Forbidden:** a cross-fade between shapes, two shapes morphing at once, a morph that knots (look at the midpoint frame of every morph in the preview), a hero that holds still between morphs. **Bends:** a flat colour field ground.
|
|
46
|
+
|
|
47
|
+
### 22. Bauhaus grid (showreel) · `09-bauhaus`
|
|
48
|
+
**When:** a kinetic poster, a festival or event opener, anything that wants to move to a beat. **How:** cream paper (`Grain` multiply on a cream base) and red, yellow, blue and black. A strict modular grid: a module is an eighth of the width on 16:9 and a fifth on 9:16; every size, position and move is a whole number of modules and every landing is on a grid line. Every move is on a beat (`manifest.music.beatFrames`; 120 BPM is every 15 frames): a quarter circle turns 90 degrees about a corner, a semicircle slides one module, a triangle flips, a square splits, and the pivot alternates between the shape's own centre, a corner and the frame's centre. 0 to 2 seconds: grid lines draw on and the first red circle lands; 2 to 6: the composition assembles into a poster; 6 to 8: on the drop, a cascade wave across the grid, each module's phase its row plus column; the rest: freeze into the poster with Swiss type, a heavy geometric sans (`font("archivoBlack")` or `font("unbounded")`) set vertically, a small-caps line under it. A slight print misregistration is allowed: a copy of one colour offset 2 px at 30 percent. Components in the library: `ModularGrid` (search "modular grid"), `GridShape` (search "grid shape"), `SwissTitle` (search "swiss title"). **Timing:** a move takes 8 to 10 frames with `Easing.inOut(Easing.quad)` or linear, then holds until the next beat; no overshoot ever. **Sound:** music "minimal techno click"; a "blip" or "click" on each flip (`cuesOnBeats`), a kick on quarters. **Forbidden:** overshoot, a diagonal off the grid, more than five colours, gradients, a move between beats. **Bends:** linear and quad moves without springs; a flat ground; nothing breathing between beats.
|
|
49
|
+
|
|
50
|
+
### 23. Synthwave (showreel) · `10-synthwave`
|
|
51
|
+
**When:** retro, nightlife, music, a game, an "80s" hook. **How:** `BgGrid` with a deep purple base and a magenta hero is the rushing perspective grid with its horizon glow. Over it a striped sun (a circle with horizontal bands cut out, magenta to orange to yellow: this look allows the multi-stop gradient on the sun and the chrome only), wireframe mountains as SVG polylines, palm silhouettes, a seeded starry sky. 0 to 1 second: VHS tracking noise and a "PLAY ▶" label in `font("jetbrainsMono")`; then the grid rushes (`BgGrid` speed up) and the sun rises; the title slams in as chrome, a multi-stop metallic gradient clipped to the text, a bevel highlight line, a star glint sweeping once, with `SceneFrame` `"flash"` on the slam; a pink neon script word (`font("yellowtail")` or `font("sacramento")`) writes on like a tube with two or three flickers (opacity stepped by frame) in double glow: `text-shadow` tight 4 px and wide 30 px at low opacity. A VHS pass over everything, one static layer: RGB split 1 to 2 px (two copies of the layer tinted red and blue, offset, screen blend), scanlines (a repeating 2 px gradient at 10 percent), one or two glitch frames a second (a 2 to 4 px horizontal shear on seeded frames). End on the hero frame with the ride sustained. Components in the library: `RetroSun` (search "retro sun"), `WireMountains` (search "wire mountains"), `ChromeTitle` (search "chrome title"), `NeonScript` (search "neon script"), `VhsPass` (search "vhs pass"). **Sound:** music "synthwave" (gated snare, arpeggiated bass); "crash" on the title slam, "vhs noise" under the opening, "neon buzz" on the script. **Forbidden:** pastels, a white ground, a thin elegant face for the title, glow on body text, more than the one flash in any second. **Bends:** glow on the hero and the lines; gradients on the sun and the chrome title; hard cuts on the beat.
|
|
52
|
+
|
|
53
|
+
### 24. Aurora glass (restrained) · `12-aurora-glass`
|
|
54
|
+
**When:** an AI or software launch that should feel slow, translucent and expensive; the 2025 "liquid glass" look. **How:** `BgAurora` on a deep indigo-black base with the hero and accent in adjacent hues (violet, blue, cyan: never complementary), intensity 0.6, with `Grain` at 3 to 5 percent over it to stop banding. `GlassPanel` cards (`theme="glass"`, `tilt` 6 to 10) float in at two or three depths, moving at 1 : 0.7 : 0.5 of a very slow `Camera` drift; inside them small interface moments, a chat bubble, a voice waveform of bars, a toggle, each arriving by opacity plus an 8 px rise and nothing more. The wow moment: a lens, a circle that carries a magnified copy of the ground (a second `BgAurora` scaled 1.3 and clipped to the circle) with a chromatic rim (two thin coloured strokes offset 1 px) and a specular edge, gliding across the cards on a long sine path. The title in a light sans (`font("outfit")` at 300) with wide tracking. Everything slow: `Easing.inOut(Easing.sin)`, nothing under 20 frames, a 2 percent camera push over the film. Components in the library: `GlassLens` (search "glass lens"), `MicroUi` (search "micro ui"), `LightTitle` (search "light title"). **Sound:** music "ambient cinematic pad"; "soft shimmer" as the lens passes; a very quiet "ui tick" on each toggle. **Forbidden:** a fast move, complementary colours, a white ground, hard edges, more than three cards, glow on the type. **Bends:** glass and the aurora gradient are the look; an opacity-plus-rise entrance for text is right here.
|
|
55
|
+
|
|
56
|
+
### 25. Variety captions (showreel) · `18-hanazi`
|
|
57
|
+
**When:** a vlog, a pet or reaction video, a funny reply, anything with footage and emotion; the television variety-show caption, now the common language of vlogs. **How:** the picture is the user's footage (`FootageLayer`) or their photos in `ImageLayers` with a slow Ken Burns, a subtle handheld shake (a seeded 2 to 4 px drift at one or two cycles a second) and snap punch-ins on the moments (`Camera` `punches`). Over it the reacting captions: three-layer text, a fill (solid or a gradient of two hues), a thick white stroke (`WebkitTextStroke` or stacked `text-shadow`), a coloured outer stroke and a drop shadow, in a rounded heavy face (`font("lilitaOne")` or `font("modak")`; `font("fredoka")` for Hebrew). Each caption pops 0 to 1.15 to 1.0 in about 8 frames (`springs.snappy`), per character with a 2-frame stagger and a slight tilt, then wiggles gently while it holds (±1.5 degrees by a sine of the frame), and it appears 2 to 3 frames after the moment it reacts to, never before. Decorations drawn as SVG, never emoji: a radial burst box with speed lines for shock, a sweat drop for awkward, sparkles for praise, a stamp for approval. The spoken words use `Captions` in `"pop"` mode. Components in the library: `PopCaption` (search "pop caption"), `BurstBox` (search "burst box"), `HandheldPhoto` (search "handheld photo"). **Sound:** music "bouncy variety" (pizzicato, claps); "boing", "pop", "ding", "slide whistle", "record scratch" on the reactions. **Forbidden:** a caption that stays after its moment, more than one reacting caption at a time, a thin typeface, a caption over the face, anyone's face but the user's. **Bends:** strong overshoot; saturated clashing colours with white strokes; a wiggle while holding.
|
|
58
|
+
|
|
59
|
+
### 26. Sticker explainer (restrained) · `19-paperclip`
|
|
60
|
+
**When:** a knowledge or explainer video with facts, numbers and parts: hard science, finance, "how many X are in a Y". **How:** a calm blue-grey or off-white ground (a flat field is right here), two or three colours plus one accent, an equal-weight sans (`font("outfit")` at 500; `font("assistant")` for Hebrew), strict alignment. Photographs become stickers: the user's pictures with the subject cut out by `reelkit assets layers`, an 8 to 12 px white outline (stacked `drop-shadow` filters, or a dilated copy under it) and a soft shadow, so pictures from different sources sit together. Everything lives on one giant canvas, a div three times the frame, and one `Camera` path flies between the information points with long eases (`lead` 20 to 30 frames): the sticker of the object, its parts exploded with leader lines drawing from each part to its label, a grid of tiles lighting up, a bar chart growing (search "bar chart" `--kind component`) with `CounterRoll` on its number, a map route drawing on, then the pull-out to a bold title card: one huge number, one small caption. Every sentence of the narration brings a new visual element in on its word (`onWord`), and every chart is to scale with the plan's own numbers. **Sound:** music "clean tech explainer"; a "ui click" per label, "counter ticks" under the count, a "soft whoosh" on each camera move (`cueOnCamera`). **Forbidden:** an invented number, a photograph without its sticker edge, a chart not to scale, a second accent, a cut (the camera carries every change). **Bends:** a flat ground.
|
|
61
|
+
|
|
62
|
+
### 27. Pixel art (showreel) · `20-pixel`
|
|
63
|
+
**When:** a game, a retro brand, an 8-bit music piece, a playful nostalgia hook. **How:** draw at a small internal size (320 by 180 on 16:9, 180 by 320 on 9:16) on a canvas in a sibling component redrawn each frame, or as a grid of divs for small sprites, and scale it up by a whole number (6) with `imageRendering: "pixelated"`; every position is a whole internal pixel. A limited palette of 16 to 32 colours; a gradient only as dithering, a checker of two colours. Three or four parallax layers moving by whole pixels at different speeds. The hero sprite is a pixel array in code with a 6 to 8 drawing run cycle at 10 a second (advance a drawing every third frame), jumping and collecting on the beat; a dialogue box types a line in a pixel-looking face (`font("jetbrainsMono")` drawn small on the internal canvas); a chunky pixel logo drops in with a shine sweep; the palette shifts from day to sunset by swapping it per scene. An optional CRT pass over everything: scanlines, a slight vignette, a phosphor glow at 20 percent, none of which blurs the pixels. Components in the library: `PixelCanvas` (search "pixel canvas"), `PixelSprite` (search "pixel sprite"), `PixelDialog` (search "pixel dialog"), `CrtPass` (search "crt pass"). **Timing:** stepped, never springs: the sprite's drawing is `Math.floor(frame / 3) % 8`. **Sound:** music "chiptune"; "jump", "coin", "chest" 8-bit effects. **Forbidden:** an anti-aliased edge, smooth easing on a sprite, a sub-pixel position, a modern typeface, a photograph. **Bends:** stepped timing; a flat ground; nothing breathes.
|
|
64
|
+
|
|
65
|
+
### 28. HUD (either mode) · `22-hud`
|
|
66
|
+
**When:** a tech review, a drone or a product with specifications, a "target found" reveal, a cyberpunk or military frame. **How:** a deep teal-black ground (`BgGrid` at low intensity, or flat), cyan linework with an orange alert state: one hero and its alert twin, this look's exception. `HudOverlay` gives the corners, the timecode and the scanlines; add a hex grid (an SVG pattern), concentric rings with ticks turning at different rates (linear is right for a loop), a radar sweep (a conic-gradient wedge turning, blips flashing on a seeded schedule), data columns in `font("jetbrainsMono")` whose digits roll through 3 to 5 frames of random then settle on their real values, typewriter status lines with a blinking cursor, sparklines. In the centre a `Scene3D` with a wireframe object, `Particles3D` from `"sphere"` or a `Hero3D` at low opacity, turning slowly. Build in hierarchy: the frame lines draw on first (`stroke-dashoffset`), then the panels, then the data comes alive, staggered 2 to 4 frames; the target is found and the lock brackets snap from scale 1.4 to 1.0 in 8 frames, fast out and slow in; then everything flips to the alert colour with "LOCKED" and the user's claim. Finish: a tight 2 to 4 px glow on lines only, a slight chromatic aberration (a 1 px red and blue offset copy at 30 percent), scanlines, at most one or two micro-glitches a second. Any number shown as data is from the plan or is plainly decorative and makes no claim. Components in the library: `RadarSweep` (search "radar sweep"), `DataColumn` (search "data column"), `LockBrackets` (search "lock brackets"), `HexGrid` (search "hex grid"), `TypeLine` (search "type line"). **Sound:** music "darksynth"; "hud beep", "scan sweep", a rising "lock-on tone", an "alarm" on the lock. **Forbidden:** a figure presented as data that is not in the plan, text under 3 percent of the width that must be read, rounded friendly shapes, more than two line colours, a glitch above two a second. **Bends:** glow on lines; linear loops; the hero in two states.
|
|
67
|
+
|
|
68
|
+
## Choosing one
|
|
69
|
+
|
|
70
|
+
Use the named card when the user chooses a style or reference. Otherwise recommend the fitting treatment; offer a numbered comparison only when requested. A product film may combine a real screen with a drawn world when that serves the message. Preserve the supplied source and make any illustration clear. The cards are craft references, not a requirement to reproduce every feature of their example films.
|
|
@@ -38,7 +38,7 @@ Tint the ground with the film's one accent colour (`tint={palette.hero}`), so th
|
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
40
|
import React from "react";
|
|
41
|
-
import { AbsoluteFill } from "
|
|
41
|
+
import { AbsoluteFill } from "reelkit/frame";
|
|
42
42
|
import { BgImage, BgSequence, BgVideo, SceneFrame, bgPerScene, palettes } from "reelkit/kit";
|
|
43
43
|
import type { VideoProps } from "reelkit/kit";
|
|
44
44
|
|
|
@@ -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.
|
package/skill/reference/clips.md
CHANGED
|
@@ -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
|
-
-
|
|
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 "
|
|
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 "
|
|
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}>
|
|
@@ -8,9 +8,11 @@ description: Use when writing a new reusable component file for a video's src/ f
|
|
|
8
8
|
## First, do not write one
|
|
9
9
|
Run `reelkit assets search "<description>" --kind component`. If a library component does the job with different props, `reelkit assets pull <id>` it (it lands in `src/`) and use it. Write a new component only when nothing fits.
|
|
10
10
|
|
|
11
|
+
Use `reference/style-components.md` to choose the role and `reference/style-recipes.md` for the shot's motion and proof. A catalog name does not mean a built-in kit export or a currently published asset; search, pull and inspect the actual source first.
|
|
12
|
+
|
|
11
13
|
## Rules for a component file
|
|
12
14
|
- One component per file. File `NumberBadge.tsx` exports `export const NumberBadge: React.FC<NumberBadgeProps>`.
|
|
13
|
-
- Imports only from `react`, `
|
|
15
|
+
- Imports only from `react`, `reelkit/frame` and `reelkit/kit`. A component never imports another component file.
|
|
14
16
|
- Everything specific comes in through props: text, numbers, colours, timing. No baked-in copy, brand names, user details or asset paths.
|
|
15
17
|
- Give every visual prop a sensible default so `<NumberBadge value={1} />` works on its own.
|
|
16
18
|
- 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
|
|
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
|
-
##
|
|
10
|
+
## A concept before a new plan
|
|
11
11
|
|
|
12
|
-
A concept is one sentence about the picture, not about the product. Before
|
|
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."
|
|
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
|
|
|
@@ -38,7 +38,7 @@ A hard cut is a choice. Use it for a burst of fast hits, or for the end card, an
|
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
40
|
import React from "react";
|
|
41
|
-
import { AbsoluteFill } from "
|
|
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
|
|
|
@@ -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
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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.
|