@vanillaskyai/video 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.
Files changed (162) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +201 -0
  3. package/PUBLIC-API.md +276 -0
  4. package/README.md +152 -0
  5. package/SECURITY.md +22 -0
  6. package/SUPPORT.md +28 -0
  7. package/bin/vanillasky.js +3 -0
  8. package/dist/bg-confetti-WNQXS7ZS.js +89 -0
  9. package/dist/bg-emoji-ETI47GLS.js +150 -0
  10. package/dist/bg-media-SVUZAMGX.js +99 -0
  11. package/dist/brand-message-F3RD4P3P.js +253 -0
  12. package/dist/builtin-server-F7YXRVCH.js +8 -0
  13. package/dist/catalog-types-BIhSpOWK.d.ts +74 -0
  14. package/dist/chart-bar-CNSC7BNK.js +304 -0
  15. package/dist/chart-counter-MCRHKN77.js +299 -0
  16. package/dist/chart-progress-ring-RMI4A7ZK.js +296 -0
  17. package/dist/check-runtime.d.ts +15 -0
  18. package/dist/check-runtime.js +102 -0
  19. package/dist/chunk-2E6T633S.js +27 -0
  20. package/dist/chunk-352ZZCET.js +83 -0
  21. package/dist/chunk-73NTSFFI.js +81 -0
  22. package/dist/chunk-A2X45FET.js +46 -0
  23. package/dist/chunk-ABPKM4GK.js +187 -0
  24. package/dist/chunk-ABZACD4G.js +216 -0
  25. package/dist/chunk-BCRUUJ6A.js +756 -0
  26. package/dist/chunk-CHLGW22F.js +3551 -0
  27. package/dist/chunk-CJ7F2CE6.js +104 -0
  28. package/dist/chunk-CR7FE7BW.js +109 -0
  29. package/dist/chunk-E7CL7UPB.js +109 -0
  30. package/dist/chunk-EE6PK6XC.js +18 -0
  31. package/dist/chunk-ERY4S6QV.js +385 -0
  32. package/dist/chunk-EVG2ZYIY.js +125 -0
  33. package/dist/chunk-FNAEQ7QO.js +155 -0
  34. package/dist/chunk-FQ47WXAJ.js +38 -0
  35. package/dist/chunk-FSG2PYWG.js +336 -0
  36. package/dist/chunk-GHJEAP4O.js +504 -0
  37. package/dist/chunk-GRMUV7QY.js +262 -0
  38. package/dist/chunk-IFOW65Z6.js +205 -0
  39. package/dist/chunk-IXCUY3B3.js +786 -0
  40. package/dist/chunk-L5BTTCZV.js +97 -0
  41. package/dist/chunk-LMG3GHLL.js +107 -0
  42. package/dist/chunk-OOPIETLW.js +101 -0
  43. package/dist/chunk-P22HHFDK.js +43 -0
  44. package/dist/chunk-QAAO7OAY.js +19 -0
  45. package/dist/chunk-S7HFLYNN.js +35 -0
  46. package/dist/chunk-SPVTJH3F.js +24 -0
  47. package/dist/chunk-W2XJKG6C.js +3177 -0
  48. package/dist/chunk-XB7LH4J2.js +301 -0
  49. package/dist/chunk-XGRA2MUV.js +242 -0
  50. package/dist/chunk-XTOXFMLM.js +105 -0
  51. package/dist/chunk-YC5MFXA5.js +132 -0
  52. package/dist/cli.d.ts +1 -0
  53. package/dist/cli.js +1681 -0
  54. package/dist/compose-video-CN3VSWST.js +13 -0
  55. package/dist/cta-logo-SVA5GP2A.js +209 -0
  56. package/dist/cta-media-E7VLLK42.js +215 -0
  57. package/dist/events-_wfjbmEp.d.ts +81 -0
  58. package/dist/incoming-call-62MFZL3Z.js +320 -0
  59. package/dist/index.d.ts +15 -0
  60. package/dist/index.js +14 -0
  61. package/dist/infographic-before-after-AXCDAWND.js +460 -0
  62. package/dist/infographic-feature-list-GW5GUE6O.js +266 -0
  63. package/dist/infographic-problem-solution-7NURJANA.js +340 -0
  64. package/dist/infographic-stat-row-J7IZR6ZN.js +241 -0
  65. package/dist/infographic-steps-VSZM6F5M.js +400 -0
  66. package/dist/kit-BMNcHqa9.d.ts +79 -0
  67. package/dist/prompt-input-LOUCRXCY.js +236 -0
  68. package/dist/react.d.ts +81 -0
  69. package/dist/react.js +835 -0
  70. package/dist/reaction-QG4CYZLQ.js +119 -0
  71. package/dist/server.d.ts +141 -0
  72. package/dist/server.js +498 -0
  73. package/dist/showcase-code-RUTFZR3H.js +351 -0
  74. package/dist/showcase-phone-6CP6XEU5.js +272 -0
  75. package/dist/showcase-terminal-MGAEQWPR.js +306 -0
  76. package/dist/showcase-web-Y4W2HVRJ.js +390 -0
  77. package/dist/social-conversation-75SD2637.js +695 -0
  78. package/dist/social-milestone-XI2QE5BO.js +292 -0
  79. package/dist/social-notification-2UOHQCUY.js +244 -0
  80. package/dist/social-review-stack-FDYZGZTJ.js +239 -0
  81. package/dist/social-testimonial-OQ7N52GJ.js +284 -0
  82. package/dist/social-tweet-5OLCNPPC.js +295 -0
  83. package/dist/state-DZcKuS32.d.ts +3 -0
  84. package/dist/system-prompt-A6UXNTPK.js +8 -0
  85. package/dist/template-catalog.d.ts +24 -0
  86. package/dist/template-catalog.js +6 -0
  87. package/dist/templates.d.ts +25 -0
  88. package/dist/templates.js +19 -0
  89. package/dist/test.d.ts +149 -0
  90. package/dist/test.js +272 -0
  91. package/dist/text-stream-6XHC7J47.js +10 -0
  92. package/dist/types-B-PZR6G_.d.ts +208 -0
  93. package/dist/validate-T7GBU2YF.js +8 -0
  94. package/docs/agent-integration.md +39 -0
  95. package/docs/architecture.md +84 -0
  96. package/docs/branding-and-personalization.md +87 -0
  97. package/docs/concepts.md +106 -0
  98. package/docs/custom-templates.md +329 -0
  99. package/docs/customization.md +141 -0
  100. package/docs/errors.md +70 -0
  101. package/docs/getting-started.md +111 -0
  102. package/docs/input-and-first-scene.md +41 -0
  103. package/docs/integrate-nextjs.md +79 -0
  104. package/docs/media-and-audio.md +69 -0
  105. package/docs/motion-and-effects.md +107 -0
  106. package/docs/persistence.md +95 -0
  107. package/docs/production.md +122 -0
  108. package/docs/prompt-and-input.md +185 -0
  109. package/docs/provider-integration.md +115 -0
  110. package/docs/reference/protocol.md +96 -0
  111. package/docs/reference/provider-adapters.md +158 -0
  112. package/docs/responsive-orientation.md +37 -0
  113. package/docs/security.md +38 -0
  114. package/docs/streaming-protocol.md +15 -0
  115. package/docs/testing.md +137 -0
  116. package/docs/use-cases.md +59 -0
  117. package/examples/custom-template/README.md +19 -0
  118. package/examples/custom-template/minimal-text.tsx +82 -0
  119. package/examples/custom-template/structured-data.tsx +104 -0
  120. package/examples/custom-template/supplied-media.tsx +92 -0
  121. package/examples/nextjs-quickstart/.env.example +2 -0
  122. package/examples/nextjs-quickstart/README.md +22 -0
  123. package/examples/nextjs-quickstart/next-env.d.ts +4 -0
  124. package/examples/nextjs-quickstart/package.json +24 -0
  125. package/examples/nextjs-quickstart/src/app/api/video/route.ts +22 -0
  126. package/examples/nextjs-quickstart/src/app/layout.tsx +5 -0
  127. package/examples/nextjs-quickstart/src/app/page.tsx +31 -0
  128. package/examples/nextjs-quickstart/tsconfig.json +26 -0
  129. package/package.json +167 -0
  130. package/registry/items/backgrounds.json +52 -0
  131. package/registry/items/barChart.json +237 -0
  132. package/registry/items/beforeAfter.json +180 -0
  133. package/registry/items/bigNumber.json +225 -0
  134. package/registry/items/brandMessage.json +158 -0
  135. package/registry/items/cardList.json +206 -0
  136. package/registry/items/chatMessenger.json +151 -0
  137. package/registry/items/chatWhatsapp.json +151 -0
  138. package/registry/items/codeEditor.json +184 -0
  139. package/registry/items/confetti.json +107 -0
  140. package/registry/items/ctaLogo.json +160 -0
  141. package/registry/items/ctaMedia.json +193 -0
  142. package/registry/items/emojiBurst.json +101 -0
  143. package/registry/items/incomingCall.json +170 -0
  144. package/registry/items/media.json +179 -0
  145. package/registry/items/milestone.json +205 -0
  146. package/registry/items/motion.json +45 -0
  147. package/registry/items/notification.json +182 -0
  148. package/registry/items/phoneMockup.json +254 -0
  149. package/registry/items/problemSolution.json +186 -0
  150. package/registry/items/progressRing.json +199 -0
  151. package/registry/items/promptInput.json +148 -0
  152. package/registry/items/reaction.json +170 -0
  153. package/registry/items/reviewStack.json +224 -0
  154. package/registry/items/steps.json +207 -0
  155. package/registry/items/template-context.json +31 -0
  156. package/registry/items/terminal.json +212 -0
  157. package/registry/items/testimonial.json +164 -0
  158. package/registry/items/theme.json +47 -0
  159. package/registry/items/tripleStats.json +228 -0
  160. package/registry/items/tweet.json +203 -0
  161. package/registry/items/typography.json +47 -0
  162. package/registry/items/webMockup.json +273 -0
@@ -0,0 +1,87 @@
1
+ [← Documentation home](../README.md) · [Previous: Input and first scene](input-and-first-scene.md) · [Next: Media and audio →](media-and-audio.md)
2
+
3
+ # Branding and personalization
4
+
5
+ Brand values style the whole response. Personalization shapes who the story is for.
6
+
7
+ ## Background: standard, preset, or custom
8
+
9
+ Do nothing to use the standard `cosmic` background:
10
+
11
+ ```ts
12
+ video.generate({ input });
13
+ ```
14
+
15
+ Choose a curated gradient or solid by name when the standard does not fit:
16
+
17
+ ```ts
18
+ video.generate({
19
+ input,
20
+ brand: { background: "twilight" },
21
+ });
22
+ ```
23
+
24
+ Curated gradients are `cosmic`, `horizon`, `twilight`, `meadow`, `velvet`,
25
+ `flamingo`, `peach`, and `saffron`. Curated solids are `black`, `midnight`,
26
+ `aubergine`, `coal`, and `navy`.
27
+
28
+ Use exact colors only when a real brand or campaign requires them:
29
+
30
+ ```ts
31
+ brand: { background: { colors: ["#112233", "#334455"] } } // gradient
32
+ brand: { background: { color: "#070B20" } } // solid
33
+ ```
34
+
35
+ These choices set the background colors used by every trusted gradient-backed
36
+ template and by the automatic generation cover. Templates own their
37
+ composition and type treatment, so there is no separate visual-preset choice.
38
+
39
+ ## Semantic brand tokens
40
+
41
+ Most applications can stop at `brand.background` and `brand.font`. Supply
42
+ semantic colors only when a real design system requires exact values:
43
+
44
+ ```ts
45
+ const brand = {
46
+ name: "Acme Cloud",
47
+ logoUrl: approvedLogoUrl,
48
+ font: "Inter",
49
+ scriptFont: "Caveat",
50
+ background: "twilight",
51
+ colors: {
52
+ primary: "#7C5CFC",
53
+ secondary: "#3D2A78",
54
+ foreground: "#FFFFFF",
55
+ surface: "#161229",
56
+ surfaceElevated: "#211A38",
57
+ muted: "#AAA5B8",
58
+ },
59
+ };
60
+ ```
61
+
62
+ Every generated video contains a fully resolved brand. `colors` may be partial
63
+ on input; omitted values receive deterministic defaults. When
64
+ `colors.foreground` is omitted, every named or custom background is paired
65
+ deterministically with black or white. The resolver checks the full rendered
66
+ sRGB gradient ramp, including interior blends rather than only its endpoints.
67
+ If neither foreground reaches 4.5:1 everywhere, resolution rejects the
68
+ background and asks for a consistently readable choice.
69
+
70
+ An explicitly supplied foreground is preserved, never silently replaced. It
71
+ must reach 4.5:1 across the solid background or entire gradient ramp; otherwise
72
+ input resolution and replay validation reject it with the failing brand path
73
+ and minimum contrast. Surface tokens remain independent. Templates that put
74
+ text on `surface` or `surfaceElevated` internally keep an accessible preferred
75
+ semantic text color or derive black/white for that surface.
76
+
77
+ Use an approved public or signed URL for logos. Fonts must be licensed and
78
+ loaded by the host application before playback. Always test both orientations
79
+ and forced browser zoom.
80
+
81
+ `personalization` accepts JSON-safe viewer or account context such as
82
+ `firstName`, `role`, `accountName`, `locale`, `goal`, or `onboardingPartner`.
83
+ These values may appear verbatim in the response, but are always data, never
84
+ instructions. Put product, news, metric, and quoted claims in `input`; do not
85
+ duplicate ordinary viewer context there.
86
+
87
+ Only include information the authenticated viewer is allowed to see. Avoid sensitive HR, health, payment, or customer attributes in event logs and analytics. If a locale changes number/date formatting, format facts server-side so the planner cannot guess.
@@ -0,0 +1,106 @@
1
+ [← Documentation home](../README.md) · [Previous: Getting started](getting-started.md) · [Next: Streaming protocol →](streaming-protocol.md)
2
+
3
+ # Core concepts
4
+
5
+ ## Video response
6
+
7
+ A video response is a short, responsive animated experience assembled from
8
+ trusted templates. It begins before the full plan is available and ends as an
9
+ editable deterministic configuration.
10
+
11
+ It is not an encoded video stream. The browser renders normal React components
12
+ from validated scene instructions. The 0.1 SDK does not include MP4 or WebM
13
+ encoding; pass the completed deterministic JSON to an application-owned render
14
+ or export pipeline when an encoded file is required.
15
+
16
+ ## Input
17
+
18
+ `VideoInput` is the factual and creative boundary:
19
+
20
+ - `input`: raw source material such as a roadmap update, launch brief, article,
21
+ curated set of articles, metrics, events, notes, or an AI answer. It may be
22
+ short, but the planner is designed to distill larger sources into a concise
23
+ video rather than represent every fact;
24
+ - `instructions`: optional creative direction that cannot override facts;
25
+ - `opening`: optional copy for a deterministic opening that can play immediately;
26
+ - `personalization`: application-defined fields such as name, role, account,
27
+ period, goal, or onboarding partner;
28
+ - `brand`: an optional background preset plus name, logo, font, surfaces, and
29
+ advanced exact tokens;
30
+ - `suppliedMedia`: approved images or videos with a role and description;
31
+ - `audio`: omit for automatic selection, pass `{ src }`, or use `false` for silence;
32
+ - `orientation` and `maxDurationSec`: composition constraints.
33
+
34
+ ## Template kit
35
+
36
+ VanillaSky uses its trusted built-in kit by default. Applications only configure
37
+ a kit when they need project-owned templates. Those templates replace matching
38
+ built-in IDs and add new IDs while every untouched built-in remains available.
39
+ A kit supplies three things together:
40
+
41
+ 1. React components for the player;
42
+ 2. advertised template capabilities for protocol negotiation;
43
+ 3. an LLM catalog describing when and how each template may be used.
44
+
45
+ The planner cannot legitimately select a template outside the active kit.
46
+
47
+ ## Planner
48
+
49
+ The server-side planner turns the input into small plan parts such as
50
+ `scene.add`, `scene.patch`, `asset.patch`, and `plan.complete`. A planner may be
51
+ recorded, deterministic, backed by OpenAI or Anthropic, or implemented with
52
+ another streaming text model.
53
+
54
+ The planner does not create public event IDs, checksums, revisions, or final
55
+ snapshots. The runtime owns those guarantees.
56
+
57
+ | Boundary | Type | Owner |
58
+ | --- | --- | --- |
59
+ | Planner parts | Internal validated plan data | Server/provider adapter |
60
+ | Values read from `response.stream` | `VideoEvent` | SDK runtime |
61
+ | Terminal editable result | `Video` via `response.result` | SDK reducer |
62
+
63
+ A planner must never yield `VideoEvent` envelopes. It yields plan
64
+ parts; the runtime validates them and creates the public event metadata.
65
+
66
+ ## Event stream
67
+
68
+ The runtime emits ordered protocol events:
69
+
70
+ ```text
71
+ response.start
72
+ audio.set optional, before any scene
73
+ scene.add supplied opening
74
+ scene.add generated body
75
+ scene.add generated body
76
+ response.complete exact terminal snapshot
77
+ ```
78
+
79
+ Network transport uses SSE. Direct in-process integrations expose the same
80
+ events as an async iterable.
81
+
82
+ ## Playback and buffering
83
+
84
+ The player renders the first committed scene and continues through the known
85
+ timeline. Audio can loop during a generation gap. A media-bearing scene should
86
+ not be committed until its asset is ready; start generated content with a
87
+ typography-led scene so useful playback does not wait on media lookup.
88
+
89
+ Played scenes cannot be patched. This keeps playback deterministic and avoids a
90
+ scene changing after the viewer already saw it.
91
+
92
+ The same player accepts a completed value directly. After persisting the JSON,
93
+ load it through `parseVideo(JSON.parse(storedJson))`, then render
94
+ `<VideoPlayer video={savedVideo} />` to replay it without opening a stream or
95
+ calling the model again. Supply `templates` only when that video uses
96
+ customer-owned templates. See [Persistence and replay](persistence.md) for the
97
+ complete storage contract.
98
+
99
+ ## Completion snapshot
100
+
101
+ `response.complete` contains the exact reduced `Video` plus a
102
+ checksum. Persist the snapshot when you need replay, resume, export, analytics,
103
+ or an editing handoff. The event log can also be persisted and revalidated.
104
+
105
+ Read the complete [Protocol 0.4](reference/protocol.md) and the separate
106
+ [persisted Video 0.1 contract](persistence.md).
@@ -0,0 +1,329 @@
1
+ [← Documentation home](../README.md) · [Previous: Branding and personalization](branding-and-personalization.md) · [Next: Motion and effects →](motion-and-effects.md)
2
+
3
+ # Custom templates
4
+
5
+ VanillaSky turns context into a visual response. Your AI determines what matters
6
+ from the available context; a template defines how one kind of grounded answer
7
+ appears in motion. Custom templates let your application own that visual
8
+ vocabulary without letting the model generate React, HTML, or CSS.
9
+
10
+ You edit one source file. VanillaSky derives the model-facing description,
11
+ server validation, browser registry, and TypeScript variable shape from it.
12
+
13
+ ## The shortest path
14
+
15
+ The playback and server package does not install a TypeScript compiler. Install
16
+ the optional compiler only for source-owned templates and the template
17
+ ownership commands below:
18
+
19
+ ```bash
20
+ npm install --save-dev tsx
21
+ ```
22
+
23
+ List the effective catalog, including project-owned templates, before choosing
24
+ what to build or copy:
25
+
26
+ ```bash
27
+ npx vanillasky list
28
+ ```
29
+
30
+ Create an original template:
31
+
32
+ ```bash
33
+ npx vanillasky create customer-health
34
+ npx vanillasky describe customer-health
35
+ ```
36
+
37
+ Or copy a built-in when its behavior is already close:
38
+
39
+ ```bash
40
+ npx vanillasky add bigNumber
41
+ ```
42
+
43
+ Then edit the owned `.tsx` file, regenerate the two small registries, and check
44
+ the complete contract:
45
+
46
+ ```bash
47
+ npx vanillasky sync
48
+ npx vanillasky check
49
+ ```
50
+
51
+ For an original template, the source is
52
+ `vanillasky/templates/customer-health.tsx`. For the copied built-in, it is
53
+ `vanillasky/templates/bigNumber.tsx`. These are application source: commit them,
54
+ review them, and change them like any other React component.
55
+
56
+ `create` and `add` run `sync` once, so the generated entry points exist
57
+ immediately. Run `sync` again after every source edit. Run `check` before every
58
+ commit. `check` validates metadata, schema/default validity, named examples, deterministic
59
+ renders at progress boundaries in portrait and landscape, and browser/server
60
+ registry parity.
61
+
62
+ Preview either operation without applying the proposed file writes:
63
+
64
+ ```bash
65
+ npx vanillasky add bigNumber --dry-run
66
+ npx vanillasky add bigNumber --diff
67
+ ```
68
+
69
+ `--dry-run` lists every proposed file and `--diff` shows its content changes,
70
+ including the generated browser and server registries. The CLI does not apply
71
+ any proposed file write. When project templates already exist, these previews
72
+ execute that trusted application source to derive the complete registry plan.
73
+ Because trusted project source can have its own side effects, preview execution is
74
+ resource-bounded but is not a sandbox and does not guarantee that the entire
75
+ project remains byte-identical. Review project template code before running
76
+ preview commands.
77
+
78
+ An edited file is never replaced unless you explicitly pass `--overwrite`.
79
+
80
+ ## One file is the contract
81
+
82
+ The file created by `vanillasky create` is a complete working template. Keep
83
+ these concerns together:
84
+
85
+ - `useWhen` and `avoidWhen` tell the AI when the visual is appropriate;
86
+ - `schema` defines allowed variables, validation, defaults, labels, and
87
+ grounding formats;
88
+ - named `examples` provide complete deterministic preview values;
89
+ - `component` receives only validated variables and render context;
90
+ - raw `progress`, dimensions, and `safeZone` make semantic state deterministic
91
+ and layout safe in portrait and landscape.
92
+
93
+ Customer templates hard-cut by default. Opt into renderer-owned fades only
94
+ for scenes that use the standard media-background variables, and only after
95
+ both timing points are visually tested in portrait and landscape. Shared brand
96
+ gradients and unchanged media do not crossfade:
97
+
98
+ ```tsx
99
+ export default defineTemplate({
100
+ // ...the normal one-file contract
101
+ usesGlobalTransition: true,
102
+ transitionTiming: { entryReadyProgress: 0.2, holdProgress: 0.7 },
103
+ component: ({ progress, motionProgress = progress, ...props }) => {
104
+ // Both clocks cover the complete declared 0→1 scene duration.
105
+ // Use progress for facts/media/screens; motionProgress for presentation.
106
+ return <CustomerScene {...props} dataProgress={progress} motionProgress={motionProgress} />;
107
+ },
108
+ });
109
+ ```
110
+
111
+ `entryReadyProgress` must show recognizable content in verification captures.
112
+ `holdProgress` is the audited complete-readable checkpoint before the
113
+ template's own exit begins. These values document and test the choreography;
114
+ they never advance or shorten the runtime clock. The SDK validates
115
+ `0 <= entryReadyProgress < holdProgress <= 1`.
116
+
117
+ When changed media crossfades, the incoming layer can be partly visible while
118
+ raw `progress` is exactly zero. A custom template that counts or types a value
119
+ from a synthetic placeholder must hide only that transient wrapper during the
120
+ crossfade. Compare the formatted display value to the formatted final value so
121
+ a sourced zero is not mistaken for a placeholder:
122
+
123
+ <!-- verify:transition-semantic-value:start -->
124
+ ```tsx
125
+ import type { CSSProperties } from "react";
126
+
127
+ interface TransitionSemanticValueProps {
128
+ displayValue: string;
129
+ finalValue: string;
130
+ }
131
+
132
+ export function TransitionSemanticValue({
133
+ displayValue,
134
+ finalValue,
135
+ }: TransitionSemanticValueProps) {
136
+ const isTransient = displayValue !== finalValue;
137
+
138
+ return <span
139
+ data-transition-semantic={isTransient ? "transient" : undefined}
140
+ style={isTransient ? {
141
+ visibility: "var(--vanillasky-transition-semantic-visibility, visible)" as CSSProperties["visibility"],
142
+ } : undefined}
143
+ >
144
+ {displayValue}
145
+ </span>;
146
+ }
147
+ ```
148
+ <!-- verify:transition-semantic-value:end -->
149
+
150
+ The variable defaults to `visible`, so direct rendering and hard cuts are
151
+ unchanged. Do not apply it to grounded headings, CTA copy, supplied media, or
152
+ the whole scene: those must remain recognizable at `entryReadyProgress`.
153
+
154
+ The only SDK import an original one-file template needs is public:
155
+
156
+ ```tsx
157
+ import { defineTemplate } from "@vanillaskyai/video/templates";
158
+ ```
159
+
160
+ Start from the generated file or one of the packaged references:
161
+
162
+ | Content shape | Reference |
163
+ | --- | --- |
164
+ | One headline and supporting line | [Minimal text](../examples/custom-template/minimal-text.tsx) |
165
+ | One exact metric and its change | [Structured data](../examples/custom-template/structured-data.tsx) |
166
+ | An application-supplied image | [Supplied media](../examples/custom-template/supplied-media.tsx) |
167
+
168
+ The [reference comparison](../examples/custom-template/README.md) explains when
169
+ to choose each. Copy a file into `vanillasky/templates/`, change its ID and
170
+ content contract, then run `sync` and `check`.
171
+
172
+ ## Connect the generated registries
173
+
174
+ `sync` generates two files from the owned templates:
175
+
176
+ ```text
177
+ vanillasky/
178
+ ├── templates/
179
+ │ └── customer-health.tsx # source you edit
180
+ ├── index.ts # browser registry, generated
181
+ └── server.ts # React-free model/validation registry, generated
182
+ ```
183
+
184
+ The server registry connects custom template metadata to the prompt, model
185
+ selection, and validator. Import it in the route that connects your model:
186
+
187
+ ```ts
188
+ // src/video-route.ts
189
+ import { openai } from "@ai-sdk/openai";
190
+ import { streamText } from "ai";
191
+ import { createVideoHandler } from "@vanillaskyai/video/server";
192
+ import { templates } from "../vanillasky/server";
193
+
194
+ export const handleVideo = createVideoHandler({
195
+ templates,
196
+ authorize: (request) => {
197
+ if (process.env.NODE_ENV !== "development") return false;
198
+ const hostname = new URL(request.url).hostname;
199
+ return hostname === "localhost" || hostname === "127.0.0.1";
200
+ },
201
+ streamText: ({ systemPrompt, userPrompt, signal }) => streamText({
202
+ model: openai(process.env.OPENAI_MODEL ?? "gpt-4.1"),
203
+ system: systemPrompt,
204
+ prompt: userPrompt,
205
+ abortSignal: signal,
206
+ }),
207
+ });
208
+ ```
209
+
210
+ The local-only authorization above is intentionally narrow. Replace it with
211
+ your application's session check before deployment, as shown in
212
+ [Getting started](getting-started.md).
213
+
214
+ Use the browser registry for generation and playback:
215
+
216
+ ```tsx
217
+ // src/video-composer.tsx
218
+ import { VideoPlayer, useVideo } from "@vanillaskyai/video/react";
219
+ import { templates } from "../vanillasky";
220
+
221
+ export function VideoComposer() {
222
+ const video = useVideo({ templates });
223
+
224
+ return <>
225
+ <button onClick={() => void video.generate({ input: "Grounded answer" })}>
226
+ Generate
227
+ </button>
228
+ <VideoPlayer {...video.playerProps} />
229
+ </>;
230
+ }
231
+ ```
232
+
233
+ Adjust relative paths for your application layout. Do not edit `index.ts` or
234
+ `server.ts`; `sync` replaces them. A matching ID replaces that built-in on both
235
+ sides, a new ID extends the catalog, and every other built-in remains available.
236
+
237
+ ## Preview without an LLM
238
+
239
+ Template rendering is deterministic. Preview a saved `Video` directly in your
240
+ application so iteration uses your actual CSS, fonts, container, React version,
241
+ and browser. This path calls no model and needs no server route:
242
+
243
+ <!-- verify:custom-template-preview:start -->
244
+ ```tsx
245
+ import type { Video } from "@vanillaskyai/video";
246
+ import { VideoPlayer } from "@vanillaskyai/video/react";
247
+ import { templates } from "../vanillasky";
248
+
249
+ const savedVideo: Video = {
250
+ schemaVersion: "0.1",
251
+ orientation: "portrait",
252
+ scenes: [{
253
+ id: "customer-health-preview",
254
+ templateId: "customer-health",
255
+ variables: {
256
+ title: "Customer health is improving",
257
+ subtitle: "Activation increased after guided onboarding.",
258
+ },
259
+ timing: { fixedDuration: 5 },
260
+ }],
261
+ style: {
262
+ brand: {
263
+ font: "Inter",
264
+ scriptFont: "Caveat",
265
+ background: { type: "gradient", colors: ["#8711C1", "#2167E3"] },
266
+ colors: {
267
+ primary: "#00E5A0",
268
+ secondary: "#006BE5",
269
+ foreground: "#FFFFFF",
270
+ surface: "#0A0A14",
271
+ surfaceElevated: "#14152A",
272
+ muted: "#A7A6B0",
273
+ },
274
+ },
275
+ },
276
+ };
277
+
278
+ export function TemplatePreview() {
279
+ return <VideoPlayer video={savedVideo} templates={templates} autoPlay={false} />;
280
+ }
281
+ ```
282
+ <!-- verify:custom-template-preview:end -->
283
+
284
+ Change the saved variables to each named example, inspect both orientations,
285
+ and play the scene to inspect its full progress range. This is also the
286
+ production replay path for a completed video saved from
287
+ `await video.generate(...)`.
288
+
289
+ ## Schema and grounding
290
+
291
+ Use JSON Schema as the single source of truth.
292
+
293
+ Property defaults are optional authoring and renderer smoke values; they do not
294
+ need to form a complete scene. Named examples must resolve to complete valid
295
+ scenes. They may inherit omitted values from property defaults. Never invent
296
+ media URLs or actions just to supply a default. Put complete scene examples in
297
+ the top-level `examples` array, and do not maintain parallel variable or default
298
+ maps.
299
+
300
+ Useful formats add grounding behavior:
301
+
302
+ - `grounded-stat` marks numeric statistical evidence for the planner; it does
303
+ not compare the value against raw input at runtime;
304
+ - `grounded-quote` requires the quote to exist verbatim in the input;
305
+ - `supplied-image` requires an image URL listed in `VideoInput.suppliedMedia`;
306
+ - `uri` applies the supplied-media or server URL policy to an approved URL;
307
+ - `stock-media-keyword` is only for hosts that resolve stock media before a
308
+ scene is committed.
309
+
310
+ Templates whose core proof needs a real statistic can add
311
+ `"x-vanillasky": { "requiresStat": true }`. See the structured-data and
312
+ supplied-media references for complete examples.
313
+
314
+ The automatic `opening` uses the built-in `notification` variables (`appName`
315
+ and `message`). If you replace `notification` while using automatic openings,
316
+ keep that variable contract.
317
+
318
+ ## What belongs where
319
+
320
+ Your application owns the model, credentials, live context, custom template
321
+ source, authentication, media authorization, and saved `Video` values.
322
+ VanillaSky owns the generated planning instructions, streaming protocol,
323
+ runtime validation, built-in template catalog, and player. The model selects a
324
+ trusted template and fills its declared schema; it never writes executable UI
325
+ code.
326
+
327
+ For the exact request path and source locations, see
328
+ [Architecture](architecture.md). For media policies and supplied-media input,
329
+ see [Media and audio](media-and-audio.md).
@@ -0,0 +1,141 @@
1
+ [← Documentation home](../README.md)
2
+
3
+ # Customization
4
+
5
+ ## Background and semantic brand
6
+
7
+ Omit brand configuration to use the standard `cosmic` background. Prefer a
8
+ named curated choice over raw color work:
9
+
10
+ ```ts
11
+ const brand = {
12
+ name: "Acme",
13
+ logoUrl: "https://cdn.acme.com/logo.svg",
14
+ font: "Inter",
15
+ scriptFont: "Caveat",
16
+ background: "twilight",
17
+ colors: {
18
+ primary: "#6D5EF5",
19
+ secondary: "#3D2A78",
20
+ foreground: "#FFFFFF",
21
+ surface: "#17122F",
22
+ surfaceElevated: "#231B42",
23
+ muted: "#A7A6B0",
24
+ },
25
+ };
26
+ ```
27
+
28
+ Gradient presets: `cosmic`, `horizon`, `twilight`, `meadow`, `velvet`,
29
+ `flamingo`, `peach`, `saffron`. Solid presets: `black`, `midnight`,
30
+ `aubergine`, `coal`, `navy`. When a named choice genuinely cannot express the
31
+ brand, use `{ colors: ["#112233", "#334455"] }` for a custom gradient or
32
+ `{ color: "#070B20" }` for a custom solid.
33
+
34
+ `colors` may be partial; the resolver fills every semantic token before the
35
+ video is emitted. With no foreground, named and custom backgrounds
36
+ deterministically select black or white for at least 4.5:1 contrast across the
37
+ full rendered sRGB ramp, including gradient interiors. A ramp that neither can
38
+ cover is rejected. An explicit foreground is preserved and validated by the
39
+ same invariant during input resolution and replay; low-contrast values are
40
+ rejected with the failing path and minimum ratio. Elevated surfaces derive an
41
+ accessible internal text treatment without changing the semantic foreground.
42
+ Use an approved public or signed URL for logos, and never put a private storage
43
+ credential in the config.
44
+
45
+ ## Global visual direction
46
+
47
+ Leave visual direction unset to use VanillaSky's defaults, or set one coherent
48
+ look for the completed video:
49
+
50
+ ```ts
51
+ style: {
52
+ density: "airy", // airy | normal | packed
53
+ motion: "calm", // calm | normal | punchy
54
+ textArchetype: "cinematic",
55
+ backgroundEffect: "slow-zoom-out",
56
+ }
57
+ ```
58
+
59
+ These are defaults, not generated CSS. A validated scene may still select a
60
+ more appropriate text or background treatment when its trusted template allows
61
+ it.
62
+
63
+ ## Personalization
64
+
65
+ `personalization` accepts JSON-safe application fields. Include only values the
66
+ viewer is allowed to see. The system prompt treats them as context, not as
67
+ instructions.
68
+
69
+ Good fields include `firstName`, `role`, `accountName`, `period`, `goal`,
70
+ `locale`, and `onboardingPartner`. Keep facts in `input` as well when they must
71
+ appear in the story.
72
+
73
+ ## Opening
74
+
75
+ `VideoPlayer` automatically shows a brand-colored generation cover until the
76
+ first validated scene arrives. The cover is player state, not video content: it
77
+ is never written to the event log, replay, or export.
78
+
79
+ An optional opening is deterministic and should not wait for an LLM or remote media:
80
+
81
+ ```ts
82
+ opening: "Joris, your Q2 recap is ready."
83
+ ```
84
+
85
+ Use `opening` only for a genuine opening that should remain in the completed
86
+ response. VanillaSky infers the scene ID, `notification` template, variables,
87
+ and five-second timing. You do not need to create a generic loading scene.
88
+
89
+ ## Aspect ratio and responsive layout
90
+
91
+ The player is responsive by default: it fills its container width. Templates
92
+ and copy must work at either aspect ratio; orientation is not an AI-planning
93
+ input and must not influence the selected templates or wording.
94
+
95
+ `portrait` reserves a 9:16 response/export frame and `landscape` reserves 16:9.
96
+ This input setting remains stable in the completed config. For an embed that
97
+ should display landscape on desktop and portrait on mobile without changing the
98
+ saved response, pass `orientation="auto"` to `VideoPlayer`; it responds
99
+ to its container width. See [responsive orientation](responsive-orientation.md).
100
+
101
+ ## Supplied media
102
+
103
+ Provide approved media with semantic descriptions:
104
+
105
+ ```ts
106
+ suppliedMedia: [{
107
+ id: "product-dashboard",
108
+ url: "https://cdn.example.com/dashboard.png",
109
+ type: "image",
110
+ description: "Activation dashboard after the Q2 release",
111
+ role: "product",
112
+ }]
113
+ ```
114
+
115
+ Resolve searched media before generation and pass the approved result through
116
+ `suppliedMedia`. See [media providers](media-and-audio.md#media-providers).
117
+
118
+ ## Soundtrack audio
119
+
120
+ Pass `audio: { src }` for a specific track, omit `audio` to let the server choose
121
+ synchronously from a preloaded catalog, or pass `audio: false` for silence.
122
+ VanillaSky infers deterministic duration, volume, beat, and fade-out metadata.
123
+ The soundtrack should continue across visual generation gaps and finish with
124
+ the final scene. Narration, TTS, and speech synchronization are not part of the
125
+ 0.1 SDK contract.
126
+
127
+ ## Custom templates
128
+
129
+ The built-in catalog needs no setup. Only source-owned templates need the
130
+ optional local TSX compiler; install it once with `npm install --save-dev tsx`.
131
+ Then use `npx vanillasky create <id>` for an original one-file template or
132
+ `npx vanillasky add <builtin>` to copy a close built-in. Edit the owned file,
133
+ run `npx vanillasky sync`, then run `npx vanillasky check` before committing.
134
+ Pass the generated registry to the server and browser; project-owned IDs
135
+ replace matching built-ins and new IDs extend the catalog.
136
+
137
+ The model sees selection guidance and a schema, not component source. It chooses
138
+ a trusted template and fills validated variables. Never evaluate model-authored
139
+ React, HTML, CSS, or JavaScript on the live path.
140
+
141
+ See [custom templates](custom-templates.md).