@lalalic/markcut 1.0.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/.env.example +27 -0
- package/.github/user-steer.md +9 -0
- package/.vscode/settings.json +3 -0
- package/AGENTS.md +271 -0
- package/README.md +219 -0
- package/SKILL.md +209 -0
- package/docs/dynamic-components.md +191 -0
- package/docs/edit-mode.md +220 -0
- package/docs/json-descriptive.md +539 -0
- package/docs/label-mode.md +110 -0
- package/docs/markdown-descriptive.md +751 -0
- package/docs/templates.md +52 -0
- package/package.json +64 -0
- package/remotion.config.ts +5 -0
- package/scripts/artlist-dl.mjs +190 -0
- package/scripts/build-pipeline.sh +19 -0
- package/scripts/build-player.sh +20 -0
- package/src/Root.tsx +55 -0
- package/src/config.mjs +88 -0
- package/src/context/EventContext.tsx +168 -0
- package/src/context/index.tsx +42 -0
- package/src/descriptive/compiler.test.ts +1135 -0
- package/src/descriptive/compiler.ts +1230 -0
- package/src/descriptive/dsl.ts +455 -0
- package/src/descriptive/markdown.test.ts +866 -0
- package/src/descriptive/markdown.ts +674 -0
- package/src/descriptive/resolve.test.ts +951 -0
- package/src/descriptive/resolve.ts +891 -0
- package/src/entry.tsx +163 -0
- package/src/index.ts +4 -0
- package/src/player/browser.tsx +356 -0
- package/src/player/bundle/player.js +60259 -0
- package/src/player/bundler.mjs +269 -0
- package/src/player/label-server.mjs +599 -0
- package/src/player/pipeline.mjs +11123 -0
- package/src/player/pipeline.ts +117 -0
- package/src/player/server-shared.mjs +144 -0
- package/src/player/server.mjs +1006 -0
- package/src/render/cli-tools.ts +177 -0
- package/src/render/cli.mjs +628 -0
- package/src/schema/index.ts +259 -0
- package/src/types/Audio.tsx +56 -0
- package/src/types/Component.tsx +135 -0
- package/src/types/Effect.tsx +88 -0
- package/src/types/Folder.tsx +180 -0
- package/src/types/FrameSyncStyle.tsx +51 -0
- package/src/types/Image.tsx +51 -0
- package/src/types/Include.tsx +394 -0
- package/src/types/Map.tsx +252 -0
- package/src/types/Rhythm.tsx +58 -0
- package/src/types/Scene.tsx +42 -0
- package/src/types/Subtitle.tsx +218 -0
- package/src/types/Video.tsx +70 -0
- package/src/types/keyframes.ts +454 -0
- package/src/utils/__tests__/vtt.test.ts +129 -0
- package/src/utils/index.ts +168 -0
- package/src/utils/tween.ts +118 -0
- package/src/vision/cli.mjs +1187 -0
- package/src/vision/vision_prompts.md +67 -0
- package/tests/dsl.test.ts +317 -0
- package/tests/fixtures/audio.json +25 -0
- package/tests/fixtures/basic.json +27 -0
- package/tests/fixtures/component-all.json +38 -0
- package/tests/fixtures/components.json +38 -0
- package/tests/fixtures/effects.json +64 -0
- package/tests/fixtures/full.json +51 -0
- package/tests/fixtures/map.json +23 -0
- package/tests/fixtures/md/all-nodes.md +28 -0
- package/tests/fixtures/md/basic.md +6 -0
- package/tests/fixtures/md/component-imports.md +20 -0
- package/tests/fixtures/md/edge-cases.md +33 -0
- package/tests/fixtures/md/effects.md +17 -0
- package/tests/fixtures/md/frontmatter.md +20 -0
- package/tests/fixtures/md/full-feature.md +58 -0
- package/tests/fixtures/md/imports-block.md +19 -0
- package/tests/fixtures/md/include-main.md +11 -0
- package/tests/fixtures/md/include-sub.md +25 -0
- package/tests/fixtures/md/jsx-code-fence.md +21 -0
- package/tests/fixtures/md/map.md +11 -0
- package/tests/fixtures/md/nested-scenes.md +25 -0
- package/tests/fixtures/md/rhythm.md +17 -0
- package/tests/fixtures/md/scenes.md +16 -0
- package/tests/fixtures/md/tween.md +11 -0
- package/tests/fixtures/md/vars-test.md +6 -0
- package/tests/fixtures/scenes.json +40 -0
- package/tests/fixtures/subtitle.json +59 -0
- package/tests/fixtures/subvideo.json +59 -0
- package/tests/fixtures/templates/courseware.md +351 -0
- package/tests/fixtures/tween-visual.json +28 -0
- package/tests/fixtures/video-series.json +54 -0
- package/tests/md-descriptive.test.ts +742 -0
- package/tests/render.test.ts +985 -0
- package/tests/schema.test.ts +68 -0
- package/tests/server.test.ts +308 -0
- package/tests/utils.ts +391 -0
- package/tests/vitest.config.ts +18 -0
- package/tests/vitest.integration.config.ts +16 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
# Markdown Descriptive (Agent Contract)
|
|
2
|
+
|
|
3
|
+
Complete reference for LLM-driven video generation. The parser uses **remark** (`unified` + `remark-parse` + `remark-frontmatter`) for structural parsing (headings, lists, code fences) and extracts raw text from source positions, so JSX values with `<`/`>` are preserved correctly.
|
|
4
|
+
|
|
5
|
+
## Output Contract
|
|
6
|
+
|
|
7
|
+
A markdown document compiled into a renderable scene tree.
|
|
8
|
+
|
|
9
|
+
- Top heading `# <name>` (heading text ignored, just marks document root; `# video`, `# sub-video`, `# anything` all work)
|
|
10
|
+
- Optional YAML frontmatter block `---\n...\n---\n` at the very top
|
|
11
|
+
- Root config line: `width:<n> height:<n> fps:<n> layout:<mode>` (key:value pairs on the line after `# video`)
|
|
12
|
+
- Scenes via `##`/`###`/`####` headings
|
|
13
|
+
- Leaf nodes via `- typeToken ...` bullets
|
|
14
|
+
- Component registrations via `` ~~~js imports `` code fence (or inline JSX definitions)
|
|
15
|
+
- Properties via indented code fences (`~~~<lang> <propName>`); `~~~script` only valid on audio nodes
|
|
16
|
+
|
|
17
|
+
## Frontmatter (metadata only)
|
|
18
|
+
|
|
19
|
+
A YAML block at the top of the document, delimited by `---`. Frontmatter is
|
|
20
|
+
**metadata only** — it does NOT affect video configuration. All video config
|
|
21
|
+
(width, height, fps, layout, tts, stt, stylesheet, etc.) comes from the
|
|
22
|
+
**root config line** (key:value pairs on the line after `# video`).
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
---
|
|
26
|
+
title: My Campaign
|
|
27
|
+
description: Q4 product launch
|
|
28
|
+
---
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Root Config Line
|
|
32
|
+
|
|
33
|
+
The line after `# video` contains all video configuration as space-separated
|
|
34
|
+
`key:value` pairs:
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
# video
|
|
38
|
+
width:1920 height:1080 fps:30 layout:series tts:"edge-tts --voice 'en-US-GuyNeural' --text '{input}' --write-media '{output}'" stylesheet:".bg { background: #000; }" subtitle:captions.vtt
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Supported keys: `width`, `height`, `fps`, `layout`, `tts`, `stt`, `tti`, `ttv`,
|
|
42
|
+
`transition`, `transitionTime`, `instruction`, `metadata`, `stylesheet`, `subtitle`.
|
|
43
|
+
|
|
44
|
+
Values containing spaces must be quoted with double or single quotes.
|
|
45
|
+
|
|
46
|
+
### Subtitle on the config line
|
|
47
|
+
|
|
48
|
+
```markdown
|
|
49
|
+
# video
|
|
50
|
+
width:640 height:480 subtitle:captions.vtt
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Currently only `src` (a VTT file path) is supported via the config line.
|
|
54
|
+
|
|
55
|
+
## Template Variables
|
|
56
|
+
|
|
57
|
+
`${width}`, `${height}`, `${fps}`, and `${variant}` can be used in `src`,
|
|
58
|
+
`prompt`, and `stylesheet` values. They are resolved at compile time using
|
|
59
|
+
the root config values.
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# video
|
|
63
|
+
width:1920 height:1080
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `src:photo_${width}x${height}.jpg` → `photo_1920x1080.jpg`
|
|
67
|
+
- `prompt:"generate an image at ${width}x${height}"` → `generate an image at 1920x1080`
|
|
68
|
+
- `stylesheet:".hero { width: ${width}px; }"` → `.hero { width: 1920px; }`
|
|
69
|
+
|
|
70
|
+
> Template variables are NOT resolved in root config keys, jsx, script, style,
|
|
71
|
+
> or other string fields — only in `src`, `prompt`, and `stylesheet`.
|
|
72
|
+
style: "color: yellow;"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Field | Required | Type | Notes |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `src` | yes | string | VTT path/URL, inline VTT body, or plain text |
|
|
78
|
+
| `type` | opt | string | caption animation: `Bounce`, `Fade`, `Typewriter`, `Colorful`, etc. Default: plain static caption |
|
|
79
|
+
| `fontSize` | opt | number | default 56 |
|
|
80
|
+
| `fontFamily` | opt | string | font family |
|
|
81
|
+
| `fontStyle` | opt | string | `normal`, `italic`, `bold`, etc. |
|
|
82
|
+
| `style` | opt | string | inline CSS for the overlay container |
|
|
83
|
+
|
|
84
|
+
> **HTML in cue text**: Cue text supports HTML tags with inline CSS, so you can style individual words:
|
|
85
|
+
> ```vtt
|
|
86
|
+
> 00:00:01.000 --> 00:00:03.000
|
|
87
|
+
> Hello <span style="color:#ff6b6b">world</span>, welcome to <b>our show</b>!
|
|
88
|
+
> ```
|
|
89
|
+
> The engine renders cue text via `dangerouslySetInnerHTML`, making `<span>`, `<b>`, `<i>`, `<br>`, and inline `style` attributes all work.
|
|
90
|
+
|
|
91
|
+
If `src` is plain text (no `-->` markers), it renders as a single caption for the full video duration. The `type` field maps to a `remotion-subtitle` animation component — omit for a plain static caption.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
### Imports block (recommended)
|
|
95
|
+
|
|
96
|
+
Use a `` ~~~js imports `` code fence at end of the document (or anywhere in the body). The block acts as a **component registry** — it re-exports components from external packages or defines them inline, making them available to JSX expressions throughout the video.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
~~~js imports
|
|
100
|
+
export { PieChart } from "recharts"
|
|
101
|
+
export { BarChart, LineChart } from "recharts"
|
|
102
|
+
export { StatCounter as Counter } from "stat-counter"
|
|
103
|
+
|
|
104
|
+
export function Hello({ name }) {
|
|
105
|
+
return <div style={{color: '#fff'}}>Hello {name}</div>;
|
|
106
|
+
}
|
|
107
|
+
~~~
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Think of this block as the **index file** for the video's component scope. `export { Name } from "spec"` re-exports an external component (conceptually correct — the block is the public API of available components). `export function Name()` defines an inline component directly.
|
|
111
|
+
|
|
112
|
+
The imports block is the **primary** way to register components. The legacy YAML `imports:` array in frontmatter is still supported as a fallback, but the code block is preferred.
|
|
113
|
+
|
|
114
|
+
> **How it works**: The server extracts the imports block from the raw source, parses it with `parseImportsBlock`, then runs `bundleFromEntries` which creates a temp npm project, installs packages, and bundles everything into a single ESM file with esbuild. The resulting bundle URL is set on `root.imports` (e.g. `/.component-cache/be710e5c.js`). At render time, `MarkCut.useComponentRegistry` dynamically imports this URL and feeds the named exports to react-jsx-parser.
|
|
115
|
+
|
|
116
|
+
Supported patterns inside the block:
|
|
117
|
+
|
|
118
|
+
| Pattern | Effect |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `export { Name } from "spec"` | Re-exports `Name` from the resolved source (recommended) |
|
|
121
|
+
| `export { Name as Alias } from "spec"` | Re-exports under `Alias` instead |
|
|
122
|
+
| `export { N1, N2 } from "spec"` | Re-exports multiple from the same source |
|
|
123
|
+
| `export default Name from "spec"` | Re-exports default export |
|
|
124
|
+
| `export function Name(...) { ... }` | Inline component definition |
|
|
125
|
+
| `export default function Name(...) { ... }` | Inline component definition (default) |
|
|
126
|
+
|
|
127
|
+
For compatibility, `import { Name } from "spec"` also works and produces the same result — both syntaxes register the name identically.
|
|
128
|
+
|
|
129
|
+
`from:` spec forms:
|
|
130
|
+
|
|
131
|
+
| Prefix | Resolved by bundler as |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `pkg` or `npm:pkg` | npm package — `npm install pkg`, then `esbuild` re-exports it |
|
|
134
|
+
| `pkg@1.2.3` or `npm:pkg@1.2.3` | npm package with pinned version |
|
|
135
|
+
| `@scope/pkg` or `npm:@scope/pkg` | npm scoped package |
|
|
136
|
+
| `git:user/repo/path` | Raw specifier passed to esbuild; requires resolvable module |
|
|
137
|
+
| `github:user/repo/path` | Same as `git:` |
|
|
138
|
+
| `https://...`, `http://...`, path | Used as-is by esbuild |
|
|
139
|
+
|
|
140
|
+
## Key Reference (use these names)
|
|
141
|
+
|
|
142
|
+
| Key | Means | Applies to | Note
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `width` | canvas width | root |
|
|
145
|
+
| `height` | canvas height | root |
|
|
146
|
+
| `fps` | frame rate | root |
|
|
147
|
+
| `theme` | *removed — use `style` on root* | root |
|
|
148
|
+
| `tts` | CLI template string (e.g. `edge-tts --voice "en-US-GuyNeural" --text "{input}" --write-media "{output}"`) | root |
|
|
149
|
+
| `stt` | CLI template string (e.g. `whisper "{input}" --output_format vtt --output_dir "{output}"`) | root |
|
|
150
|
+
| `layout` | `series\|parallel\|transitionSeries` | root, scene |
|
|
151
|
+
| `transition` | `fade\|slide\|wipe\|flip\|clockWipe` | transitionSeries |
|
|
152
|
+
| `transitionTime` | seconds | transitionSeries |
|
|
153
|
+
| `src` | media path/URL (for image/video/audio/include) | leaves (not component) |
|
|
154
|
+
| `duration` | seconds | leaves |
|
|
155
|
+
| `start` | parallel-only offset | parallel children |
|
|
156
|
+
| `startFrom` | trim from source start | video, audio |
|
|
157
|
+
| `endAt` | trim at source position | video, audio |
|
|
158
|
+
| `volume` | 0–1 | video, audio, rhythm |
|
|
159
|
+
| `foreground` | bool; ducks parent video audio while playing | audio |
|
|
160
|
+
| `spots` | number[] beat timestamps | rhythm |
|
|
161
|
+
| `fit` | `contain\|cover\|fill` | image |
|
|
162
|
+
| `loop` | int >1 | audio |
|
|
163
|
+
| `playbackRate` | number | video |
|
|
164
|
+
| `jsx` | usage JSX expression (`"<ComA value={42} />"`); compiled at runtime with registered imports in scope | component |
|
|
165
|
+
| `effects` | `[name, name(params...)]` e.g. `[fadeIn, bounceIn(1, ease-out, 2)]` | any leaf/container | Apply animations directly — no wrapper node needed |
|
|
166
|
+
| `waypoints` | `[lat,lng,"label";...]` | map |
|
|
167
|
+
| `travelMode` | `DRIVING\|WALKING\|BICYCLING\|TRANSIT` | map |
|
|
168
|
+
| `routeColor` | hex color e.g. `"#FF5733"` | map |
|
|
169
|
+
| `routeWeight` | int (default 4) | map |
|
|
170
|
+
| `zoom` | int (default 10) | map |
|
|
171
|
+
| `center` | `{lat:n,lng:n}` JSON | map |
|
|
172
|
+
| `mapType` | `roadmap\|satellite\|hybrid\|terrain` | map |
|
|
173
|
+
| `routeMarker` | emoji string e.g. `"🚗"` | map |
|
|
174
|
+
| `title` | display title | scene |
|
|
175
|
+
| `instruction` | visual intent / style / any prompt; NOT rendered | any |
|
|
176
|
+
| `script` | narration/dialogue text; TTS source; NOT rendered directly | audio | Only on audio nodes — see Narration section below |
|
|
177
|
+
| `tts` | CLI template string; per-scene TTS override (overrides root `tts`) | root, scene |
|
|
178
|
+
| `metadata` | arbitrary metadata string | root |
|
|
179
|
+
| `stylesheet` | global CSS string; selectors use `.className` on elements | root |
|
|
180
|
+
| `style` | inline CSS applied to the node's container div e.g. `"border-radius:12px"` | any |
|
|
181
|
+
| `visible` | bool default true; `false` hides without removing | any |
|
|
182
|
+
| `isBackground` | bool; loops to fill parent duration; does NOT count toward container duration — use for BGM or looping bg imagery | any |
|
|
183
|
+
| `on` | `on:(when, state)` event spec; fires JS expression at a specific frame, mutating registered component state | any | See [Events](#events) section |
|
|
184
|
+
| `id` | unique string within parent scope | any |
|
|
185
|
+
|
|
186
|
+
## Type Catalog
|
|
187
|
+
|
|
188
|
+
Each type below shows: when to use, required keys, markdown syntax.
|
|
189
|
+
|
|
190
|
+
### `scene` (heading) — organizer (preferred)
|
|
191
|
+
|
|
192
|
+
When: always. `scene` is a container — scenes can nest inside other scenes via deeper headings (`##` → `###` → `####`).
|
|
193
|
+
|
|
194
|
+
Syntax:
|
|
195
|
+
|
|
196
|
+
```md
|
|
197
|
+
## <title>
|
|
198
|
+
layout:<x> [transition:<t> transitionTime:<n>] [instruction:..]
|
|
199
|
+
- <children>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Scene metadata (layout, instruction, transition) goes on the line(s) immediately below the heading, before any child bullets. This keeps the heading clean. `name` comes from the heading text (must be a single token — no spaces). For multi-word titles, use key-value `title:"Long Title"` on the metadata line. `title` optionally follows ` - ` in the heading (e.g. `## Chapter1 - The Beginning` splits to name=`Chapter1`, title=`The Beginning`).
|
|
203
|
+
|
|
204
|
+
For narration, use the `- script "..."` audio alias as a child of the scene (see [Narration / TTS](#narration--tts) below).
|
|
205
|
+
|
|
206
|
+
### `image`
|
|
207
|
+
|
|
208
|
+
When: photos, stills, title cards. Required: `src`, `duration`.
|
|
209
|
+
|
|
210
|
+
`- image src:cover.jpg duration:2 fit:cover`
|
|
211
|
+
|
|
212
|
+
### `video`
|
|
213
|
+
|
|
214
|
+
When: moving footage. Required: `src` + (`duration` or `endAt`).
|
|
215
|
+
|
|
216
|
+
`- video src:clip.mp4 startFrom:1 endAt:4 volume:0.8`
|
|
217
|
+
|
|
218
|
+
### `audio`
|
|
219
|
+
|
|
220
|
+
When: voiceover, BGM, SFX, TTS narration. Required: `src` + (`duration` or `endAt`).
|
|
221
|
+
|
|
222
|
+
`- audio src:bgm.mp3 duration:6 volume:0.4 loop:2`
|
|
223
|
+
|
|
224
|
+
### Narration / TTS
|
|
225
|
+
|
|
226
|
+
Narration text is set via the `script` field on audio nodes. There are three ways to provide it:
|
|
227
|
+
|
|
228
|
+
**1. Inline `script:` attribute on an audio node**
|
|
229
|
+
|
|
230
|
+
```md
|
|
231
|
+
- audio src:bg.mp3 duration:5 script:"Welcome to the course"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**2. Standalone `- script "..."` bullet (alias for audio)**
|
|
235
|
+
|
|
236
|
+
This is a shorthand that creates an audio node with the `script` field. Supports all standard audio keys:
|
|
237
|
+
|
|
238
|
+
```md
|
|
239
|
+
- script "Welcome to the course"
|
|
240
|
+
- script "Voiceover" volume:0.8 start:2 duration:5 foreground:true
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**3. Code fence `~~~script` on an audio node**
|
|
244
|
+
|
|
245
|
+
For longer text, use an indented code fence:
|
|
246
|
+
|
|
247
|
+
```md
|
|
248
|
+
- audio src:bg.mp3 duration:10
|
|
249
|
+
~~~script
|
|
250
|
+
This is a longer narration
|
|
251
|
+
that spans multiple lines.
|
|
252
|
+
~~~
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
All three patterns produce an `audio` node with a `script` field. The pipeline's TTS resolver (`resolveScripts`) picks up these nodes, generates speech audio files, and sets the `src` field to the output path. The `script` field is consumed by the resolver and is not present in the compiled stream tree.
|
|
256
|
+
|
|
257
|
+
### `subtitle` (root-level overlay)
|
|
258
|
+
|
|
259
|
+
Subtitles are configured at the root level as a VTT overlay, not as tree nodes. Set via YAML frontmatter `subtitle:` or `root.subtitle` in JSON.
|
|
260
|
+
|
|
261
|
+
The `type` field selects an animated caption component from `remotion-subtitle`:
|
|
262
|
+
|
|
263
|
+
| Value | Component |
|
|
264
|
+
|---|---|
|
|
265
|
+
| *(omit)* | `Caption` — plain static text |
|
|
266
|
+
| `Bounce` | `BounceCaption` — bouncing entrance |
|
|
267
|
+
| `Fade` | `FadeCaption` — fade in |
|
|
268
|
+
| `Typewriter` | `TypewriterCaption` — typewriter reveal |
|
|
269
|
+
| `Colorful` | `ColorfulCaption` — rainbow text |
|
|
270
|
+
| `Glowing` | `GlowingCaption` — glow effect |
|
|
271
|
+
| `Neon` | `NeonCaption` — neon sign |
|
|
272
|
+
| `Zoom` | `ZoomCaption` — zoom in |
|
|
273
|
+
|
|
274
|
+
> **HTML in cue text**: Each cue's text is rendered via `dangerouslySetInnerHTML`, so you can use HTML tags with inline CSS to style individual words:
|
|
275
|
+
> ```vtt
|
|
276
|
+
> 00:00:01.000 --> 00:00:03.000
|
|
277
|
+
> The <span style="color:#ff6b6b;font-weight:bold">quick</span> brown <span style="font-style:italic">fox</span> jumps over the lazy dog
|
|
278
|
+
> ```
|
|
279
|
+
> The `Typewriter` caption animation correctly respects HTML tag boundaries (character reveal skips over tags, only animates visible text).
|
|
280
|
+
|
|
281
|
+
Each cue is rendered as a separate `<Sequence>` for optimal performance — inactive cues consume zero CPU.
|
|
282
|
+
|
|
283
|
+
See [JSON Descriptive](json-descriptive.md#subtitle-root-level-overlay) for the full field reference.
|
|
284
|
+
|
|
285
|
+
## Effects on Any Node
|
|
286
|
+
|
|
287
|
+
Apply animations directly via the `effects` key on **any** node (leaf or container). No wrapper node needed.
|
|
288
|
+
|
|
289
|
+
```md
|
|
290
|
+
- image src:hero.jpg duration:3 effects:[fadeIn]
|
|
291
|
+
- component duration:2 jsx:"<Title />" effects:[bounceIn]
|
|
292
|
+
- video src:clip.mp4 duration:4 effects:[fadeIn, slideInLeft]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Parameterized syntax
|
|
296
|
+
|
|
297
|
+
Add positional parameters in parentheses — comma-separated.
|
|
298
|
+
|
|
299
|
+
```md
|
|
300
|
+
- image src:card.jpg duration:3 effects:[fadeIn(1.5)]
|
|
301
|
+
- image src:card.jpg duration:3 effects:[fadeIn(1.5, ease-out)]
|
|
302
|
+
- image src:card.jpg duration:3 effects:[fadeIn(1.5, ease-out, 2)]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Order: `(duration, timingFunction, iterationCount)`
|
|
306
|
+
|
|
307
|
+
| Position | Parameter | Example |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| 1st | `duration` (seconds) | `fadeIn(1.5)` |
|
|
310
|
+
| 2nd | `timingFunction` | `fadeIn(1.5, ease-out)` |
|
|
311
|
+
| 3rd | `iterationCount` | `fadeIn(1.5, ease-out, 2)` |
|
|
312
|
+
|
|
313
|
+
All parameters are optional — omit trailing ones.
|
|
314
|
+
|
|
315
|
+
### Multiple effects
|
|
316
|
+
|
|
317
|
+
Effects are applied outermost-first (first in the array is the outermost wrapper):
|
|
318
|
+
|
|
319
|
+
```md
|
|
320
|
+
- image src:hero.jpg duration:3 effects:[fadeIn, bounceIn]
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### `component`
|
|
324
|
+
|
|
325
|
+
When: JSX expression rendered at runtime with registered imports in scope. Required for non-background components: `duration` . The `jsx` value can come from an inline attribute or an indented code fence.
|
|
326
|
+
|
|
327
|
+
Components must be registered via a `` ~~~js imports `` code block. Usage is via `jsx:"<TagName ... />"` on the component node.
|
|
328
|
+
|
|
329
|
+
```md
|
|
330
|
+
~~~js imports
|
|
331
|
+
import { StatCounter } from "stat-counter"
|
|
332
|
+
import { Logo } from "github:myorg/design#Logo"
|
|
333
|
+
~~~
|
|
334
|
+
|
|
335
|
+
# JSX usage (references registered components as tags)
|
|
336
|
+
- component duration:1 jsx:"<StatCounter value={42} />"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Code fence properties
|
|
340
|
+
|
|
341
|
+
Properties that are too long for a single line can be provided via an indented code fence under the bullet item. The fence language (`~~~<lang> <propName>`) specifies which property to set:
|
|
342
|
+
|
|
343
|
+
```md
|
|
344
|
+
- component duration:4 isBackground:true
|
|
345
|
+
~~~jsx jsx
|
|
346
|
+
<div style={{color:'#fff'}}>Hello</div>
|
|
347
|
+
~~~
|
|
348
|
+
|
|
349
|
+
- video start:5 volume:0
|
|
350
|
+
~~~prompt prompt
|
|
351
|
+
animation of a robot learning to walk, cinematic lighting
|
|
352
|
+
~~~
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The fence syntax is `~~~<lang> <propName>`. If `propName` is omitted, it defaults to `lang`. Common patterns:
|
|
356
|
+
|
|
357
|
+
| Fence | Sets property | Use case |
|
|
358
|
+
|---|---|---|
|
|
359
|
+
| `~~~jsx jsx` or `~~~jsx` | `jsx` | Component JSX expression |
|
|
360
|
+
| `~~~prompt prompt` | `prompt` | TTI/TTV generation prompt |
|
|
361
|
+
| `~~~script script` or `~~~script` | `script` | Narration text on audio nodes |
|
|
362
|
+
|
|
363
|
+
### `rhythm`
|
|
364
|
+
|
|
365
|
+
When: beat-synced audio (music drops, music-reactive reveals). Required: `src`, `spots`, `children`.
|
|
366
|
+
|
|
367
|
+
Each child is assigned to a beat slot: child[i] starts at `spots[i]`, ends at `spots[i+1]` (last child ends at the final beat). No `duration` field — it is derived from `spots` and children count.
|
|
368
|
+
|
|
369
|
+
```md
|
|
370
|
+
- rhythm src:beat.mp3 spots:[0.5,1.2,1.9]
|
|
371
|
+
- image src:flash1.jpg
|
|
372
|
+
- image src:flash2.jpg
|
|
373
|
+
- image src:flash3.jpg
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### `effect`
|
|
377
|
+
|
|
378
|
+
When: CSS keyframe animation wrapper. Required: `children`. `duration` falls back to children max.
|
|
379
|
+
|
|
380
|
+
`- effect animation:fadeIn duration:2` then indented children.
|
|
381
|
+
|
|
382
|
+
Built-in `animation` values:
|
|
383
|
+
`fadeIn fadeOut fadeInDown fadeInUp fadeInLeft fadeInRight fadeOutDown fadeOutUp fadeOutLeft fadeOutRight slideInDown slideInUp slideInLeft slideInRight slideOutDown slideOutUp slideOutLeft slideOutRight zoomIn zoomOut zoomInDown zoomInUp zoomInLeft zoomInRight bounce bounceIn rotateIn rotateOut rotateInDownLeft rotateInDownRight rotateInUpLeft rotateInUpRight flipInX flipInY pulse flash heartBeat rubberBand shakeX shakeY swing tada wobble jello rollIn rollOut jackInTheBox lightSpeedIn lightSpeedOut`
|
|
384
|
+
|
|
385
|
+
### `map`
|
|
386
|
+
|
|
387
|
+
When: animated route. Required: `duration`, `waypoints`.
|
|
388
|
+
|
|
389
|
+
`- map duration:3 travelMode:DRIVING waypoints:[37.77,-122.41,"SF";34.05,-118.24,"LA"]`
|
|
390
|
+
|
|
391
|
+
### `include`
|
|
392
|
+
|
|
393
|
+
When: embed an external markdown file as a sub-video. The sub-video is
|
|
394
|
+
fully resolved by the pipeline (TTS, media, includes, component imports).
|
|
395
|
+
|
|
396
|
+
The sub-video can have its own ` ```js imports ``` ` block — components
|
|
397
|
+
are bundled independently and available in an isolated `ComposeContext`
|
|
398
|
+
at render time (nested context, "sub-video wins" priority).
|
|
399
|
+
|
|
400
|
+
`- include src:./sub-video.md`
|
|
401
|
+
|
|
402
|
+
> **Note:** `duration` is optional — the pipeline compiles the sub-video
|
|
403
|
+
> to determine its real duration. The compiled JSON is cached at
|
|
404
|
+
> `.markcut/generated/includes/<hash>.json` so repeated runs are fast.
|
|
405
|
+
>
|
|
406
|
+
> Component imports from the sub-video are bundled to
|
|
407
|
+
> `.markcut/<sub-basename>/components/<hash>.js`.
|
|
408
|
+
|
|
409
|
+
## Events
|
|
410
|
+
|
|
411
|
+
Fire a JavaScript expression at a specific frame to mutate a registered component's state. Useful for syncing UI state with narration beats.
|
|
412
|
+
|
|
413
|
+
### Registering a component for events
|
|
414
|
+
|
|
415
|
+
Add an `id` attribute to a `component` node to register it in the global event context:
|
|
416
|
+
|
|
417
|
+
```md
|
|
418
|
+
- component id:slide1 duration:4 jsx:"<Slide current={current}>{source}</Slide>"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The `id` becomes the variable name available in event expressions.
|
|
422
|
+
|
|
423
|
+
### Firing an event
|
|
424
|
+
|
|
425
|
+
Use `on:(when, state)` on any node (audio, video, image, component, scene, etc.):
|
|
426
|
+
|
|
427
|
+
```md
|
|
428
|
+
- script "Narration 1" on:(start, slide1.current=0)
|
|
429
|
+
- script "Narration 2" on:(start, slide1.current++)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
The `when` argument selects the target frame, and `state` is a JavaScript expression evaluated with all registered component proxies in scope.
|
|
433
|
+
|
|
434
|
+
### Supported `when` values
|
|
435
|
+
|
|
436
|
+
| Value | Fires at |
|
|
437
|
+
|---|---|
|
|
438
|
+
| `start` | Frame 0 (beginning of the node's timeline) |
|
|
439
|
+
| `end` | Last frame (end of the node's duration) |
|
|
440
|
+
| `50%` | 50% through the node's duration |
|
|
441
|
+
| `2.5s` | 2.5 seconds into the node (multiplied by root `fps`) |
|
|
442
|
+
|
|
443
|
+
Any percentage (`0%`–`100%`) or seconds value (`0s`, `1.5s`, `10s`) works.
|
|
444
|
+
|
|
445
|
+
### `state` expression
|
|
446
|
+
|
|
447
|
+
Any valid JavaScript expression — assign values, increment counters, toggle booleans:
|
|
448
|
+
|
|
449
|
+
```md
|
|
450
|
+
- script "Narration" on:(start, slide1.current=0)
|
|
451
|
+
- script "Beat2" on:(start, slide1.current++)
|
|
452
|
+
- script "Done" on:(end, slide1.visible=false)
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
The expression is evaluated with all registered component IDs as scope variables. Each component variable is a Proxy whose property setter triggers a React re-render.
|
|
456
|
+
|
|
457
|
+
### JSON form
|
|
458
|
+
|
|
459
|
+
In the compiled stream tree, events are represented as an `on` field on any node:
|
|
460
|
+
|
|
461
|
+
```json
|
|
462
|
+
{
|
|
463
|
+
"type": "audio",
|
|
464
|
+
"src": "narration.mp3",
|
|
465
|
+
"duration": 3,
|
|
466
|
+
"on": { "when": "start", "state": "slide1.current=0" }
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Variants (Language / Platform / Any Override)
|
|
471
|
+
|
|
472
|
+
Produce different versions of the same video from a single markdown file
|
|
473
|
+
— for example, Chinese and English narration, portrait and landscape layouts,
|
|
474
|
+
or TikTok and YouTube versions.
|
|
475
|
+
|
|
476
|
+
### How it works
|
|
477
|
+
|
|
478
|
+
The document has a **base section** (`# video`) that defines the default video.
|
|
479
|
+
Additional **variant sections** (`# zh`, `# portrait`, `# tiktok`) contain
|
|
480
|
+
root-level config overrides. Leaf nodes in the base section carry
|
|
481
|
+
variant-prefixed keys to override specific fields per variant.
|
|
482
|
+
|
|
483
|
+
### 1. Define a variant section
|
|
484
|
+
|
|
485
|
+
A variant is a top-level `# <name>` heading. It can override root config
|
|
486
|
+
keys (like `tts`, `width`, `height`):
|
|
487
|
+
|
|
488
|
+
```markdown
|
|
489
|
+
# video
|
|
490
|
+
layout:series width:1920 height:1080 fps:30
|
|
491
|
+
tts:"edge-tts --voice 'en-US-GuyNeural' --text '{input}' --write-media '{output}'"
|
|
492
|
+
|
|
493
|
+
## Hook
|
|
494
|
+
- image duration:3 prompt:"..."
|
|
495
|
+
|
|
496
|
+
## Title
|
|
497
|
+
- script "Welcome to the course"
|
|
498
|
+
- component jsx:"<Slide>{source}</Slide>"
|
|
499
|
+
~~~md source
|
|
500
|
+
# Hello
|
|
501
|
+
~~~
|
|
502
|
+
|
|
503
|
+
# zh
|
|
504
|
+
tts:"edge-tts --voice 'zh-CN-YunxiNeural' --text '{input}' --write-media '{output}'"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
The `# zh` section only needs the keys that differ from the base —
|
|
508
|
+
here the TTS voice is switched to Chinese. The video's width, height, fps,
|
|
509
|
+
scenes and all content remain inherited from `# video`.
|
|
510
|
+
|
|
511
|
+
### 2. Override leaf values
|
|
512
|
+
|
|
513
|
+
Always write variant overrides on a **new indented line** under the bullet,
|
|
514
|
+
never inline on the same line. This keeps the base declaration clean and
|
|
515
|
+
makes variants easy to scan:
|
|
516
|
+
|
|
517
|
+
```markdown
|
|
518
|
+
# video
|
|
519
|
+
- script "Welcome to the course"
|
|
520
|
+
zh:"欢迎来到本课程"
|
|
521
|
+
- component jsx:"<Slide>{source}</Slide>"
|
|
522
|
+
~~~md source
|
|
523
|
+
## English title
|
|
524
|
+
~~~
|
|
525
|
+
~~~md zh-source
|
|
526
|
+
## 中文标题
|
|
527
|
+
~~~
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Two mechanisms for overriding content per-variant on individual nodes:
|
|
531
|
+
|
|
532
|
+
**Variant-prefixed keys** (`zh-<key>`) — replace a specific field.
|
|
533
|
+
The prefix (`zh-`) matches the variant section name (`# zh`):
|
|
534
|
+
|
|
535
|
+
```markdown
|
|
536
|
+
- component jsx:"<Slide>{source}</Slide>"
|
|
537
|
+
~~~md source ← base value for key "source"
|
|
538
|
+
## English title
|
|
539
|
+
~~~
|
|
540
|
+
~~~md zh-source ← overrides "source" when --variant zh is used
|
|
541
|
+
## 中文标题
|
|
542
|
+
~~~
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Here `zh-source` replaces `source` when the `zh` variant is active.
|
|
546
|
+
Code-fence-backed props (`~~~md source`, `~~~js jsx`) use variant-prefixed
|
|
547
|
+
keys the same way as inline attributes: the code fence's prop name gets
|
|
548
|
+
the variant prefix.
|
|
549
|
+
|
|
550
|
+
**Bare variant keys** (`zh`) — replaces the node's "primary content" field
|
|
551
|
+
(meaning depends on node type):
|
|
552
|
+
|
|
553
|
+
| Node type | Primary key | `zh` replaces |
|
|
554
|
+
|---|---|---|
|
|
555
|
+
| `audio` / `- script` | `script` | narration text |
|
|
556
|
+
| `component` | `jsx` | JSX expression |
|
|
557
|
+
| `image` | `src` | image path |
|
|
558
|
+
| `video` | `src` | video path |
|
|
559
|
+
|
|
560
|
+
```markdown
|
|
561
|
+
- script "Welcome to the course"
|
|
562
|
+
zh:"欢迎来到本课程"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Here `zh` replaces `script` on the audio node, switching the narration text
|
|
566
|
+
to Chinese when the variant is active.
|
|
567
|
+
|
|
568
|
+
### 3. Run with a variant
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
# Preview Chinese version
|
|
572
|
+
npx markcut preview courseware.md --variant zh
|
|
573
|
+
|
|
574
|
+
# Preview with multiple variants (e.g. Chinese + TikTok portrait)
|
|
575
|
+
npx markcut preview courseware.md --variant zh --variant tiktok
|
|
576
|
+
|
|
577
|
+
# Render Chinese version
|
|
578
|
+
npx markcut render courseware.md --variant zh --output zh-video.mp4
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Combined variants
|
|
582
|
+
|
|
583
|
+
Multiple `--variant` flags are applied in order. Each subsequent variant
|
|
584
|
+
can override values set by a previous one. The directory is named with
|
|
585
|
+
all variants joined by `-`:
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
npx markcut preview courseware.md --variant zh --variant tiktok
|
|
589
|
+
# Uses .markcut/courseware/zh-tiktok/ for variant artifacts
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Root config override priority
|
|
593
|
+
|
|
594
|
+
When a variant section provides a root-level key (e.g., `tts`), it merges
|
|
595
|
+
into the base config. Scene-level `tts` overrides root-level `tts`,
|
|
596
|
+
and the `ttsCli` option (if provided) overrides both.
|
|
597
|
+
|
|
598
|
+
### How overrides are resolved
|
|
599
|
+
|
|
600
|
+
1. The parser extracts `# video` as the base root and each `# <name>` as a
|
|
601
|
+
variant config map.
|
|
602
|
+
2. When `--variant zh` is used, the `# zh` section's root keys are merged
|
|
603
|
+
into the base root (e.g., `tts` is replaced).
|
|
604
|
+
3. `resolveVariantOverrides` walks the entire tree: for each node,
|
|
605
|
+
variant-prefixed keys (like `zh-src`) replace their unprefixed
|
|
606
|
+
counterparts. Bare variant keys (like `zh`) replace the node's
|
|
607
|
+
primary content key (see table above).
|
|
608
|
+
4. All `<variant>-*` keys and bare variant keys are stripped from the
|
|
609
|
+
output — the compiled tree contains only the resolved values.
|
|
610
|
+
5. The pipeline then runs TTS/STT/media generation as usual with the
|
|
611
|
+
overridden config and content.
|
|
612
|
+
|
|
613
|
+
### Directory layout for variant artifacts
|
|
614
|
+
|
|
615
|
+
```
|
|
616
|
+
.markcut/
|
|
617
|
+
├── generated/ ← shared, content-addressed
|
|
618
|
+
│ ├── tts/ ← TTS audio (same script → same file)
|
|
619
|
+
│ ├── media/ ← TTI/TTV images/videos
|
|
620
|
+
│ └── includes/ ← compiled sub-video JSON
|
|
621
|
+
├── courseware/ ← per-source-file
|
|
622
|
+
│ ├── components/ ← component bundles (shared)
|
|
623
|
+
│ ├── compiled.json ← default (en) compiled output
|
|
624
|
+
│ └── zh/
|
|
625
|
+
│ ├── compiled.json ← zh variant compiled output
|
|
626
|
+
│ └── subtitles.vtt ← zh variant subtitles
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
TTS audio files are content-addressed (hash of `script + CLI`), so
|
|
630
|
+
identical scripts across variants produce the same file. The merged
|
|
631
|
+
`subtitles.vtt` is per-variant because script content and timing offsets
|
|
632
|
+
differ.
|
|
633
|
+
|
|
634
|
+
## Timing Rules
|
|
635
|
+
|
|
636
|
+
1. `duration` authoritative for non-trimmed leaves.
|
|
637
|
+
2. `video`/`audio` with `startFrom`+`endAt`: clip = `endAt − startFrom`.
|
|
638
|
+
3. `start` only in `parallel`.
|
|
639
|
+
4. `transitionTime` subtracted between `transitionSeries` items.
|
|
640
|
+
5. Containers derive duration from children (parallel=max, series=sum, ts=sum−overlap).
|
|
641
|
+
|
|
642
|
+
## Generation Workflow
|
|
643
|
+
|
|
644
|
+
1. Root: `# video` + `width:<n> height:<n> fps:<n> layout:<mode>` on the next line.
|
|
645
|
+
2. Frontmatter (optional): `---` block for root attrs + tts/stt pipeline config + `stylesheet`.
|
|
646
|
+
3. Component registrations: `` ~~~js imports `` block for external components.
|
|
647
|
+
4. Scenes via `##` with `layout:` metadata on the line below.
|
|
648
|
+
5. Leaves as `- type key:value ...` bullets indented under scenes.
|
|
649
|
+
6. Long values (JSX, prompts, scripts) use indented `~~~<lang> <propName>` code fences.
|
|
650
|
+
7. Verify each leaf has resolvable `duration`.
|
|
651
|
+
|
|
652
|
+
## Tween Animation
|
|
653
|
+
|
|
654
|
+
Animate numeric props over time by using `tween(from?, to, easing?)` expressions inside JSX usage expressions. Tweens resolve at render time using Remotion's `interpolate()`.
|
|
655
|
+
|
|
656
|
+
### Usage
|
|
657
|
+
|
|
658
|
+
Call `tween(from, to)` directly as a function inside the usage expression — it's available in the compiled scope:
|
|
659
|
+
|
|
660
|
+
```md
|
|
661
|
+
- component duration:4 jsx:"<BarChart data={[{name:'A',value:tween(0,80)},{name:'B',value:tween(0,60)}]}/>"
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
You can also use `tween()` in inline SVG:
|
|
665
|
+
|
|
666
|
+
```md
|
|
667
|
+
- component duration:4 jsx:"<svg viewBox='0 0 400 300'><rect y={260-tween(0,200)} width={80} height={tween(0,200)} fill='#E38627' /></svg>"
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Supported syntax
|
|
671
|
+
|
|
672
|
+
```
|
|
673
|
+
tween(to) — 0 → to, linear
|
|
674
|
+
tween(from, to) — from → to, linear
|
|
675
|
+
tween(from, to, easeOut) — with easing
|
|
676
|
+
tween(from, to, spring) — spring animation
|
|
677
|
+
tween(#000, #FFF) — color interpolation
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Important notes
|
|
681
|
+
|
|
682
|
+
- Tween values only work on `component` nodes.
|
|
683
|
+
- Frame range is derived from the component's `duration` (minus `start` offset).
|
|
684
|
+
- `tween()` uses Remotion's `interpolate()` with the component's action duration.
|
|
685
|
+
- At frame 0, `tween(from, to)` returns `from`.
|
|
686
|
+
|
|
687
|
+
## Self-Check
|
|
688
|
+
|
|
689
|
+
- [ ] Root has `width`, `height`, `fps`, `layout`.
|
|
690
|
+
- [ ] Every scene has ≥1 child or `script`.
|
|
691
|
+
- [ ] All values use explicit `key:value` syntax (no bare tokens).
|
|
692
|
+
- [ ] `start` only used inside `parallel` containers.
|
|
693
|
+
- [ ] No `src` on component nodes (use `jsx:` instead).
|
|
694
|
+
- [ ] Component registrations use `` ~~~js imports `` block — the ONLY supported method.
|
|
695
|
+
- [ ] Every component `jsx:` references a name registered in `` ~~~js imports ``.
|
|
696
|
+
- [ ] Every `jsx:` on a component node is a usage expression (JSX tag), not a definition.
|
|
697
|
+
- [ ] Inline component definitions go inside `` ~~~js imports `` as `export function Name(...) { ... }`.
|
|
698
|
+
- [ ] Scene names are single tokens (no spaces) — use `title:"..."` for multi-word titles.
|
|
699
|
+
- [ ] Code fence properties are indented under their parent bullet.
|
|
700
|
+
- [ ] Event targets (`id:name` on component) match the `id` used in `on:(when, id.prop=value)` expressions.
|
|
701
|
+
|
|
702
|
+
## Validation with CLI
|
|
703
|
+
|
|
704
|
+
Use `markcut verify` to parse and validate a descriptive markdown file without rendering:
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
markcut verify courseware.md
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
## Example
|
|
711
|
+
|
|
712
|
+
```md
|
|
713
|
+
---
|
|
714
|
+
width: 1080
|
|
715
|
+
height: 1920
|
|
716
|
+
fps: 30
|
|
717
|
+
---
|
|
718
|
+
# video
|
|
719
|
+
layout:series
|
|
720
|
+
~~~js imports
|
|
721
|
+
export { StatCounter } from "stat-counter"
|
|
722
|
+
export { Logo } from "github:myorg/design-system#Logo"
|
|
723
|
+
|
|
724
|
+
export function Greeting({ name }) {
|
|
725
|
+
return <div style={{color: '#fff', fontSize: 28, textAlign: 'center'}}>Hello {name}!</div>
|
|
726
|
+
}
|
|
727
|
+
~~~
|
|
728
|
+
|
|
729
|
+
## Hook
|
|
730
|
+
layout:parallel
|
|
731
|
+
- script "Set location and emotional tone"
|
|
732
|
+
- image src:cover.jpg duration:2
|
|
733
|
+
|
|
734
|
+
## Journey
|
|
735
|
+
layout:transitionSeries transition:fade transitionTime:0.4
|
|
736
|
+
- script "Move through moments"
|
|
737
|
+
- video src:clips/arrival.mp4 startFrom:0 endAt:3.5
|
|
738
|
+
- video src:clips/fire.mp4 startFrom:1 endAt:4
|
|
739
|
+
|
|
740
|
+
## Stat
|
|
741
|
+
layout:parallel
|
|
742
|
+
- component duration:2 jsx:"<StatCounter value={42} label='S-mores' />"
|
|
743
|
+
|
|
744
|
+
## Logo
|
|
745
|
+
layout:parallel
|
|
746
|
+
- component duration:1 jsx:"<Logo />"
|
|
747
|
+
|
|
748
|
+
## Route
|
|
749
|
+
layout:parallel
|
|
750
|
+
- map duration:3 travelMode:DRIVING waypoints:[37.7749,-122.4194,"SF";34.0522,-118.2437,"LA"]
|
|
751
|
+
```
|