@scenerok/sdk 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/skills/scenerok/SKILL.md
CHANGED
|
@@ -40,7 +40,7 @@ Copy this checklist into your notes and work through it:
|
|
|
40
40
|
screenshots, logo, footage, numbers, words. Probe each file
|
|
41
41
|
- [ ] 4. Choose each beat's tools with choosing.md; write one line of why per beat
|
|
42
42
|
- [ ] 5. Decide the look (palette, fonts, sizes) as `let`s: look-and-feel.md
|
|
43
|
-
- [ ] 6. Write the script (recipes.md has
|
|
43
|
+
- [ ] 6. Write the script ([recipes.md](references/recipes.md) has the visual patterns; a plugin's README has its story). Validate: fix every error;
|
|
44
44
|
read every aesthetic warning as a question about your choice
|
|
45
45
|
- [ ] 7. Uses TTS or generated media: deep-validate and fix the report (CLI only)
|
|
46
46
|
- [ ] 8. Render, pull one frame per beat, look, and answer the review questions
|
|
@@ -143,6 +143,18 @@ right, so treat the list as information, not a verdict.
|
|
|
143
143
|
Without a shell (web editor, ChatGPT, MCP), ask questions 1-4 and 6 of the
|
|
144
144
|
script itself, and have the user watch the preview.
|
|
145
145
|
|
|
146
|
+
## Who owns what
|
|
147
|
+
|
|
148
|
+
This skill owns the **language and the craft**: VidScript, the tool repertoire
|
|
149
|
+
and how to choose from it, the look-and-feel numbers, how to source imagery, and
|
|
150
|
+
how to verify a frame.
|
|
151
|
+
|
|
152
|
+
A **plugin owns its domain**: the order of beats, the copy rules, the prompt that
|
|
153
|
+
writes the words, and the template that times them. When a plugin covers the job,
|
|
154
|
+
use it and then improve the *look* of its beats — do not restate its story here.
|
|
155
|
+
Product promo, changelog and talking-head stories live in their packages'
|
|
156
|
+
READMEs; [recipes.md](references/recipes.md) is visual patterns only.
|
|
157
|
+
|
|
146
158
|
## Rules that prevent most failures
|
|
147
159
|
|
|
148
160
|
1. **Import before calling.** `import xai from "@scenerok/xai"`, then
|
|
@@ -175,7 +187,7 @@ Read the ones the task needs. Paths are relative to this file.
|
|
|
175
187
|
| --- | --- |
|
|
176
188
|
| Any video | [choosing.md](references/choosing.md) (always): how to pick tools for a beat, with worked choices |
|
|
177
189
|
| What a tool looks like, is for, and fights; tools by job | [repertoire.md](references/repertoire.md) (generated; search it by job or name) |
|
|
178
|
-
|
|
|
190
|
+
| Visual patterns to adapt; palette, type scale, safe areas, sourcing pictures | [recipes.md](references/recipes.md), [look-and-feel.md](references/look-and-feel.md) |
|
|
179
191
|
| Any VidScript | [vidscript-strict.md](references/vidscript-strict.md) (always) |
|
|
180
192
|
| Editing and compositing: trims, speed, reverse, freeze, picture-in-picture, blend modes, masks, crops, split screens, audio mixing and ducking, grading and LUTs, captions, timecode | [editing-actions.md](references/editing-actions.md) |
|
|
181
193
|
| Full grammar | [vidscript-guide.md](references/vidscript-guide.md) |
|
|
@@ -29,13 +29,13 @@ Defaults unless the user says otherwise:
|
|
|
29
29
|
|
|
30
30
|
| Workflow | Load these slugs | Primary APIs (VidScript · SDK) |
|
|
31
31
|
| --- | --- | --- |
|
|
32
|
-
| **Product promo** | `recipes.md
|
|
32
|
+
| **Product promo** | `plugins.md` (run `listing-promo`), `recipes.md` for the visual moves, `look-and-feel.md`, `brand-setup.md` (+ `ai-models.md` when the page has no usable pictures, `sdk-guide.md` if TS) | page images as `input` with a scale push, `motion.riseIn` / `popIn`, `solid` CTA button, `filter "cdl"`, `eleven.music` |
|
|
33
33
|
| **Motion graphic** | `animations-picker.md` (+ `animations-catalog.md` if needed) | `animations.*` (VidScript HyperFrames; prefer VidScript for catalog blocks) |
|
|
34
34
|
| **Social proof card** | `animations-picker.md` | `animations.x_post`, `reddit_post`, … (VidScript) |
|
|
35
35
|
| **Edit existing footage** | `editing-actions.md` (+ `media-stdlib.md` to probe the clips, `transitions-catalog.md` for cuts) | `Trim` / `Speed` / `Loop` / `Crop` / `Mask` / `Overlay` / `Composite`, `blend:`, `duck_under:`, `filter "lut"` (VidScript only) |
|
|
36
36
|
| **Scene montage** | `transitions-catalog.md`, `ai-models.md` (+ `sdk-guide.md` if TS loops) | `tr.dissolve` · SDK: `import tr from '@scenerok/transitions'` + `t[start][end] = tr.dissolve()` |
|
|
37
37
|
| **Narrated explainer** | `tts.md`, `text-styles.md`, `music.md`, `workflow.md` (+ `sdk-guide.md` if TS) | `xai.tts` · `await xai.tts(...)` + `t.push(vo)` |
|
|
38
|
-
| **Code demo / PR / release notes** | `
|
|
38
|
+
| **Code demo / PR / release notes** | `plugins.md` (run `release-video`), `recipes.md` (the code beat), `animations-picker.md` (code rows) | 16:9: `code_typing`, `code_diff`; 9:16: a code card of `text` lines with `motion.typewriter` (VidScript) |
|
|
39
39
|
| **Text animation / headline / stat** | `animations-picker.md` (text-anim rows) | `animations.text_*` (VidScript) · SDK kinetic: `text` + `fadeIn` / `popIn` |
|
|
40
40
|
| **Kinetic title only** | `text-styles.md` (+ `sdk-guide.md` if TS) | `text` + `motion.*` · SDK: `text(..., { area })` + `.animate(fadeIn(...))` |
|
|
41
41
|
| **Custom-authored animation (no block fits)** | `animations-catalog.md` → "Custom HTML" | `animations.custom_html` (VidScript) |
|
|
@@ -43,7 +43,7 @@ Defaults unless the user says otherwise:
|
|
|
43
43
|
|
|
44
44
|
## Staged routing
|
|
45
45
|
|
|
46
|
-
1. **Classify** — workflow from table above, then the matching
|
|
46
|
+
1. **Classify** — workflow from table above, then the matching pattern in `recipes.md` (a plugin's README owns its beat order)
|
|
47
47
|
2. **Choose authoring** — VidScript by default and for every editing action (masks, blends, crops, ducking, LUTs, timecode); `@scenerok/sdk` (`sdk-guide.md`) only for loops or computed timing (it has none of the editing actions)
|
|
48
48
|
3. **Load catalogs** — only what that workflow needs (conditional loading)
|
|
49
49
|
4. **Name block or API** — VidScript: pick `animations.<fn>` from `animations-picker.md`. SDK: factories (`video` / `loadVideo`, `text`, `fadeIn`) + AI imports (`@scenerok/xai`, …)
|
|
@@ -1,223 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Visual patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
with `choosing.md`.
|
|
3
|
+
Composition moves that lift any script, whichever plugin produced it. They are
|
|
4
|
+
not a story: a **plugin owns its domain's story** — the order of beats, the copy
|
|
5
|
+
rules and the template — and this file owns how a beat is *made to look*.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
| Domain | Its story lives in |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Product promo | [`plugins/listing-promo/README.md`](../../../plugins/listing-promo/README.md#story-and-copy) |
|
|
10
|
+
| Changelog / release notes | [`plugins/release-video/README.md`](../../../plugins/release-video/README.md#story) |
|
|
11
|
+
| Talking head with captions | [`plugins/voiceover-booth/README.md`](../../../plugins/voiceover-booth/README.md#story) |
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
clean region and make a blurred 9:16 backdrop from the same shot:
|
|
13
|
+
Use the plugin for the story, then use these patterns — and the records in
|
|
14
|
+
[repertoire.md](repertoire.md) — to make each beat read on a phone. Every
|
|
15
|
+
pattern says when it earns its place and what it fights; none of them is
|
|
16
|
+
required.
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
## A held still needs a push
|
|
19
|
+
|
|
20
|
+
A still that sits unchanged for three seconds reads as a slide, however good the
|
|
21
|
+
photo is. A 1.08 scale over the hold keeps the eye engaged without movement you
|
|
22
|
+
notice. Fights: a second moving element in the same beat, or a photo whose edges
|
|
23
|
+
carry detail you want still.
|
|
21
24
|
|
|
22
25
|
```vidscript
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# Look: one palette, two fonts, one accent.
|
|
27
|
-
let bg = "#0A0C10"
|
|
28
|
-
let ink = "#F4F6FA"
|
|
29
|
-
let muted = "#A3ADBD"
|
|
30
|
-
let accent = "#3D8BFF"
|
|
31
|
-
let display = "Archivo Black"
|
|
32
|
-
let body = "Inter"
|
|
33
|
-
|
|
34
|
-
# Assets: the product page's own images, cropped and blurred with ffmpeg first.
|
|
35
|
-
input hero = "hero.jpg" # 483x472, product on white
|
|
36
|
-
input roomBand = "room-band.jpg" # 1080x398, crop of a lifestyle shot
|
|
37
|
-
input roomBg = "room-bg.jpg" # 1080x1920, same shot, blurred + darkened
|
|
38
|
-
input closeBand = "closeup-band.jpg" # 1080x368
|
|
39
|
-
input closeBg = "closeup-bg.jpg" # 1080x1920
|
|
40
|
-
|
|
41
|
-
# A slow push on the stills: these photos hold 3-4 s each, and a push keeps a held
|
|
42
|
-
# photo from reading as a slide.
|
|
26
|
+
input still = "https://cdn.example.com/photo.jpg"
|
|
27
|
+
|
|
28
|
+
# 8% over four seconds is enough to read as alive and not as a zoom.
|
|
43
29
|
let push = { type: "scale", from: { scale: 1 }, to: { scale: 1.08 }, easing: "linear" }
|
|
30
|
+
[0s .. 4s] = still, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
[0s .. 3s] = roomBg, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
47
|
-
[0s .. 3s] = roomBand, width: 984, height: 363, x: 48, y: 820, radius: 36, blend: "normal", animate: [motion.riseIn(0.5s, 80), push, motion.fadeOut(0.3s)]
|
|
48
|
-
[0.2s .. 3s] = text "Vacuums. Mops.\nEmpties itself.", font: display, size: 104, color: ink, line_height: 1.05, x: "50%", y: "27%", max_width: "86%", align: center, shadow_color: "#000000", shadow_blur: 24, animate: [motion.riseIn(0.6s, 50), motion.fadeOut(0.3s)]
|
|
49
|
-
|
|
50
|
-
# 2. Hero (3-7 s): the product on its own white card.
|
|
51
|
-
[3s .. 7s] = solid(bg)
|
|
52
|
-
[3s .. 7s] = hero, width: 900, height: 880, x: 90, y: 300, radius: 48, blend: "normal", animate: [motion.fadeIn(0.4s), push, motion.fadeOut(0.3s)]
|
|
53
|
-
[3.3s .. 7s] = text "ECOVACS", font: body, size: 44, color: accent, letter_spacing: 10, x: "50%", y: "64.5%", align: center, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
54
|
-
[3.5s .. 7s] = text "DEEBOT T30e OMNI", font: display, size: 72, color: ink, x: "50%", y: "71%", max_width: "88%", align: center, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
55
|
-
|
|
56
|
-
# 3. Features (7-11 s): a close-up band, then one bullet every 0.6 s.
|
|
57
|
-
[7s .. 11s] = closeBg, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
58
|
-
[7s .. 11s] = closeBand, width: 984, height: 335, x: 48, y: 300, radius: 36, blend: "normal", animate: [motion.fadeIn(0.4s), push, motion.fadeOut(0.3s)]
|
|
59
|
-
[7.4s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 868, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
60
|
-
[7.4s .. 11s] = text "25,000 Pa suction", font: display, size: 64, color: ink, x: 150, y: 880, anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
61
|
-
[8.0s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 1028, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
62
|
-
[8.0s .. 11s] = text "ZeroTangle 3.0 brush", font: display, size: 64, color: ink, x: 150, y: 1040, anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
63
|
-
[8.6s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 1188, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
64
|
-
[8.6s .. 11s] = text "Self-emptying dock", font: display, size: 64, color: ink, x: 150, y: 1200, max_width: "80%", anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
65
|
-
|
|
66
|
-
# 4. Price and CTA (11-15 s). The button is a solid with a radius under the label.
|
|
67
|
-
[11s .. 15s] = solid(bg)
|
|
68
|
-
[11s .. 15s] = hero, width: 560, height: 548, x: 260, y: 260, radius: 40, blend: "normal", animate: [motion.popIn(0.5s), motion.float(3s, 10)]
|
|
69
|
-
[11.3s .. 15s] = text "₹44,999", font: display, size: 150, color: ink, x: "50%", y: "50%", align: center, animate: motion.popIn(0.5s)
|
|
70
|
-
[11.6s .. 15s] = text "★ 4.4 · 166 ratings", font: body, size: 48, color: muted, x: "50%", y: "58.5%", align: center, animate: motion.fadeIn(0.4s)
|
|
71
|
-
[12s .. 15s] = solid(accent, width: 660, height: 132, x: 210, y: 1210, radius: 66), animate: motion.popIn(0.5s)
|
|
72
|
-
[12s .. 15s] = text "Shop on Amazon", font: display, size: 56, color: "#FFFFFF", x: "50%", y: 1276, align: center, animate: motion.fadeIn(0.4s)
|
|
73
|
-
|
|
74
|
-
# Grade: warm the whole frame, pull the edges down.
|
|
75
|
-
[0s .. 15s] = filter "cdl", slope: "1.04 1.0 0.96", offset: "0 0 0", power: "1 1 1", saturation: 1.08
|
|
76
|
-
[0s .. 15s] = filter "vignette", intensity: 0.3
|
|
77
|
-
|
|
78
|
-
# Sound: a bed under everything.
|
|
79
|
-
let bed = eleven.music("Bright, modern electronic product-ad bed, confident pulse, clean ending", duration: 15, instrumental: true)
|
|
80
|
-
[0s .. 15s] = audio bed, volume: 0.5, fade_out: 1s
|
|
81
|
-
|
|
82
|
-
output to "promo.mp4", resolution: "1080x1920", fps: 30, background: "#0A0C10"
|
|
32
|
+
output to "beat.mp4", resolution: "1080x1920", fps: 30
|
|
83
33
|
```
|
|
84
34
|
|
|
85
|
-
|
|
86
|
-
leave together on one 0.3 s fade, and the linter counts each exit as motion.
|
|
87
|
-
Accept that warning here and fix any other.
|
|
35
|
+
## A product belongs on a card over its own context
|
|
88
36
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
and put the product on the right and the copy on the left.
|
|
37
|
+
Cutting straight from a lifestyle shot to a plain background loses the thing the
|
|
38
|
+
shot was selling. Keep a blurred, darkened copy as the field and put the product
|
|
39
|
+
on a rounded card above it: the eye reads "this object, in that place".
|
|
93
40
|
|
|
94
|
-
|
|
41
|
+
```vidscript
|
|
42
|
+
input hero = "https://cdn.example.com/product.jpg"
|
|
43
|
+
input room = "https://cdn.example.com/room.jpg"
|
|
95
44
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`fix`/`feat` lines rather than estimating. The logo is the project's own
|
|
100
|
-
(`https://avatars.githubusercontent.com/u/<id>`, or the README image). The
|
|
101
|
-
background is a brand glow made with ffmpeg:
|
|
45
|
+
# The background is the same shot, blurred and darkened ahead of time with
|
|
46
|
+
# ffmpeg, so the card sits in context instead of on a flat colour.
|
|
47
|
+
input roomBg = "https://cdn.example.com/room-bg.jpg"
|
|
102
48
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```
|
|
49
|
+
[0s .. 4s] = roomBg, width: 1080, height: 1920, x: 0, y: 0
|
|
50
|
+
[0.4s .. 4s] = hero, width: 900, height: 880, x: 90, y: 300, radius: 48
|
|
51
|
+
[0.8s .. 4s] = text "The product name", size: 72, color: "#F5F5F7", x: "50%", y: "71%", max_width: "88%", align: center
|
|
107
52
|
|
|
108
|
-
|
|
109
|
-
import motion from "@scenerok/basic-animations"
|
|
110
|
-
import animations from "@scenerok/animations"
|
|
111
|
-
import eleven from "@elevenlabs/music"
|
|
112
|
-
|
|
113
|
-
let ink = "#F5F5F7"
|
|
114
|
-
let muted = "#9CA3AF"
|
|
115
|
-
let accent = "#FF5B11"
|
|
116
|
-
let display = "Archivo Black"
|
|
117
|
-
let mono = "JetBrains Mono"
|
|
118
|
-
|
|
119
|
-
input glow = "glow.png" # 1080x1920, made with ffmpeg: brand-colour glow on near-black
|
|
120
|
-
input flame = "flame.png" # 460x460 RGBA, the project's logo
|
|
121
|
-
|
|
122
|
-
# Ambient layer: the background drifts for the whole video.
|
|
123
|
-
[0s .. 18s] = glow, width: 1080, height: 1920, x: 0, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.15 }, easing: "linear" }
|
|
124
|
-
|
|
125
|
-
# 1. Hook (0-3.5 s)
|
|
126
|
-
[0s .. 3.5s] = flame, width: 460, height: 460, x: 310, y: 340, blend: "normal", animate: [motion.popIn(0.6s), motion.float(2.9s, 14), motion.fadeOut(0.3s)]
|
|
127
|
-
[0.3s .. 3.5s] = text "RELEASE NOTES", font: mono, size: 44, color: accent, letter_spacing: 8, x: "50%", y: "50%", align: center, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
128
|
-
[0.5s .. 3.5s] = text "hono v4.13.14", font: display, size: 120, color: ink, x: "50%", y: "57%", max_width: "88%", align: center, animate: [motion.riseIn(0.6s, 60), motion.fadeOut(0.3s)]
|
|
129
|
-
|
|
130
|
-
# 2. The new feature as code (3.5-10 s): headline, card, then the code types itself.
|
|
131
|
-
[3.5s .. 10s] = text "NEW IN BEARER-AUTH", font: mono, size: 44, color: accent, letter_spacing: 6, x: 96, y: 300, anchor: "left", align: left, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
132
|
-
[3.6s .. 10s] = text "Error messages can\nreturn a Response", font: display, size: 84, color: ink, line_height: 1.1, x: 96, y: 440, anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
133
|
-
[4s .. 10s] = solid("#16161D", width: 960, height: 700, x: 60, y: 620, radius: 32), animate: [motion.riseIn(0.5s, 60), motion.fadeOut(0.3s)]
|
|
134
|
-
# One text layer per code line: a tall multi-line block can clip. Indent with x, not spaces.
|
|
135
|
-
[4.4s .. 10s] = text "bearerAuth({ token,", font: mono, size: 44, color: ink, x: 108, y: 760, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
136
|
-
[4.9s .. 10s] = text "invalidToken: {", font: mono, size: 44, color: ink, x: 164, y: 836, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
137
|
-
[5.4s .. 10s] = text "message: (c) =>", font: mono, size: 44, color: accent, x: 220, y: 912, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
138
|
-
[5.9s .. 10s] = text "c.json(problem, 401),", font: mono, size: 44, color: accent, x: 276, y: 988, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
139
|
-
[6.4s .. 10s] = text "}})", font: mono, size: 44, color: ink, x: 108, y: 1064, anchor: "left", align: left, animate: [motion.typewriter(0.3s), motion.fadeOut(0.3s)]
|
|
140
|
-
# Highlight beat: a bar sweeps under the changed lines once the code is typed.
|
|
141
|
-
[7.2s .. 10s] = solid(accent, width: 900, height: 158, x: 90, y: 872, radius: 16, opacity: 0.16), animate: [{ type: "slideX", from: { x: -1000 }, to: { x: 0 }, start: 0, end: 0.5, easing: "easeOutCubic" }, motion.fadeOut(0.3s)]
|
|
142
|
-
[7.4s .. 10s] = text "Any content type, e.g. problem+json", font: mono, size: 44, color: muted, x: 108, y: 1220, anchor: "left", align: left, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
143
|
-
|
|
144
|
-
# 3. Highlight (10-14 s): one number, then three rows. suffix: "" because the block defaults to "+".
|
|
145
|
-
[10s .. 14s] = animations.text_counter_stat("12", "BUG FIXES", suffix: "", accent: accent)
|
|
146
|
-
[11s .. 14s] = text "serve-static: GET and HEAD only", font: mono, size: 44, color: ink, x: "50%", y: "70%", align: center, animate: [motion.riseIn(0.4s, 30), motion.fadeOut(0.3s)]
|
|
147
|
-
[11.4s .. 14s] = text "css: safer url() minifying", font: mono, size: 44, color: ink, x: "50%", y: "74.5%", align: center, animate: [motion.riseIn(0.4s, 30), motion.fadeOut(0.3s)]
|
|
148
|
-
[11.8s .. 14s] = text "trie-router: keep first param", font: mono, size: 44, color: ink, x: "50%", y: "79%", align: center, animate: [motion.riseIn(0.4s, 30), motion.fadeOut(0.3s)]
|
|
149
|
-
|
|
150
|
-
# 4. Outro (14-18 s)
|
|
151
|
-
[14s .. 18s] = flame, width: 320, height: 320, x: 380, y: 520, blend: "normal", animate: [motion.popIn(0.5s), motion.float(3s, 12)]
|
|
152
|
-
[14.3s .. 18s] = solid("#1C1C24", width: 860, height: 120, x: 110, y: 950, radius: 60), animate: motion.popIn(0.5s)
|
|
153
|
-
[14.3s .. 18s] = text "npm i hono@4.13.14", font: mono, size: 48, color: ink, x: "50%", y: 1010, align: center, animate: motion.fadeIn(0.4s)
|
|
154
|
-
[14.8s .. 18s] = text "github.com/honojs/hono", font: mono, size: 44, color: muted, x: "50%", y: 1150, align: center, animate: motion.fadeIn(0.4s)
|
|
155
|
-
|
|
156
|
-
# Grade
|
|
157
|
-
[0s .. 18s] = filter "cdl", slope: "1.03 1.0 0.97", offset: "0 0 0", power: "1 1 1", saturation: 1.05
|
|
158
|
-
[0s .. 18s] = filter "vignette", intensity: 0.35
|
|
159
|
-
|
|
160
|
-
let bed = eleven.music("Minimal driving synth pulse for a developer product update, clean and modern", duration: 18, instrumental: true)
|
|
161
|
-
[0s .. 18s] = audio bed, volume: 0.5, fade_out: 1.5s
|
|
162
|
-
|
|
163
|
-
output to "release.mp4", resolution: "1080x1920", fps: 30, background: "#0B0B0F"
|
|
53
|
+
output to "card.mp4", resolution: "1080x1920", fps: 30
|
|
164
54
|
```
|
|
165
55
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
there; fix any other warning. This recipe builds its own code card so the
|
|
169
|
-
headline, card and caption can share one frame. The `code_snippet_*` themes and
|
|
170
|
-
`code_diff` also have 9:16 layouts of their own (see their records in
|
|
171
|
-
`repertoire.md`): reach for them when the code is the whole beat.
|
|
56
|
+
Fights: a busy background behind the card (blur it more), or a product shot that
|
|
57
|
+
is already on white — then the card is redundant and a plain field is better.
|
|
172
58
|
|
|
173
|
-
##
|
|
59
|
+
## A code beat needs one line to land
|
|
174
60
|
|
|
175
|
-
A
|
|
176
|
-
|
|
177
|
-
|
|
61
|
+
A file on screen is not a beat; the line that matters is. Put the code in a
|
|
62
|
+
card, keep four to six lines, and let one line carry the highlight. Use
|
|
63
|
+
`animations.code_snippet_dark_2026` (or a portrait variant — see the record) for
|
|
64
|
+
the card, and place the changed line as its own text layer when it must be the
|
|
65
|
+
loudest thing in the frame. Use the `code` job in
|
|
66
|
+
[repertoire.md](repertoire.md#find-tools-by-job) to compare the card options.
|
|
178
67
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
[7.2s .. 10s] = broll.Trim(0s, 2.8s), width: 3413, height: 1920, x: -1166, y: 0, animate: { type: "scale", from: { scale: 1.1 }, to: { scale: 1 }, easing: "linear" }
|
|
198
|
-
[9.8s .. 10.2s] = tr.dissolve()
|
|
199
|
-
[10.2s .. 14s] = talk.Trim(22.2s, 26s), width: 3413, height: 1920, x: -1166, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.05 }, easing: "linear" }
|
|
200
|
-
|
|
201
|
-
# Name card from the catalog (it has a native 9:16 layout).
|
|
202
|
-
[0.6s .. 4s] = animations.lt_kicker_name("FOUNDER", "Asha Rao", accent: accent)
|
|
203
|
-
|
|
204
|
-
# Captions: 2-4 words per card, timed to the transcript, the key words in the accent colour.
|
|
205
|
-
[0.2s .. 1.4s] = text "We shipped it", font: display, size: 72, color: ink, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
206
|
-
[1.4s .. 2.8s] = text "in nine days", font: display, size: 72, color: accent, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
207
|
-
[4s .. 5.6s] = text "and it paid", font: display, size: 72, color: ink, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
208
|
-
[5.6s .. 7s] = text "for itself", font: display, size: 72, color: accent, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
209
|
-
|
|
210
|
-
# Sound: the speaker's own audio, with music ducked under it.
|
|
211
|
-
[0s .. 7s] = audio talk.Trim(12s, 19s)
|
|
212
|
-
[10.2s .. 14s] = audio talk.Trim(22.2s, 26s)
|
|
213
|
-
[0s .. 14s] = audio bed, volume: 0.5, duck_under: talk, fade_out: 1s
|
|
214
|
-
|
|
215
|
-
[0s .. 14s] = filter "cdl", slope: "1.02 1.0 0.98", offset: "0 0 0", power: "1 1 1", saturation: 1.05
|
|
216
|
-
|
|
217
|
-
output to "talk.mp4", resolution: "1080x1920", fps: 30
|
|
218
|
-
```
|
|
68
|
+
Fights: more than ~7 lines on a phone, a second animation in the same beat, or
|
|
69
|
+
copy that repeats the code's meaning.
|
|
70
|
+
|
|
71
|
+
## A number deserves a count
|
|
72
|
+
|
|
73
|
+
When the beat is "how much", the number is the picture. A counter block
|
|
74
|
+
(`text_counter_stat`, `apple_money_count`) makes the eye follow the change;
|
|
75
|
+
static type makes it a caption. Fights: numbers that are not the point, or two
|
|
76
|
+
counters in one video.
|
|
77
|
+
|
|
78
|
+
## A type-led beat is a legitimate beat
|
|
79
|
+
|
|
80
|
+
Some content *is* the words: a quote, a price, a definition, a breaking change,
|
|
81
|
+
a legal line. Still type on a quiet field, with generous margins and no motion,
|
|
82
|
+
respects the reader. Reach for it when motion would compete; do not treat it as
|
|
83
|
+
the safe default for a beat that has a real picture available.
|
|
84
|
+
|
|
85
|
+
## Cut, unless the cut is the problem
|
|
219
86
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
87
|
+
A hard cut is the strongest transition and the one an audience never notices. A
|
|
88
|
+
transition earns its place when it carries meaning — a wipe across a change of
|
|
89
|
+
place, a match on shape, a dissolve on a memory. Otherwise it delays the beat.
|
|
90
|
+
Compare the `transition` records before choosing one.
|
|
@@ -11,7 +11,7 @@ Contents: Agent quick start · Program structure · Statements (input, output, l
|
|
|
11
11
|
1. **Import every package function** — use `import xai from "@scenerok/xai"`, `import cf from "@scenerok/cloudflare"`, `import eleven from "@elevenlabs/music"`, and `import motion from "@scenerok/basic-animations"` before calling `xai.*`, `cf.*`, `eleven.*`, or `motion.*`.
|
|
12
12
|
2. **Validate early** — `scenerok validate file.vid` (errors show as `Line N:C — message`).
|
|
13
13
|
3. **Music package syntax** — `import eleven from "@elevenlabs/music"`, then call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`.
|
|
14
|
-
4. **Choose per beat** — name each beat's job, then pick tools with `choosing.md` and `repertoire.md` (what each tool looks like, is for, and fights). `recipes.md` has
|
|
14
|
+
4. **Choose per beat** — name each beat's job, then pick tools with `choosing.md` and `repertoire.md` (what each tool looks like, is for, and fights). `recipes.md` has the visual patterns and a plugin's README owns its beat order; `look-and-feel.md` has the type scale and safe areas.
|
|
15
15
|
5. **Look at a URL's pictures before choosing** — when the user gives a website or product URL, collect the page's own product images, logo and focused screenshots (`look-and-feel.md` → Sourcing imagery), then decide per beat whether to use them. Don't use a full-page screenshot as a background.
|
|
16
16
|
6. **Probe asset type and dimensions** — before placing real assets, identify image vs video with `file`; inspect width, height, and duration with `ffprobe`, `sips`, or `identify`; then scale from the measured aspect ratio and set explicit `width`, `height`, `x`, and `y`.
|
|
17
17
|
7. **Use still images directly** — `input` supports videos and images. Place still screenshots, logos, and product photos with `video image_input`; the renderer treats images as static clips for the block duration.
|