@scenerok/sdk 0.27.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 +1 -1
- package/skills/scenerok/SKILL.md +81 -54
- package/skills/scenerok/references/animations-picker.md +1 -1
- package/skills/scenerok/references/catalog-index.json +2855 -1
- package/skills/scenerok/references/choosing.md +111 -0
- package/skills/scenerok/references/intent-router.md +3 -3
- package/skills/scenerok/references/look-and-feel.md +25 -18
- package/skills/scenerok/references/plugins.md +5 -4
- package/skills/scenerok/references/recipes.md +69 -200
- package/skills/scenerok/references/repertoire.md +1493 -0
- package/skills/scenerok/references/shaders-filters.md +1 -1
- package/skills/scenerok/references/vidscript-guide.md +2 -2
- package/skills/scenerok/references/vidscript-strict.md +4 -4
- package/skills/scenerok/references/workflow.md +3 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Choosing tools for a beat
|
|
2
|
+
|
|
3
|
+
SceneRok's blocks, motion, transitions, filters and media are a repertoire,
|
|
4
|
+
not a checklist. Nothing is required, nothing is decoration by default: each
|
|
5
|
+
beat gets what its job calls for. `repertoire.md` says what every tool looks
|
|
6
|
+
like, what it is for, and what it fights. This file is how to use that.
|
|
7
|
+
|
|
8
|
+
## The procedure
|
|
9
|
+
|
|
10
|
+
For each beat:
|
|
11
|
+
|
|
12
|
+
1. **Name its job** with one word from the job list in `repertoire.md`
|
|
13
|
+
(announce, count, quote, code, prove, demo, establish, cta, ...), and the one
|
|
14
|
+
thing the viewer must take away.
|
|
15
|
+
2. **Put the material on the table.** What do you actually have for this beat:
|
|
16
|
+
the source's photos, screenshots, footage, logo, the exact numbers and words?
|
|
17
|
+
You can only choose a picture you have looked at.
|
|
18
|
+
3. **List the candidates** under that job in `repertoire.md` → Find tools by job.
|
|
19
|
+
Plain `text` and "nothing extra" are always candidates.
|
|
20
|
+
4. **Drop what fights.** Read each candidate's *Fights* against your content
|
|
21
|
+
(length of the words, busyness of the picture), its neighbours (the beat
|
|
22
|
+
before and after, anything else moving) and the frame (9:16 or 16:9: read
|
|
23
|
+
*Fit*).
|
|
24
|
+
5. **Choose by the look.** Read *Looks* for the survivors and pick the frame you
|
|
25
|
+
want the viewer to see. When two would do, take the simpler.
|
|
26
|
+
6. **Write one line**: `<beat>: <tool> because <reason>`. If you can't finish
|
|
27
|
+
the sentence, choose something plainer.
|
|
28
|
+
7. **Verify by looking.** After rendering, open a frame of that beat. If it
|
|
29
|
+
doesn't look like the *Looks* you chose for, the description or your
|
|
30
|
+
placement is wrong. Fix the placement, and report the description.
|
|
31
|
+
|
|
32
|
+
## When plain text on a still frame is right, and when it is not
|
|
33
|
+
|
|
34
|
+
Right, when:
|
|
35
|
+
|
|
36
|
+
- the words are the content: a quote, a definition, a price, a command, a
|
|
37
|
+
legal line;
|
|
38
|
+
- the viewer needs reading time, and anything moving would compete;
|
|
39
|
+
- the design is typographic on purpose (a brand that speaks in type);
|
|
40
|
+
- there is no honest picture: an abstract claim, an internal change with no
|
|
41
|
+
UI. A stock or generated image would only pretend.
|
|
42
|
+
|
|
43
|
+
Wrong, when:
|
|
44
|
+
|
|
45
|
+
- the source has pictures that show the thing (a product page, an app, a repo
|
|
46
|
+
with a UI) and the beat's job is demo, prove or establish;
|
|
47
|
+
- several beats in a row are text on the same flat colour, so the video turns
|
|
48
|
+
into a slideshow;
|
|
49
|
+
- the text says what a picture could show ("sleek design", "easy to use").
|
|
50
|
+
|
|
51
|
+
The same goes for motion: a still frame is right when the eye should rest.
|
|
52
|
+
Motion is for arrival, emphasis and time passing, and one moving thing at a
|
|
53
|
+
time is easier to follow than three.
|
|
54
|
+
|
|
55
|
+
## Worked choices
|
|
56
|
+
|
|
57
|
+
**Product promo, hook (9:16).** Job: establish. Material: three lifestyle
|
|
58
|
+
photos from the product page, a 500 px hero shot on white. Candidates: a page
|
|
59
|
+
photo, `xai.imagine`, `animations.liquid_glass_notification`, `text`.
|
|
60
|
+
Dropped: `xai.imagine` fights facts (it would invent a different robot); the
|
|
61
|
+
liquid-glass block is fixed artwork in rainbow colours. Chosen: the lifestyle
|
|
62
|
+
photo as a band over a blurred copy of itself, with a 3 s push, because a still
|
|
63
|
+
held for 3 s reads as a slide and the push gives it time. Headline: `text`
|
|
64
|
+
with `riseIn`.
|
|
65
|
+
|
|
66
|
+
**Customer review (any aspect).** Job: quote. Material: one two-sentence review
|
|
67
|
+
from the page. Candidates: `text_typewriter_quote`, `x_post`, `reddit_post`,
|
|
68
|
+
`text`. Dropped: `x_post` and `reddit_post` would dress the review as a post on
|
|
69
|
+
a platform it never appeared on; the typewriter takes three seconds to type
|
|
70
|
+
what a reader takes two to read. Chosen: plain `text` on a flat dark frame with
|
|
71
|
+
a muted attribution, no motion, held 5 s.
|
|
72
|
+
|
|
73
|
+
**Breaking change in release notes.** Job: announce. Material: one sentence
|
|
74
|
+
from the changelog. Chosen: plain `text`, no entrance, no grade change, held
|
|
75
|
+
4 s. Gravity is the point; any flourish would make a warning read like
|
|
76
|
+
marketing. Nothing else was considered necessary, and that is a valid outcome
|
|
77
|
+
of step 3.
|
|
78
|
+
|
|
79
|
+
**A stat (9:16).** Job: count. Material: "12 bug fixes" counted from the
|
|
80
|
+
release body. Candidates: `text_counter_stat`, `apple_money_count`, `text` with
|
|
81
|
+
`popIn`. Dropped: `apple_money_count` is money on a white slab. Chosen:
|
|
82
|
+
`text_counter_stat` with `suffix: ""` (its default `+` would turn 12 into
|
|
83
|
+
"12+", which is false).
|
|
84
|
+
|
|
85
|
+
**Code in 9:16.** Job: code. Material: a five-line before/after. Candidates:
|
|
86
|
+
`code_typing`, `code_diff`, a `code_snippet_*` theme, `text` lines. Dropped:
|
|
87
|
+
`code_typing` (in 9:16 its code shrinks to about 17 px). Chosen: `code_diff`,
|
|
88
|
+
which has its own 9:16 layout and keeps the removed line on screen with
|
|
89
|
+
`keep_removed: true`, so the change reads as a before/after. Its *Looks* says it
|
|
90
|
+
sits in the upper half, so the beat's headline goes in the empty lower half
|
|
91
|
+
instead of on top of the code.
|
|
92
|
+
|
|
93
|
+
**Cut between two lifestyle shots in a calm promo.** Job: transition.
|
|
94
|
+
Candidates: every `tr.*`, and a hard cut. Dropped: everything whose *Fights*
|
|
95
|
+
says "calm or premium edits"; `flash` (flicker). Chosen: a hard cut, because
|
|
96
|
+
the shots differ enough to read as a new beat. `dissolve` only if the two shots
|
|
97
|
+
share a subject. A transition is a candidate, not a default.
|
|
98
|
+
|
|
99
|
+
**Talking head, key line.** Job: emphasize. Material: a 16:9 interview, the
|
|
100
|
+
line "it paid for itself". Candidates: a punch-in (scale the clip),
|
|
101
|
+
`text_kinetic_words`, `blend_caption`, `text_glitch_title`. Dropped: kinetic
|
|
102
|
+
words fight a sentence that must be read whole; glitch fights the tone.
|
|
103
|
+
Chosen: a punch-in on the speaker with a two-word caption card in the accent
|
|
104
|
+
colour.
|
|
105
|
+
|
|
106
|
+
## Before you trust a description
|
|
107
|
+
|
|
108
|
+
The records come from the block source and the catalog previews. Some previews
|
|
109
|
+
predate a block's 9:16 layout, and a few are blank (see each record's *Fit*).
|
|
110
|
+
When a choice rests on a block you have never seen rendered, render a short
|
|
111
|
+
test or look at its preview before building the video around it.
|
|
@@ -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`, …)
|
|
@@ -58,13 +58,16 @@ than it predicts, so check the rendered frame before trusting a line to fit.
|
|
|
58
58
|
|
|
59
59
|
## Pictures
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
Whether a beat gets a picture is a choice (`choosing.md`). When it does, the
|
|
62
|
+
sources rank like this:
|
|
63
|
+
|
|
64
|
+
1. **Real**: product photos, logos, app screenshots, footage, screen
|
|
65
|
+
recordings. They prove things; nothing else does.
|
|
66
|
+
2. **Generated**: `xai.image`, `xai.imageToVideo` from a real photo,
|
|
67
|
+
`xai.imagine` (`ai-models.md`; prompts forbid text). They set a mood and
|
|
68
|
+
invent everything else.
|
|
66
69
|
3. **Made**: a brand-colour glow or gradient rendered with ffmpeg (recipes →
|
|
67
|
-
Release notes)
|
|
70
|
+
Release notes). Atmosphere without a subject.
|
|
68
71
|
|
|
69
72
|
Placing stills in a frame of a different shape:
|
|
70
73
|
|
|
@@ -112,15 +115,17 @@ which needs no file.
|
|
|
112
115
|
|
|
113
116
|
## Motion
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
When you do use motion, these timings read well. What each primitive looks
|
|
119
|
+
like is in `repertoire.md`.
|
|
120
|
+
|
|
121
|
+
| Phase | Typical choice | Timing |
|
|
116
122
|
| --- | --- | --- |
|
|
117
123
|
| Entrance | `motion.riseIn(0.5s, 40-80)` for copy and cards, `motion.popIn(0.4-0.6s)` for prices, buttons, logos, `motion.typewriter` for code, `motion.fadeIn(0.4s)` for secondary lines | Stagger items 0.3-0.6 s apart |
|
|
118
|
-
| Hold |
|
|
119
|
-
| Exit | `motion.fadeOut(0.3s)`
|
|
124
|
+
| Hold | Nothing (rest the eye), or a push on a still `{ type: "scale", from: { scale: 1 }, to: { scale: 1.08 }, easing: "linear" }`, or `motion.float(3s, 10-14)` on a logo or product | A still hold beyond ~3 s should be one you meant: reading time, gravity |
|
|
125
|
+
| Exit | A cut, or `motion.fadeOut(0.3s)` when the element should leave softly | The last 0.3 s of the block |
|
|
120
126
|
|
|
121
|
-
A push on a soft gradient
|
|
122
|
-
|
|
123
|
-
(`editing-actions.md`).
|
|
127
|
+
A push on a soft gradient is invisible, so it adds nothing. Fast moves can take
|
|
128
|
+
`motion_blur:` (`editing-actions.md`).
|
|
124
129
|
|
|
125
130
|
## Pacing
|
|
126
131
|
|
|
@@ -129,11 +134,13 @@ or a floating element to move. Fast moves can take `motion_blur:`
|
|
|
129
134
|
- One idea per beat: the hook, the product, three features, the price, and the call to action.
|
|
130
135
|
- Close on the call to action for at least 2.5 s.
|
|
131
136
|
|
|
132
|
-
## What premium
|
|
137
|
+
## What premium tends to mean
|
|
138
|
+
|
|
139
|
+
Tendencies, not requirements; each is a choice you can argue against.
|
|
133
140
|
|
|
134
|
-
-
|
|
141
|
+
- Real pictures where the subject has them; type alone where the words are the point.
|
|
135
142
|
- One accent, used sparingly; everything else is ink and muted on dark (or the reverse).
|
|
136
|
-
- Big type with tight copy: 3-6 words a line,
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
- Sound
|
|
143
|
+
- Big type with tight copy: 3-6 words a line, rarely a paragraph.
|
|
144
|
+
- Restraint: one thing moving at a time, consistent entrances, stillness where the eye should rest.
|
|
145
|
+
- A grade only if the pictures need tying together: `filter "cdl"` warmth or coolness, a `vignette` of about 0.3.
|
|
146
|
+
- Sound that fits the use: many feeds play muted, so the picture must work without it.
|
|
@@ -7,10 +7,11 @@ cheaper than hand-composing. **Never assume which plugins exist** — discover
|
|
|
7
7
|
them at run time; the catalog grows and changes.
|
|
8
8
|
|
|
9
9
|
Plugins are the reuse-first path: check `intent-router.md` for the workflow
|
|
10
|
-
match.
|
|
11
|
-
first
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
match. Review a plugin's video with the same questions as your own (`SKILL.md` →
|
|
11
|
+
Review). Draft first and read the filled script. Today the first-party
|
|
12
|
+
`listing-promo` and `release-video` templates set every beat as text over a
|
|
13
|
+
flat colour and leave the product images unused. When the request calls for
|
|
14
|
+
more, change those beats before `render-draft`.
|
|
14
15
|
|
|
15
16
|
## Workflow (MCP)
|
|
16
17
|
|
|
@@ -1,221 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Visual patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
beat, motion on everything, and a grade and sound over the whole video.
|
|
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
|
-
# Slow push-in for every still, so no frame is ever frozen.
|
|
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.
|
|
42
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
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
[0s .. 3s] = roomBg, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
46
|
-
[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)]
|
|
47
|
-
[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)]
|
|
48
|
-
|
|
49
|
-
# 2. Hero (3-7 s): the product on its own white card.
|
|
50
|
-
[3s .. 7s] = solid(bg)
|
|
51
|
-
[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)]
|
|
52
|
-
[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)]
|
|
53
|
-
[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)]
|
|
54
|
-
|
|
55
|
-
# 3. Features (7-11 s): a close-up band, then one bullet every 0.6 s.
|
|
56
|
-
[7s .. 11s] = closeBg, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
57
|
-
[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)]
|
|
58
|
-
[7.4s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 868, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
59
|
-
[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)]
|
|
60
|
-
[8.0s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 1028, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
61
|
-
[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)]
|
|
62
|
-
[8.6s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 1188, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
63
|
-
[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)]
|
|
64
|
-
|
|
65
|
-
# 4. Price and CTA (11-15 s). The button is a solid with a radius under the label.
|
|
66
|
-
[11s .. 15s] = solid(bg)
|
|
67
|
-
[11s .. 15s] = hero, width: 560, height: 548, x: 260, y: 260, radius: 40, blend: "normal", animate: [motion.popIn(0.5s), motion.float(3s, 10)]
|
|
68
|
-
[11.3s .. 15s] = text "₹44,999", font: display, size: 150, color: ink, x: "50%", y: "50%", align: center, animate: motion.popIn(0.5s)
|
|
69
|
-
[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)
|
|
70
|
-
[12s .. 15s] = solid(accent, width: 660, height: 132, x: 210, y: 1210, radius: 66), animate: motion.popIn(0.5s)
|
|
71
|
-
[12s .. 15s] = text "Shop on Amazon", font: display, size: 56, color: "#FFFFFF", x: "50%", y: 1276, align: center, animate: motion.fadeIn(0.4s)
|
|
72
|
-
|
|
73
|
-
# Grade: warm the whole frame, pull the edges down.
|
|
74
|
-
[0s .. 15s] = filter "cdl", slope: "1.04 1.0 0.96", offset: "0 0 0", power: "1 1 1", saturation: 1.08
|
|
75
|
-
[0s .. 15s] = filter "vignette", intensity: 0.3
|
|
76
|
-
|
|
77
|
-
# Sound: a bed under everything.
|
|
78
|
-
let bed = eleven.music("Bright, modern electronic product-ad bed, confident pulse, clean ending", duration: 15, instrumental: true)
|
|
79
|
-
[0s .. 15s] = audio bed, volume: 0.5, fade_out: 1s
|
|
80
|
-
|
|
81
|
-
output to "promo.mp4", resolution: "1080x1920", fps: 30, background: "#0A0C10"
|
|
32
|
+
output to "beat.mp4", resolution: "1080x1920", fps: 30
|
|
82
33
|
```
|
|
83
34
|
|
|
84
|
-
|
|
85
|
-
leave together on one 0.3 s fade, and the linter counts each exit as motion.
|
|
86
|
-
Accept that warning here and fix any other.
|
|
35
|
+
## A product belongs on a card over its own context
|
|
87
36
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
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".
|
|
92
40
|
|
|
93
|
-
|
|
41
|
+
```vidscript
|
|
42
|
+
input hero = "https://cdn.example.com/product.jpg"
|
|
43
|
+
input room = "https://cdn.example.com/room.jpg"
|
|
94
44
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`fix`/`feat` lines rather than estimating. The logo is the project's own
|
|
99
|
-
(`https://avatars.githubusercontent.com/u/<id>`, or the README image). The
|
|
100
|
-
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"
|
|
101
48
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```
|
|
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
|
|
106
52
|
|
|
107
|
-
|
|
108
|
-
import motion from "@scenerok/basic-animations"
|
|
109
|
-
import animations from "@scenerok/animations"
|
|
110
|
-
import eleven from "@elevenlabs/music"
|
|
111
|
-
|
|
112
|
-
let ink = "#F5F5F7"
|
|
113
|
-
let muted = "#9CA3AF"
|
|
114
|
-
let accent = "#FF5B11"
|
|
115
|
-
let display = "Archivo Black"
|
|
116
|
-
let mono = "JetBrains Mono"
|
|
117
|
-
|
|
118
|
-
input glow = "glow.png" # 1080x1920, made with ffmpeg: brand-colour glow on near-black
|
|
119
|
-
input flame = "flame.png" # 460x460 RGBA, the project's logo
|
|
120
|
-
|
|
121
|
-
# Ambient layer: the background drifts for the whole video.
|
|
122
|
-
[0s .. 18s] = glow, width: 1080, height: 1920, x: 0, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.15 }, easing: "linear" }
|
|
123
|
-
|
|
124
|
-
# 1. Hook (0-3.5 s)
|
|
125
|
-
[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)]
|
|
126
|
-
[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)]
|
|
127
|
-
[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)]
|
|
128
|
-
|
|
129
|
-
# 2. The new feature as code (3.5-10 s): headline, card, then the code types itself.
|
|
130
|
-
[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)]
|
|
131
|
-
[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)]
|
|
132
|
-
[4s .. 10s] = solid("#16161D", width: 960, height: 700, x: 60, y: 620, radius: 32), animate: [motion.riseIn(0.5s, 60), motion.fadeOut(0.3s)]
|
|
133
|
-
# One text layer per code line: a tall multi-line block can clip. Indent with x, not spaces.
|
|
134
|
-
[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)]
|
|
135
|
-
[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)]
|
|
136
|
-
[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)]
|
|
137
|
-
[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)]
|
|
138
|
-
[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)]
|
|
139
|
-
# Highlight beat: a bar sweeps under the changed lines once the code is typed.
|
|
140
|
-
[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)]
|
|
141
|
-
[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)]
|
|
142
|
-
|
|
143
|
-
# 3. Highlight (10-14 s): one number, then three rows. suffix: "" because the block defaults to "+".
|
|
144
|
-
[10s .. 14s] = animations.text_counter_stat("12", "BUG FIXES", suffix: "", accent: accent)
|
|
145
|
-
[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)]
|
|
146
|
-
[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)]
|
|
147
|
-
[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)]
|
|
148
|
-
|
|
149
|
-
# 4. Outro (14-18 s)
|
|
150
|
-
[14s .. 18s] = flame, width: 320, height: 320, x: 380, y: 520, blend: "normal", animate: [motion.popIn(0.5s), motion.float(3s, 12)]
|
|
151
|
-
[14.3s .. 18s] = solid("#1C1C24", width: 860, height: 120, x: 110, y: 950, radius: 60), animate: motion.popIn(0.5s)
|
|
152
|
-
[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)
|
|
153
|
-
[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)
|
|
154
|
-
|
|
155
|
-
# Grade
|
|
156
|
-
[0s .. 18s] = filter "cdl", slope: "1.03 1.0 0.97", offset: "0 0 0", power: "1 1 1", saturation: 1.05
|
|
157
|
-
[0s .. 18s] = filter "vignette", intensity: 0.35
|
|
158
|
-
|
|
159
|
-
let bed = eleven.music("Minimal driving synth pulse for a developer product update, clean and modern", duration: 18, instrumental: true)
|
|
160
|
-
[0s .. 18s] = audio bed, volume: 0.5, fade_out: 1.5s
|
|
161
|
-
|
|
162
|
-
output to "release.mp4", resolution: "1080x1920", fps: 30, background: "#0B0B0F"
|
|
53
|
+
output to "card.mp4", resolution: "1080x1920", fps: 30
|
|
163
54
|
```
|
|
164
55
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
there; fix any other warning. The `code_*` catalog blocks are 1920x1080
|
|
168
|
-
designs: use them in 16:9, where they fill the frame. In 9:16 they shrink to
|
|
169
|
-
56 % with empty bands above and below.
|
|
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.
|
|
170
58
|
|
|
171
|
-
##
|
|
59
|
+
## A code beat needs one line to land
|
|
172
60
|
|
|
173
|
-
A
|
|
174
|
-
|
|
175
|
-
|
|
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.
|
|
176
67
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
[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" }
|
|
196
|
-
[9.8s .. 10.2s] = tr.dissolve()
|
|
197
|
-
[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" }
|
|
198
|
-
|
|
199
|
-
# Name card from the catalog (it has a native 9:16 layout).
|
|
200
|
-
[0.6s .. 4s] = animations.lt_kicker_name("FOUNDER", "Asha Rao", accent: accent)
|
|
201
|
-
|
|
202
|
-
# Captions: 2-4 words per card, timed to the transcript, the key words in the accent colour.
|
|
203
|
-
[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)
|
|
204
|
-
[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)
|
|
205
|
-
[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)
|
|
206
|
-
[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)
|
|
207
|
-
|
|
208
|
-
# Sound: the speaker's own audio, with music ducked under it.
|
|
209
|
-
[0s .. 7s] = audio talk.Trim(12s, 19s)
|
|
210
|
-
[10.2s .. 14s] = audio talk.Trim(22.2s, 26s)
|
|
211
|
-
[0s .. 14s] = audio bed, volume: 0.5, duck_under: talk, fade_out: 1s
|
|
212
|
-
|
|
213
|
-
[0s .. 14s] = filter "cdl", slope: "1.02 1.0 0.98", offset: "0 0 0", power: "1 1 1", saturation: 1.05
|
|
214
|
-
|
|
215
|
-
output to "talk.mp4", resolution: "1080x1920", fps: 30
|
|
216
|
-
```
|
|
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
|
|
217
86
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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.
|