videowright 0.1.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/README.md +91 -0
- package/dist/cli/argv.d.ts +28 -0
- package/dist/cli/argv.d.ts.map +1 -0
- package/dist/cli/argv.js +115 -0
- package/dist/cli/argv.js.map +1 -0
- package/dist/cli/bin.d.ts +7 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +10 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/dev.d.ts +19 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +104 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/discover.d.ts +29 -0
- package/dist/cli/discover.d.ts.map +1 -0
- package/dist/cli/discover.js +104 -0
- package/dist/cli/discover.js.map +1 -0
- package/dist/cli/discover_project.d.ts +29 -0
- package/dist/cli/discover_project.d.ts.map +1 -0
- package/dist/cli/discover_project.js +108 -0
- package/dist/cli/discover_project.js.map +1 -0
- package/dist/cli/errors.d.ts +10 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +13 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/ffmpeg.d.ts +57 -0
- package/dist/cli/ffmpeg.d.ts.map +1 -0
- package/dist/cli/ffmpeg.js +122 -0
- package/dist/cli/ffmpeg.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +152 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/playwright_check.d.ts +44 -0
- package/dist/cli/playwright_check.d.ts.map +1 -0
- package/dist/cli/playwright_check.js +20 -0
- package/dist/cli/playwright_check.js.map +1 -0
- package/dist/cli/prompt.d.ts +13 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +47 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/render.d.ts +60 -0
- package/dist/cli/render.d.ts.map +1 -0
- package/dist/cli/render.js +471 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/script_cmd.d.ts +26 -0
- package/dist/cli/script_cmd.d.ts.map +1 -0
- package/dist/cli/script_cmd.js +88 -0
- package/dist/cli/script_cmd.js.map +1 -0
- package/dist/cli/time_shim.d.ts +44 -0
- package/dist/cli/time_shim.d.ts.map +1 -0
- package/dist/cli/time_shim.js +390 -0
- package/dist/cli/time_shim.js.map +1 -0
- package/dist/cli/ts_loader.d.ts +28 -0
- package/dist/cli/ts_loader.d.ts.map +1 -0
- package/dist/cli/ts_loader.js +95 -0
- package/dist/cli/ts_loader.js.map +1 -0
- package/dist/cli/vite_helpers.d.ts +62 -0
- package/dist/cli/vite_helpers.d.ts.map +1 -0
- package/dist/cli/vite_helpers.js +273 -0
- package/dist/cli/vite_helpers.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/player/hash_router.d.ts +23 -0
- package/dist/player/hash_router.d.ts.map +1 -0
- package/dist/player/hash_router.js +49 -0
- package/dist/player/hash_router.js.map +1 -0
- package/dist/player/hud.d.ts +33 -0
- package/dist/player/hud.d.ts.map +1 -0
- package/dist/player/hud.js +357 -0
- package/dist/player/hud.js.map +1 -0
- package/dist/player/index.d.ts +123 -0
- package/dist/player/index.d.ts.map +1 -0
- package/dist/player/index.js +848 -0
- package/dist/player/index.js.map +1 -0
- package/dist/player/input.d.ts +14 -0
- package/dist/player/input.d.ts.map +1 -0
- package/dist/player/input.js +90 -0
- package/dist/player/input.js.map +1 -0
- package/dist/player/slot.d.ts +22 -0
- package/dist/player/slot.d.ts.map +1 -0
- package/dist/player/slot.js +43 -0
- package/dist/player/slot.js.map +1 -0
- package/dist/player/transitions/cut.d.ts +7 -0
- package/dist/player/transitions/cut.d.ts.map +1 -0
- package/dist/player/transitions/cut.js +9 -0
- package/dist/player/transitions/cut.js.map +1 -0
- package/dist/player/transitions/fade.d.ts +7 -0
- package/dist/player/transitions/fade.d.ts.map +1 -0
- package/dist/player/transitions/fade.js +18 -0
- package/dist/player/transitions/fade.js.map +1 -0
- package/dist/player/transitions/index.d.ts +4 -0
- package/dist/player/transitions/index.d.ts.map +1 -0
- package/dist/player/transitions/index.js +4 -0
- package/dist/player/transitions/index.js.map +1 -0
- package/dist/player/transitions/slide.d.ts +6 -0
- package/dist/player/transitions/slide.d.ts.map +1 -0
- package/dist/player/transitions/slide.js +35 -0
- package/dist/player/transitions/slide.js.map +1 -0
- package/dist/script/index.d.ts +2 -0
- package/dist/script/index.d.ts.map +1 -0
- package/dist/script/index.js +2 -0
- package/dist/script/index.js.map +1 -0
- package/dist/script/script.d.ts +10 -0
- package/dist/script/script.d.ts.map +1 -0
- package/dist/script/script.js +41 -0
- package/dist/script/script.js.map +1 -0
- package/dist/segment/SegmentRunner.d.ts +52 -0
- package/dist/segment/SegmentRunner.d.ts.map +1 -0
- package/dist/segment/SegmentRunner.js +187 -0
- package/dist/segment/SegmentRunner.js.map +1 -0
- package/dist/segment/defineConfig.d.ts +6 -0
- package/dist/segment/defineConfig.d.ts.map +1 -0
- package/dist/segment/defineConfig.js +7 -0
- package/dist/segment/defineConfig.js.map +1 -0
- package/dist/segment/defineSegment.d.ts +7 -0
- package/dist/segment/defineSegment.d.ts.map +1 -0
- package/dist/segment/defineSegment.js +25 -0
- package/dist/segment/defineSegment.js.map +1 -0
- package/dist/segment/index.d.ts +5 -0
- package/dist/segment/index.d.ts.map +1 -0
- package/dist/segment/index.js +4 -0
- package/dist/segment/index.js.map +1 -0
- package/dist/timeline/index.d.ts +73 -0
- package/dist/timeline/index.d.ts.map +1 -0
- package/dist/timeline/index.js +142 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/timeline/loadAudioTrack.d.ts +18 -0
- package/dist/timeline/loadAudioTrack.d.ts.map +1 -0
- package/dist/timeline/loadAudioTrack.js +44 -0
- package/dist/timeline/loadAudioTrack.js.map +1 -0
- package/dist/timeline/loadVoiceover.d.ts +18 -0
- package/dist/timeline/loadVoiceover.d.ts.map +1 -0
- package/dist/timeline/loadVoiceover.js +38 -0
- package/dist/timeline/loadVoiceover.js.map +1 -0
- package/dist/timeline/resolveTiming.d.ts +28 -0
- package/dist/timeline/resolveTiming.d.ts.map +1 -0
- package/dist/timeline/resolveTiming.js +63 -0
- package/dist/timeline/resolveTiming.js.map +1 -0
- package/dist/timeline/validateTiming.d.ts +29 -0
- package/dist/timeline/validateTiming.d.ts.map +1 -0
- package/dist/timeline/validateTiming.js +62 -0
- package/dist/timeline/validateTiming.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
- package/skill/SKILL.md +64 -0
- package/skill/assets/hello_world/PLAN.md +31 -0
- package/skill/assets/hello_world/README.md +27 -0
- package/skill/assets/hello_world/audio/audio_plan.md +14 -0
- package/skill/assets/hello_world/segments/hello_intro.ts +69 -0
- package/skill/assets/hello_world/segments/hello_outro.ts +71 -0
- package/skill/assets/hello_world/timeline.ts +15 -0
- package/skill/assets/hello_world/voiceover_script/script.md +10 -0
- package/skill/assets/install/package.json +10 -0
- package/skill/assets/install/tsconfig.json +23 -0
- package/skill/assets/styles/editorial-mono/STYLE.md +124 -0
- package/skill/assets/styles/editorial-mono/brand.md +85 -0
- package/skill/assets/styles/editorial-mono/reference/animations.jsx +752 -0
- package/skill/assets/styles/editorial-mono/reference/scenes.html +563 -0
- package/skill/assets/styles/editorial-mono/sample/bullet.ts +101 -0
- package/skill/assets/styles/editorial-mono/sample/content.ts +104 -0
- package/skill/assets/styles/editorial-mono/sample/cta.ts +113 -0
- package/skill/assets/styles/editorial-mono/sample/feature.ts +111 -0
- package/skill/assets/styles/editorial-mono/sample/grid.ts +97 -0
- package/skill/assets/styles/editorial-mono/sample/kinetic.ts +96 -0
- package/skill/assets/styles/editorial-mono/sample/section.ts +101 -0
- package/skill/assets/styles/editorial-mono/sample/stat.ts +128 -0
- package/skill/assets/styles/editorial-mono/sample/title.ts +97 -0
- package/skill/assets/styles/editorial-mono/sample/ui-showcase.ts +159 -0
- package/skill/assets/styles/editorial-mono/tokens.css +44 -0
- package/skill/assets/styles/iso-diagram/STYLE.md +109 -0
- package/skill/assets/styles/iso-diagram/brand.md +32 -0
- package/skill/assets/styles/iso-diagram/reference/animations.jsx +673 -0
- package/skill/assets/styles/iso-diagram/reference/scenes.html +427 -0
- package/skill/assets/styles/iso-diagram/sample/bullet.ts +144 -0
- package/skill/assets/styles/iso-diagram/sample/content.ts +192 -0
- package/skill/assets/styles/iso-diagram/sample/cta.ts +162 -0
- package/skill/assets/styles/iso-diagram/sample/feature.ts +205 -0
- package/skill/assets/styles/iso-diagram/sample/grid.ts +181 -0
- package/skill/assets/styles/iso-diagram/sample/kinetic.ts +102 -0
- package/skill/assets/styles/iso-diagram/sample/section.ts +149 -0
- package/skill/assets/styles/iso-diagram/sample/stat.ts +164 -0
- package/skill/assets/styles/iso-diagram/sample/title.ts +173 -0
- package/skill/assets/styles/iso-diagram/sample/ui-showcase.ts +162 -0
- package/skill/assets/styles/iso-diagram/tokens.css +40 -0
- package/skill/assets/styles/motion-engineering/STYLE.md +106 -0
- package/skill/assets/styles/motion-engineering/brand.md +29 -0
- package/skill/assets/styles/motion-engineering/reference/animations.jsx +673 -0
- package/skill/assets/styles/motion-engineering/reference/scenes.html +513 -0
- package/skill/assets/styles/motion-engineering/sample/bullet.ts +176 -0
- package/skill/assets/styles/motion-engineering/sample/content.ts +228 -0
- package/skill/assets/styles/motion-engineering/sample/cta.ts +209 -0
- package/skill/assets/styles/motion-engineering/sample/feature.ts +299 -0
- package/skill/assets/styles/motion-engineering/sample/grid.ts +190 -0
- package/skill/assets/styles/motion-engineering/sample/kinetic.ts +159 -0
- package/skill/assets/styles/motion-engineering/sample/section.ts +196 -0
- package/skill/assets/styles/motion-engineering/sample/stat.ts +230 -0
- package/skill/assets/styles/motion-engineering/sample/title.ts +219 -0
- package/skill/assets/styles/motion-engineering/sample/ui-showcase.ts +267 -0
- package/skill/assets/styles/motion-engineering/tokens.css +40 -0
- package/skill/assets/styles/neon-terminal/STYLE.md +105 -0
- package/skill/assets/styles/neon-terminal/brand.md +27 -0
- package/skill/assets/styles/neon-terminal/reference/animations.jsx +673 -0
- package/skill/assets/styles/neon-terminal/reference/scenes.html +387 -0
- package/skill/assets/styles/neon-terminal/sample/bullet.ts +113 -0
- package/skill/assets/styles/neon-terminal/sample/content.ts +117 -0
- package/skill/assets/styles/neon-terminal/sample/cta.ts +131 -0
- package/skill/assets/styles/neon-terminal/sample/feature.ts +112 -0
- package/skill/assets/styles/neon-terminal/sample/grid.ts +128 -0
- package/skill/assets/styles/neon-terminal/sample/kinetic.ts +105 -0
- package/skill/assets/styles/neon-terminal/sample/section.ts +96 -0
- package/skill/assets/styles/neon-terminal/sample/stat.ts +123 -0
- package/skill/assets/styles/neon-terminal/sample/title.ts +122 -0
- package/skill/assets/styles/neon-terminal/sample/ui-showcase.ts +127 -0
- package/skill/assets/styles/neon-terminal/tokens.css +39 -0
- package/skill/assets/styles/risograph/STYLE.md +110 -0
- package/skill/assets/styles/risograph/brand.md +26 -0
- package/skill/assets/styles/risograph/reference/animations.jsx +673 -0
- package/skill/assets/styles/risograph/reference/scenes.html +403 -0
- package/skill/assets/styles/risograph/sample/bullet.ts +124 -0
- package/skill/assets/styles/risograph/sample/content.ts +135 -0
- package/skill/assets/styles/risograph/sample/cta.ts +149 -0
- package/skill/assets/styles/risograph/sample/feature.ts +152 -0
- package/skill/assets/styles/risograph/sample/grid.ts +123 -0
- package/skill/assets/styles/risograph/sample/kinetic.ts +125 -0
- package/skill/assets/styles/risograph/sample/section.ts +130 -0
- package/skill/assets/styles/risograph/sample/stat.ts +145 -0
- package/skill/assets/styles/risograph/sample/title.ts +132 -0
- package/skill/assets/styles/risograph/sample/ui-showcase.ts +147 -0
- package/skill/assets/styles/risograph/tokens.css +39 -0
- package/skill/assets/styles/swiss-console/STYLE.md +107 -0
- package/skill/assets/styles/swiss-console/brand.md +37 -0
- package/skill/assets/styles/swiss-console/reference/animations.jsx +673 -0
- package/skill/assets/styles/swiss-console/reference/scenes.html +420 -0
- package/skill/assets/styles/swiss-console/sample/bullet.ts +122 -0
- package/skill/assets/styles/swiss-console/sample/content.ts +137 -0
- package/skill/assets/styles/swiss-console/sample/cta.ts +109 -0
- package/skill/assets/styles/swiss-console/sample/feature.ts +163 -0
- package/skill/assets/styles/swiss-console/sample/grid.ts +145 -0
- package/skill/assets/styles/swiss-console/sample/kinetic.ts +117 -0
- package/skill/assets/styles/swiss-console/sample/section.ts +127 -0
- package/skill/assets/styles/swiss-console/sample/stat.ts +148 -0
- package/skill/assets/styles/swiss-console/sample/title.ts +148 -0
- package/skill/assets/styles/swiss-console/sample/ui-showcase.ts +198 -0
- package/skill/assets/styles/swiss-console/tokens.css +39 -0
- package/skill/install/INSTALL.md +400 -0
- package/skill/references/audio/audio_plan.md +199 -0
- package/skill/references/audio/build.md +208 -0
- package/skill/references/audio/cue_template.md +219 -0
- package/skill/references/audio/ffmpeg_cookbook.md +267 -0
- package/skill/references/audio/music/music.md +171 -0
- package/skill/references/audio/music/providers/elevenlabs.md +170 -0
- package/skill/references/audio/music/providers/manual.md +140 -0
- package/skill/references/audio/music/providers/openverse.md +265 -0
- package/skill/references/audio/sfx/providers/elevenlabs.md +152 -0
- package/skill/references/audio/sfx/providers/manual.md +117 -0
- package/skill/references/audio/sfx/providers/openverse.md +243 -0
- package/skill/references/audio/sfx/sfx.md +149 -0
- package/skill/references/audio/styles.md +102 -0
- package/skill/references/audio/sync.md +237 -0
- package/skill/references/audio/voiceover/animation_sync.md +142 -0
- package/skill/references/audio/voiceover/provider_script.md +153 -0
- package/skill/references/audio/voiceover/providers/elevenlabs.md +288 -0
- package/skill/references/audio/voiceover/providers/manual.md +100 -0
- package/skill/references/audio/voiceover/script_writing.md +100 -0
- package/skill/references/audio/voiceover/style_intake.md +56 -0
- package/skill/references/audio/voiceover/sync_algorithm.md +167 -0
- package/skill/references/audio/voiceover.md +296 -0
- package/skill/references/audio.md +135 -0
- package/skill/references/authoring_segment.md +446 -0
- package/skill/references/create_or_edit_video.md +232 -0
- package/skill/references/dev_server.md +157 -0
- package/skill/references/export.md +145 -0
- package/skill/references/new_video.md +117 -0
- package/skill/references/project_structure.md +144 -0
- package/skill/references/setup.md +109 -0
- package/skill/references/setup_new_style.md +158 -0
- package/skill/references/styles.md +154 -0
- package/skill/references/testing.md +115 -0
- package/skill/references/types.md +240 -0
- package/src/cli/entry/components/copy_button.ts +42 -0
- package/src/cli/entry/components/download_modal.ts +204 -0
- package/src/cli/entry/components/empty_state.ts +55 -0
- package/src/cli/entry/components/hide_hud_tab.ts +37 -0
- package/src/cli/entry/components/icons.ts +31 -0
- package/src/cli/entry/components/top_bar.ts +69 -0
- package/src/cli/entry/components/video_card.ts +57 -0
- package/src/cli/entry/dev_frame.ts +189 -0
- package/src/cli/entry/entry_index.ts +16 -0
- package/src/cli/entry/entry_video.ts +24 -0
- package/src/cli/entry/index.html +12 -0
- package/src/cli/entry/parse_slug.ts +14 -0
- package/src/cli/entry/render.html +17 -0
- package/src/cli/entry/render_entry.ts +121 -0
- package/src/cli/entry/styles/base.css +45 -0
- package/src/cli/entry/styles/components.css +605 -0
- package/src/cli/entry/styles/tokens.css +44 -0
- package/src/cli/entry/video.html +22 -0
- package/src/cli/entry/views/homepage.ts +66 -0
- package/src/cli/entry/views/video_view.ts +286 -0
- package/src/cli/entry/virtual.d.ts +8 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Authoring a Segment
|
|
2
|
+
|
|
3
|
+
## What a segment is
|
|
4
|
+
|
|
5
|
+
A segment is a self-contained TypeScript module that owns a DOM element and animates it. Segments are the building blocks of Videowright videos. Each segment lives in `segments/<id>/index.ts` and default-exports the result of `defineSegment()`.
|
|
6
|
+
|
|
7
|
+
Segments are shared across all videos. Any video's timeline can reference any segment. Do not duplicate a segment for a different video — parameterize it or extract shared parts into a component.
|
|
8
|
+
|
|
9
|
+
## `defineSegment`
|
|
10
|
+
|
|
11
|
+
Every segment is authored via `defineSegment()`. It validates the spec at runtime, brands the object, and returns a frozen `Segment`.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { defineSegment } from 'videowright';
|
|
15
|
+
|
|
16
|
+
export default defineSegment({
|
|
17
|
+
id: 'intro',
|
|
18
|
+
advances: [3.0],
|
|
19
|
+
voiceover: 'Welcome to the demo.',
|
|
20
|
+
|
|
21
|
+
mount(el, ctx) {
|
|
22
|
+
// Called when the player gives the segment its host element.
|
|
23
|
+
// Set up DOM, attach components, register event listeners.
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
async play(ctx) {
|
|
27
|
+
// Main animation/content logic. Required.
|
|
28
|
+
// The segment decides when it is done via awaits on ctx methods.
|
|
29
|
+
await ctx.hold(3000);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
unmount() {
|
|
33
|
+
// Called when the segment is removed. Clean up resources.
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## SegmentSpec fields
|
|
39
|
+
|
|
40
|
+
| Field | Required | Purpose |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `id` | Yes | Unique identifier. Must match the folder name under `segments/`. |
|
|
43
|
+
| `advances` | Yes | Timing array for automated playback (render export; see below). |
|
|
44
|
+
| `voiceover` | No | VO text for this segment. Used by `videowright script` and shown in the HUD. |
|
|
45
|
+
| `notes` | No | Freeform notes. Not rendered anywhere. |
|
|
46
|
+
| `mount(el, ctx)` | No | Called when the player gives the segment its host `HTMLElement`. Set up DOM here. |
|
|
47
|
+
| `play(ctx)` | Yes | Main animation logic. Must return a `Promise<void>`. The segment ends when play resolves. |
|
|
48
|
+
| `unmount()` | No | Called when the segment is removed from the player. Clean up listeners, timers, WebGL contexts, etc. |
|
|
49
|
+
| `next()` | No | Override default next-press behavior. Return `true` to consume the press (internal beat), or `false` to let the player handle it normally. Omit the function entirely to use default behavior. |
|
|
50
|
+
| `prev()` | No | Override default prev-press behavior. Return `true` to consume the press, or `false` to let the player handle it normally. Omit the function entirely to use default behavior. |
|
|
51
|
+
|
|
52
|
+
## Lifecycle
|
|
53
|
+
|
|
54
|
+
1. **`mount(el, ctx)`** — The player creates a host `<div>` and passes it to the segment. The segment populates `el` with its DOM. This runs once per segment instance. `mount` is optional — if the segment only needs `play`, skip it.
|
|
55
|
+
|
|
56
|
+
2. **`play(ctx)`** — Runs after mount completes. This is where animation and timing happen. The segment controls its duration by awaiting `ctx.waitForNext()` and `ctx.hold(ms)`. When `play()` resolves, the segment is done and the player transitions to the next segment.
|
|
57
|
+
|
|
58
|
+
3. **`unmount()`** — Called when the player removes the segment (transition to next, or restart). Clean up any resources: event listeners, animation frames, WebGL contexts, audio nodes.
|
|
59
|
+
|
|
60
|
+
The `ctx.signal` (`AbortSignal`) is aborted when the segment is unmounted. Use it to cancel in-flight work (fetch requests, long-running loops) so they do not leak after the segment is gone.
|
|
61
|
+
|
|
62
|
+
## PlayerContext
|
|
63
|
+
|
|
64
|
+
The `ctx` object passed to `mount` and `play`:
|
|
65
|
+
|
|
66
|
+
| Method/Property | Purpose |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `ctx.waitForNext()` | Pauses `play()` until the next user advance (interactive mode) or scheduled beat (render mode). Returns a `Promise<void>`. |
|
|
69
|
+
| `ctx.hold(ms)` | Pauses `play()` for the given duration in milliseconds. In render mode, resolves when the virtual clock advances past the requested duration (deterministic, no wall-clock dependence). Returns a `Promise<void>`. |
|
|
70
|
+
| `ctx.signal` | `AbortSignal` — aborted when the segment is unmounted. Wire this to fetch calls, animation loops, or anything that should stop when the segment leaves. |
|
|
71
|
+
| `ctx.mode` | `'interactive'` (dev server) or `'render'` (export pipeline). Segments can branch on this if needed. |
|
|
72
|
+
| `ctx.clock()` | Milliseconds since this segment was mounted. In render mode, returns deterministic time based on frame count. In interactive mode, returns wall-clock elapsed. |
|
|
73
|
+
|
|
74
|
+
### Timing: `waitForNext` vs. `hold`
|
|
75
|
+
|
|
76
|
+
Use **`ctx.waitForNext()`** for content beats — points where the visual advances to match a voiceover cue (or the user's manual advance in dev mode). This creates a pause that the user steps through with Space / Right Arrow in dev mode, and the export pipeline drives with the `advances` timing.
|
|
77
|
+
|
|
78
|
+
**`waitForNext()` is the voiceover alignment primitive.** Place a `waitForNext()` at every point where the visual should sync to a voiceover cue — each section reveal, each bullet point, each stat highlight. This decouples segment authoring from any specific voiceover recording: swapping narration only requires new timing data (a different `Timing` in the voiceover), not rewriting the segment code. If you instead bake VO-specific durations into `hold()` calls, the segment is married to one narration and must be re-authored to accommodate a different voice, pacing, or language.
|
|
79
|
+
|
|
80
|
+
**Rule of thumb:** if you are deciding between `waitForNext()` and `hold()` for a content reveal, ask: "Would a different voiceover want to trigger this at a different time?" If yes, use `waitForNext()`.
|
|
81
|
+
|
|
82
|
+
Use **`ctx.hold(ms)`** for timed pauses that are internal to a beat — entrance animation lead-in, dwell time after a reveal before the next interactive point, or inserting a gap between logical phases that should always take the same amount of time regardless of voiceover. In dev mode, `hold` waits real time. In render mode, `hold` resolves when the virtual clock advances past the delay — identical semantics to dev mode but driven by the deterministic frame clock.
|
|
83
|
+
|
|
84
|
+
`ctx.hold(ms)` is deterministic in render mode, so `for...await ctx.hold(N)` mutation loops fire across distinct frames as expected. For smooth sub-frame interpolation (eased motion), WAAPI or CSS animations remain preferred — hold-driven loops are best for stepped/discrete state changes.
|
|
85
|
+
|
|
86
|
+
`setTimeout`/`setInterval` are virtualized by the render shim and fire deterministically; you may use them freely. Prefer `ctx.hold(ms)` for control-flow pauses since it participates in the structured `play()` flow and respects segment teardown.
|
|
87
|
+
|
|
88
|
+
### Render-safe animation patterns
|
|
89
|
+
|
|
90
|
+
Render mode uses a **virtual clock** that the render driver advances frame by frame between captured screenshots. The render shim virtualizes all timer primitives: `performance.now()`, `Date.now()`, `setTimeout`, `setInterval`, and `requestAnimationFrame` all reflect deterministic virtual time. WAAPI and CSS animations also run on the document timeline, which the render driver controls.
|
|
91
|
+
|
|
92
|
+
#### Patterns and recommendations
|
|
93
|
+
|
|
94
|
+
All of the following patterns work deterministically under the render shim. WAAPI is preferred for DOM animation because it provides smoother sub-frame interpolation and requires less code, but these alternatives are acceptable — especially for stepped/discrete state changes.
|
|
95
|
+
|
|
96
|
+
**1. `await ctx.hold(N)` in a mutation loop** — works deterministically; each iteration fires on a distinct frame. Prefer WAAPI for smooth eased animation (e.g., typing effects with per-character opacity fade), but hold loops are fine for discrete state changes:
|
|
97
|
+
```ts
|
|
98
|
+
// Works: typing animation with hold loop (stepped reveal, no easing)
|
|
99
|
+
async play(ctx) {
|
|
100
|
+
const el = host!.querySelector('[data-ref="text"]') as HTMLElement;
|
|
101
|
+
const text = 'Hello, world!';
|
|
102
|
+
for (let i = 0; i <= text.length; i++) {
|
|
103
|
+
el.textContent = text.slice(0, i);
|
|
104
|
+
await ctx.hold(50); // each iteration advances virtual time by 50ms
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**2. `setTimeout` / `setInterval`** — virtualized by the render shim, fires at the correct virtual time. Prefer `ctx.hold(ms)` for control-flow pauses (abort-signal integration), but `setTimeout` is fine for fire-and-forget scheduling:
|
|
110
|
+
```ts
|
|
111
|
+
// Works: delayed class addition via setTimeout
|
|
112
|
+
setTimeout(() => el.classList.add('visible'), 500);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**3. `performance.now()` / `Date.now()` for animation progress** — virtualized by the shim; returns deterministic virtual time. `ctx.clock()` is preferred for semantic clarity (it clearly communicates "time since segment mount"), but raw timer reads also work:
|
|
116
|
+
```ts
|
|
117
|
+
// Works: manual rAF loop with performance.now()
|
|
118
|
+
const start = performance.now();
|
|
119
|
+
function tick() {
|
|
120
|
+
const t = (performance.now() - start) / 1000;
|
|
121
|
+
mesh.rotation.x = t * 0.25;
|
|
122
|
+
requestAnimationFrame(tick);
|
|
123
|
+
}
|
|
124
|
+
requestAnimationFrame(tick);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**4. Custom `requestAnimationFrame` loops with deltaTime** — rAF timestamps are virtualized, so deltaTime calculations produce correct deterministic results. `ctx.clock()` is still preferred for Three.js/WebGL scenes (clearer intent):
|
|
128
|
+
```ts
|
|
129
|
+
// Works: deltaTime from rAF timestamps
|
|
130
|
+
let last = 0;
|
|
131
|
+
function tick(now: number) {
|
|
132
|
+
const dt = now - last;
|
|
133
|
+
last = now;
|
|
134
|
+
position += velocity * dt; // dt reflects virtual time
|
|
135
|
+
requestAnimationFrame(tick);
|
|
136
|
+
}
|
|
137
|
+
requestAnimationFrame(tick);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Prescribed patterns
|
|
141
|
+
|
|
142
|
+
**1. WAAPI for DOM animations** (the default choice):
|
|
143
|
+
```ts
|
|
144
|
+
// GOOD: WAAPI animations run on the document timeline — render-safe
|
|
145
|
+
el.animate(
|
|
146
|
+
[
|
|
147
|
+
{ opacity: 0, transform: 'translateY(20px)' },
|
|
148
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
149
|
+
],
|
|
150
|
+
{ duration: 500, fill: 'forwards', easing: 'ease-out' },
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Prefer WAAPI `delay` to stagger multiple animations over `ctx.hold()` between them (less code, smoother easing):
|
|
155
|
+
```ts
|
|
156
|
+
// GOOD: staggered entrance using WAAPI delay — render-safe
|
|
157
|
+
const items = host!.querySelectorAll('.item');
|
|
158
|
+
items.forEach((item, i) => {
|
|
159
|
+
(item as HTMLElement).animate(
|
|
160
|
+
[
|
|
161
|
+
{ opacity: 0, transform: 'translateY(16px)' },
|
|
162
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
163
|
+
],
|
|
164
|
+
{ duration: 400, delay: i * 80, fill: 'forwards', easing: 'ease-out' },
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**2. CSS animations and transitions** declared in CSS or set via `el.style.animation`:
|
|
170
|
+
```ts
|
|
171
|
+
// GOOD: CSS animation — runs on document timeline, render-safe
|
|
172
|
+
el.style.animation = 'fadeIn 0.5s ease-out forwards';
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**3. WAAPI for typing animations** — use per-character `delay` instead of a mutation loop:
|
|
176
|
+
```ts
|
|
177
|
+
// GOOD: typing animation using WAAPI delay-per-character — render-safe
|
|
178
|
+
mount(el) {
|
|
179
|
+
const text = 'Hello, world!';
|
|
180
|
+
el.innerHTML = text
|
|
181
|
+
.split('')
|
|
182
|
+
.map((ch) => `<span style="opacity:0">${ch === ' ' ? ' ' : ch}</span>`)
|
|
183
|
+
.join('');
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
async play(ctx) {
|
|
187
|
+
const chars = host!.querySelectorAll('span');
|
|
188
|
+
chars.forEach((ch, i) => {
|
|
189
|
+
(ch as HTMLElement).animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
190
|
+
duration: 1, // near-instant per character
|
|
191
|
+
delay: i * 50, // 50ms between each character
|
|
192
|
+
fill: 'forwards',
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
await ctx.waitForNext();
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**4. `ctx.clock()` for time-derived values** — returns deterministic milliseconds in render mode:
|
|
200
|
+
```ts
|
|
201
|
+
// GOOD: Three.js driven by ctx.clock() — render-safe
|
|
202
|
+
async play(ctx) {
|
|
203
|
+
function tick() {
|
|
204
|
+
if (ctx.signal.aborted) return;
|
|
205
|
+
const t = ctx.clock() / 1000; // seconds since mount, deterministic in render
|
|
206
|
+
mesh.rotation.x = t * 0.25;
|
|
207
|
+
mesh.rotation.y = t * 0.15;
|
|
208
|
+
renderer.render(scene, camera);
|
|
209
|
+
requestAnimationFrame(tick);
|
|
210
|
+
}
|
|
211
|
+
requestAnimationFrame(tick);
|
|
212
|
+
await ctx.waitForNext();
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**5. Lottie — drive frames manually via `ctx.clock()`** instead of letting lottie-web run its own rAF loop:
|
|
217
|
+
```ts
|
|
218
|
+
// GOOD: Lottie with manual frame drive — render-safe
|
|
219
|
+
async play(ctx) {
|
|
220
|
+
const anim = lottie.loadAnimation({
|
|
221
|
+
container: host!,
|
|
222
|
+
animationData: data,
|
|
223
|
+
autoplay: false, // critical: disable internal rAF loop
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
function tick() {
|
|
227
|
+
if (ctx.signal.aborted) return;
|
|
228
|
+
anim.goToAndStop(ctx.clock(), false); // ctx.clock() returns ms
|
|
229
|
+
requestAnimationFrame(tick);
|
|
230
|
+
}
|
|
231
|
+
requestAnimationFrame(tick);
|
|
232
|
+
await ctx.waitForNext();
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**6. Three.js — prefer `ctx.clock()` over `performance.now()`** for time-derived values (rotation, position, shader uniforms). `performance.now()` also works under the shim; `ctx.clock()` is preferred for semantic clarity (segment-relative time):
|
|
237
|
+
```ts
|
|
238
|
+
// GOOD: Three.js scene driven by deterministic clock
|
|
239
|
+
const t = ctx.clock() / 1000;
|
|
240
|
+
mesh.position.y = Math.sin(t * 2) * 0.5;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### When `ctx.hold()` is the right choice
|
|
244
|
+
|
|
245
|
+
`ctx.hold()` is idiomatic for:
|
|
246
|
+
- **Control-flow pauses** — waiting a fixed time before calling `ctx.waitForNext()`, or inserting a gap between logical phases of a segment.
|
|
247
|
+
- **Stepped/discrete state changes** — typing out characters, revealing list items one by one, or any mutation loop where each step is a distinct visual state rather than a smoothly interpolated motion.
|
|
248
|
+
|
|
249
|
+
For smooth eased animation (opacity fades, position tweens), prefer WAAPI — it provides sub-frame interpolation that hold-driven loops cannot.
|
|
250
|
+
|
|
251
|
+
### Percentage-based timing within beats
|
|
252
|
+
|
|
253
|
+
**Strong default:** express `hold()` durations inside a beat or sub-segment as a percentage of the beat's total time rather than fixed millisecond values.
|
|
254
|
+
|
|
255
|
+
When a voiceover changes — even slightly — every segment's beat durations shift. If the animations within a beat use hardcoded millisecond timers, those timers no longer fit the new beat length: an animation that filled 80% of a 4-second beat now overruns a 3-second beat, or leaves dead air in a 5-second beat. Percentage-based timing adapts gracefully because the durations scale with the beat.
|
|
256
|
+
|
|
257
|
+
A typical pattern: compute the beat duration from context (e.g., the total hold time allocated to the current phase), then derive individual timers as fractions of it.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
// GOOD: timers scale with beat length
|
|
261
|
+
const beatMs = 3000; // total time for this phase
|
|
262
|
+
await ctx.hold(beatMs * 0.2); // entrance animation: 20% of beat
|
|
263
|
+
items.forEach((item, i) => {
|
|
264
|
+
(item as HTMLElement).animate(
|
|
265
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
266
|
+
{ duration: beatMs * 0.15, delay: beatMs * 0.25 + i * (beatMs * 0.1), fill: 'forwards' },
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
await ctx.hold(beatMs * 0.8); // remaining time for content display
|
|
270
|
+
|
|
271
|
+
// AVOID: fixed timers break when beat length changes
|
|
272
|
+
await ctx.hold(600);
|
|
273
|
+
items.forEach((item, i) => {
|
|
274
|
+
(item as HTMLElement).animate(
|
|
275
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
276
|
+
{ duration: 450, delay: 750 + i * 300, fill: 'forwards' },
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
await ctx.hold(2400);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This is a best practice, not a hard rule. Fixed timers are appropriate when the duration is truly independent of the beat — e.g., a 200ms micro-interaction that should always feel the same speed. But as a default posture, reach for percentages.
|
|
283
|
+
|
|
284
|
+
**Related smell:** if you find yourself tweaking `hold()` values inside a segment to match a specific voiceover's pacing, that is a sign the beat boundaries are wrong. The content that needs to align with the voiceover should be gated by its own `waitForNext()` call so the timing comes from the `advances`/`Timing` data, not from hardcoded milliseconds. See [voiceover.md § VO-alignment smell](audio/voiceover.md#vo-alignment-smell) for more detail.
|
|
285
|
+
|
|
286
|
+
## The `advances` array
|
|
287
|
+
|
|
288
|
+
Every segment must declare an `advances` array. It tells the render export pipeline when to fire each advance during automated playback.
|
|
289
|
+
|
|
290
|
+
Each entry is a **segment-relative time in seconds** at which the driver fires a `triggerNext()`. The array must be **monotonically increasing** and contain only **positive numbers**.
|
|
291
|
+
|
|
292
|
+
### How `advances` maps to `waitForNext` and `hold`
|
|
293
|
+
|
|
294
|
+
The length of `advances` equals the **total number of triggerNext() presses** needed to traverse the segment, **including the final press that transitions to the next segment**.
|
|
295
|
+
|
|
296
|
+
**Example 1: hold only**
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
async play(ctx) {
|
|
300
|
+
await ctx.hold(3000); // 3 seconds of animation
|
|
301
|
+
}
|
|
302
|
+
// play() resolves after hold completes -> 1 press needed to move to the next segment
|
|
303
|
+
// advances: [3.0]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
One advance at 3.0s. After the hold completes and `play()` resolves, the export driver fires triggerNext at t=3.0s, which causes the player to transition to the next segment. The advance does not cause the hold — the hold runs on its own clock and the advance fires afterward to move past the segment.
|
|
307
|
+
|
|
308
|
+
**Example 2: one interactive beat**
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
async play(ctx) {
|
|
312
|
+
// Show title
|
|
313
|
+
await ctx.waitForNext(); // 1 press: reveals content
|
|
314
|
+
// Show content
|
|
315
|
+
await ctx.hold(2000); // 2 second animation
|
|
316
|
+
}
|
|
317
|
+
// play() resolves -> 1 more press to move to next segment
|
|
318
|
+
// advances: [1.5, 4.0]
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Two advances: first press at t=1.5s reveals content, second press at t=4.0s moves to the next segment.
|
|
322
|
+
|
|
323
|
+
**Example 3: multiple interactive beats**
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
async play(ctx) {
|
|
327
|
+
await ctx.waitForNext(); // 1: reveal point A
|
|
328
|
+
await ctx.waitForNext(); // 2: reveal point B
|
|
329
|
+
await ctx.hold(1000); // 1 second hold
|
|
330
|
+
}
|
|
331
|
+
// play() resolves -> 1 more press to move to next segment
|
|
332
|
+
// advances: [2.0, 4.0, 6.0]
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Three advances total. Each waitForNext consumes a press, and the final press after play resolves transitions out.
|
|
336
|
+
|
|
337
|
+
### Key rules
|
|
338
|
+
|
|
339
|
+
- `advances` is **required** on every segment. `defineSegment` throws if it is missing or empty.
|
|
340
|
+
- Values must be **positive** and **monotonically increasing**.
|
|
341
|
+
- The array length determines how many presses the export pipeline fires for this segment.
|
|
342
|
+
- `advances` is used in render mode (deterministic export). In interactive dev mode, the user's key presses drive timing.
|
|
343
|
+
|
|
344
|
+
## Internal beats with `next()`
|
|
345
|
+
|
|
346
|
+
For segments that need sub-steps (e.g., a list that reveals items one by one), use the `next()` override.
|
|
347
|
+
|
|
348
|
+
Note: `defineSegment()` freezes the returned object, so you cannot store mutable state on `this`. Use closure-scoped variables instead:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
let items: NodeListOf<Element>;
|
|
352
|
+
let revealed = 0;
|
|
353
|
+
|
|
354
|
+
export default defineSegment({
|
|
355
|
+
id: 'feature-list',
|
|
356
|
+
advances: [1.5, 3.0, 4.5, 6.0],
|
|
357
|
+
|
|
358
|
+
mount(el) {
|
|
359
|
+
el.innerHTML = '<div class="item">A</div><div class="item">B</div><div class="item">C</div>';
|
|
360
|
+
items = el.querySelectorAll('.item');
|
|
361
|
+
revealed = 0;
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
next() {
|
|
365
|
+
if (revealed < items.length) {
|
|
366
|
+
items[revealed].classList.add('visible');
|
|
367
|
+
revealed++;
|
|
368
|
+
return true; // consumed — stay on this segment
|
|
369
|
+
}
|
|
370
|
+
return false; // not consumed — let the player advance to next segment
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
async play(ctx) {
|
|
374
|
+
// play can just hold while next() handles the reveals
|
|
375
|
+
await ctx.hold(6000);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
When `next()` returns `true`, the press is consumed as an internal beat — the player does not transition. When it returns `false`, the player handles the press normally (moves to next segment or resolves a pending `waitForNext`).
|
|
381
|
+
|
|
382
|
+
## Idempotency
|
|
383
|
+
|
|
384
|
+
Segments should be safe to mount and unmount multiple times. This happens when:
|
|
385
|
+
|
|
386
|
+
- The user navigates backward and forward through the timeline.
|
|
387
|
+
- The player restarts.
|
|
388
|
+
- The dev server hot-reloads.
|
|
389
|
+
|
|
390
|
+
Rules:
|
|
391
|
+
|
|
392
|
+
- Do not assume `mount` is called only once per page load.
|
|
393
|
+
- Clean up everything in `unmount()` — event listeners, DOM mutations outside `el`, global state.
|
|
394
|
+
- Use `ctx.signal` to abort in-flight work when the segment is unmounted mid-play.
|
|
395
|
+
|
|
396
|
+
## Any web tech is welcome
|
|
397
|
+
|
|
398
|
+
Segments own their DOM. Inside a segment, use whatever fits:
|
|
399
|
+
|
|
400
|
+
- **CSS animations and transitions** for simple motion. Render-safe -- they run on the document timeline.
|
|
401
|
+
- **GSAP** for complex timeline-based animation. Render-safe when using its document-timeline mode.
|
|
402
|
+
- **Three.js / WebGL** for 3D scenes. Drive time-derived values with `ctx.clock()`, not `performance.now()`. See render-safe patterns above.
|
|
403
|
+
- **Lottie** for After Effects animations. Use manual frame drive with `ctx.clock()` (`autoplay: false`, `anim.goToAndStop(ctx.clock(), false)`). See render-safe patterns above.
|
|
404
|
+
- **Animated SVG** for vector graphics. Use WAAPI or CSS animations for SVG element animations.
|
|
405
|
+
- **React / shadcn** for component-driven UI (attach to `el` via `createRoot`).
|
|
406
|
+
- **echarts / D3** for data visualization.
|
|
407
|
+
- **Canvas 2D** for pixel-level control. Use `ctx.clock()` for time-derived drawing.
|
|
408
|
+
|
|
409
|
+
If you need DOM isolation (e.g., to avoid CSS conflicts), attach a shadow root to `el`:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
mount(el) {
|
|
413
|
+
const shadow = el.attachShadow({ mode: 'open' });
|
|
414
|
+
shadow.innerHTML = `<style>/* scoped styles */</style><div>...</div>`;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Visual sizing — fill the frame
|
|
419
|
+
|
|
420
|
+
Video is not a web page. There is no scrolling, no responsive reflow, no "above the fold." Every frame is a fixed canvas (typically 1920x1080) and the viewer sees exactly what is rendered. Treat every pixel as valuable.
|
|
421
|
+
|
|
422
|
+
### Rules
|
|
423
|
+
|
|
424
|
+
- **Text must be legible at playback size.** If a viewer watching the video at its native resolution cannot comfortably read the text, it is too small. Headings, stats, labels, and body copy should be generously sized. A good baseline: body text should be at least 36px at 1080p; headings should be 64px or larger. These are minimums — go bigger when the content allows.
|
|
425
|
+
- **Use the full canvas.** Content should fill the frame. Avoid excessive margins, padding, or whitespace that pushes the visual into a small centered region. A segment showing a single stat or a short heading should scale that content up to dominate the frame, not float it in the middle at web-scale sizes.
|
|
426
|
+
- **If text is too small to matter, remove it.** Tiny footnotes, fine-print labels, or decorative micro-text that cannot be read serve no purpose in a video. Either make the text large enough to read or omit it. Small decorative labels are acceptable only when they are an intentional style element (e.g., a HUD aesthetic with tertiary metadata), not a default choice.
|
|
427
|
+
- **Margins are intentional design, not defaults.** When a style calls for breathing room (e.g., editorial layouts with generous whitespace), that is a deliberate aesthetic choice documented in the style's STYLE.md. The default posture is to fill the frame, and whitespace should be a conscious decision, not an accident of web-scale CSS habits.
|
|
428
|
+
- **Test at actual video resolution.** Run `npx videowright dev` and check whether the content fills the player viewport. If elements look small or lost in empty space, increase sizes.
|
|
429
|
+
|
|
430
|
+
### Common mistakes
|
|
431
|
+
|
|
432
|
+
| Mistake | Fix |
|
|
433
|
+
|---|---|
|
|
434
|
+
| `font-size: 16px` or `1rem` on body text | Use `36px`+ for body, `64px`+ for headings at 1080p |
|
|
435
|
+
| `max-width: 800px; margin: 0 auto` (web-page centering) | Remove max-width constraints or set them close to the video width. Use `width: 90%` or `padding: 40px` instead. |
|
|
436
|
+
| Small centered card with large empty background | Scale the card to fill 80-90% of the frame, or use the background intentionally (texture, animation, gradient) |
|
|
437
|
+
| Bullet list with small text and huge line spacing | Increase font size, reduce line count if needed, fill the frame |
|
|
438
|
+
| Container widths set too conservatively | Use `width: 80%`+ for primary content containers; `90%`+ is often appropriate. Prefer `%` over `vw`/`vh` for consistency with the rules above. |
|
|
439
|
+
|
|
440
|
+
## Using style tokens
|
|
441
|
+
|
|
442
|
+
Segments consume the active style's design tokens via CSS custom properties: `var(--color-accent)`, `var(--font-display)`, etc. Segments do **not** import `tokens.css` themselves — the timeline-level import provides the variables at runtime via `:root` custom properties.
|
|
443
|
+
|
|
444
|
+
This means one segment can be reused across videos with different styles. The CSS variables resolve to whatever the video's timeline imported.
|
|
445
|
+
|
|
446
|
+
See [styles.md](styles.md) for the full token system and recommended token set.
|