@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.
- package/CHANGELOG.md +45 -0
- package/LICENSE +201 -0
- package/PUBLIC-API.md +276 -0
- package/README.md +152 -0
- package/SECURITY.md +22 -0
- package/SUPPORT.md +28 -0
- package/bin/vanillasky.js +3 -0
- package/dist/bg-confetti-WNQXS7ZS.js +89 -0
- package/dist/bg-emoji-ETI47GLS.js +150 -0
- package/dist/bg-media-SVUZAMGX.js +99 -0
- package/dist/brand-message-F3RD4P3P.js +253 -0
- package/dist/builtin-server-F7YXRVCH.js +8 -0
- package/dist/catalog-types-BIhSpOWK.d.ts +74 -0
- package/dist/chart-bar-CNSC7BNK.js +304 -0
- package/dist/chart-counter-MCRHKN77.js +299 -0
- package/dist/chart-progress-ring-RMI4A7ZK.js +296 -0
- package/dist/check-runtime.d.ts +15 -0
- package/dist/check-runtime.js +102 -0
- package/dist/chunk-2E6T633S.js +27 -0
- package/dist/chunk-352ZZCET.js +83 -0
- package/dist/chunk-73NTSFFI.js +81 -0
- package/dist/chunk-A2X45FET.js +46 -0
- package/dist/chunk-ABPKM4GK.js +187 -0
- package/dist/chunk-ABZACD4G.js +216 -0
- package/dist/chunk-BCRUUJ6A.js +756 -0
- package/dist/chunk-CHLGW22F.js +3551 -0
- package/dist/chunk-CJ7F2CE6.js +104 -0
- package/dist/chunk-CR7FE7BW.js +109 -0
- package/dist/chunk-E7CL7UPB.js +109 -0
- package/dist/chunk-EE6PK6XC.js +18 -0
- package/dist/chunk-ERY4S6QV.js +385 -0
- package/dist/chunk-EVG2ZYIY.js +125 -0
- package/dist/chunk-FNAEQ7QO.js +155 -0
- package/dist/chunk-FQ47WXAJ.js +38 -0
- package/dist/chunk-FSG2PYWG.js +336 -0
- package/dist/chunk-GHJEAP4O.js +504 -0
- package/dist/chunk-GRMUV7QY.js +262 -0
- package/dist/chunk-IFOW65Z6.js +205 -0
- package/dist/chunk-IXCUY3B3.js +786 -0
- package/dist/chunk-L5BTTCZV.js +97 -0
- package/dist/chunk-LMG3GHLL.js +107 -0
- package/dist/chunk-OOPIETLW.js +101 -0
- package/dist/chunk-P22HHFDK.js +43 -0
- package/dist/chunk-QAAO7OAY.js +19 -0
- package/dist/chunk-S7HFLYNN.js +35 -0
- package/dist/chunk-SPVTJH3F.js +24 -0
- package/dist/chunk-W2XJKG6C.js +3177 -0
- package/dist/chunk-XB7LH4J2.js +301 -0
- package/dist/chunk-XGRA2MUV.js +242 -0
- package/dist/chunk-XTOXFMLM.js +105 -0
- package/dist/chunk-YC5MFXA5.js +132 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1681 -0
- package/dist/compose-video-CN3VSWST.js +13 -0
- package/dist/cta-logo-SVA5GP2A.js +209 -0
- package/dist/cta-media-E7VLLK42.js +215 -0
- package/dist/events-_wfjbmEp.d.ts +81 -0
- package/dist/incoming-call-62MFZL3Z.js +320 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/infographic-before-after-AXCDAWND.js +460 -0
- package/dist/infographic-feature-list-GW5GUE6O.js +266 -0
- package/dist/infographic-problem-solution-7NURJANA.js +340 -0
- package/dist/infographic-stat-row-J7IZR6ZN.js +241 -0
- package/dist/infographic-steps-VSZM6F5M.js +400 -0
- package/dist/kit-BMNcHqa9.d.ts +79 -0
- package/dist/prompt-input-LOUCRXCY.js +236 -0
- package/dist/react.d.ts +81 -0
- package/dist/react.js +835 -0
- package/dist/reaction-QG4CYZLQ.js +119 -0
- package/dist/server.d.ts +141 -0
- package/dist/server.js +498 -0
- package/dist/showcase-code-RUTFZR3H.js +351 -0
- package/dist/showcase-phone-6CP6XEU5.js +272 -0
- package/dist/showcase-terminal-MGAEQWPR.js +306 -0
- package/dist/showcase-web-Y4W2HVRJ.js +390 -0
- package/dist/social-conversation-75SD2637.js +695 -0
- package/dist/social-milestone-XI2QE5BO.js +292 -0
- package/dist/social-notification-2UOHQCUY.js +244 -0
- package/dist/social-review-stack-FDYZGZTJ.js +239 -0
- package/dist/social-testimonial-OQ7N52GJ.js +284 -0
- package/dist/social-tweet-5OLCNPPC.js +295 -0
- package/dist/state-DZcKuS32.d.ts +3 -0
- package/dist/system-prompt-A6UXNTPK.js +8 -0
- package/dist/template-catalog.d.ts +24 -0
- package/dist/template-catalog.js +6 -0
- package/dist/templates.d.ts +25 -0
- package/dist/templates.js +19 -0
- package/dist/test.d.ts +149 -0
- package/dist/test.js +272 -0
- package/dist/text-stream-6XHC7J47.js +10 -0
- package/dist/types-B-PZR6G_.d.ts +208 -0
- package/dist/validate-T7GBU2YF.js +8 -0
- package/docs/agent-integration.md +39 -0
- package/docs/architecture.md +84 -0
- package/docs/branding-and-personalization.md +87 -0
- package/docs/concepts.md +106 -0
- package/docs/custom-templates.md +329 -0
- package/docs/customization.md +141 -0
- package/docs/errors.md +70 -0
- package/docs/getting-started.md +111 -0
- package/docs/input-and-first-scene.md +41 -0
- package/docs/integrate-nextjs.md +79 -0
- package/docs/media-and-audio.md +69 -0
- package/docs/motion-and-effects.md +107 -0
- package/docs/persistence.md +95 -0
- package/docs/production.md +122 -0
- package/docs/prompt-and-input.md +185 -0
- package/docs/provider-integration.md +115 -0
- package/docs/reference/protocol.md +96 -0
- package/docs/reference/provider-adapters.md +158 -0
- package/docs/responsive-orientation.md +37 -0
- package/docs/security.md +38 -0
- package/docs/streaming-protocol.md +15 -0
- package/docs/testing.md +137 -0
- package/docs/use-cases.md +59 -0
- package/examples/custom-template/README.md +19 -0
- package/examples/custom-template/minimal-text.tsx +82 -0
- package/examples/custom-template/structured-data.tsx +104 -0
- package/examples/custom-template/supplied-media.tsx +92 -0
- package/examples/nextjs-quickstart/.env.example +2 -0
- package/examples/nextjs-quickstart/README.md +22 -0
- package/examples/nextjs-quickstart/next-env.d.ts +4 -0
- package/examples/nextjs-quickstart/package.json +24 -0
- package/examples/nextjs-quickstart/src/app/api/video/route.ts +22 -0
- package/examples/nextjs-quickstart/src/app/layout.tsx +5 -0
- package/examples/nextjs-quickstart/src/app/page.tsx +31 -0
- package/examples/nextjs-quickstart/tsconfig.json +26 -0
- package/package.json +167 -0
- package/registry/items/backgrounds.json +52 -0
- package/registry/items/barChart.json +237 -0
- package/registry/items/beforeAfter.json +180 -0
- package/registry/items/bigNumber.json +225 -0
- package/registry/items/brandMessage.json +158 -0
- package/registry/items/cardList.json +206 -0
- package/registry/items/chatMessenger.json +151 -0
- package/registry/items/chatWhatsapp.json +151 -0
- package/registry/items/codeEditor.json +184 -0
- package/registry/items/confetti.json +107 -0
- package/registry/items/ctaLogo.json +160 -0
- package/registry/items/ctaMedia.json +193 -0
- package/registry/items/emojiBurst.json +101 -0
- package/registry/items/incomingCall.json +170 -0
- package/registry/items/media.json +179 -0
- package/registry/items/milestone.json +205 -0
- package/registry/items/motion.json +45 -0
- package/registry/items/notification.json +182 -0
- package/registry/items/phoneMockup.json +254 -0
- package/registry/items/problemSolution.json +186 -0
- package/registry/items/progressRing.json +199 -0
- package/registry/items/promptInput.json +148 -0
- package/registry/items/reaction.json +170 -0
- package/registry/items/reviewStack.json +224 -0
- package/registry/items/steps.json +207 -0
- package/registry/items/template-context.json +31 -0
- package/registry/items/terminal.json +212 -0
- package/registry/items/testimonial.json +164 -0
- package/registry/items/theme.json +47 -0
- package/registry/items/tripleStats.json +228 -0
- package/registry/items/tweet.json +203 -0
- package/registry/items/typography.json +47 -0
- package/registry/items/webMockup.json +273 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { VideoPlayer, useVideo } from "@vanillaskyai/video/react";
|
|
5
|
+
|
|
6
|
+
export default function Page() {
|
|
7
|
+
const [input, setInput] = useState(
|
|
8
|
+
"Activation increased from 41% to 58% after guided onboarding.",
|
|
9
|
+
);
|
|
10
|
+
const video = useVideo();
|
|
11
|
+
|
|
12
|
+
return <main>
|
|
13
|
+
<h1>VanillaSky quickstart</h1>
|
|
14
|
+
<label>
|
|
15
|
+
Grounded input
|
|
16
|
+
<textarea value={input} onChange={(event) => setInput(event.target.value)} />
|
|
17
|
+
</label>
|
|
18
|
+
<button
|
|
19
|
+
disabled={!input.trim() || video.status === "streaming"}
|
|
20
|
+
onClick={() => { void video.generate({
|
|
21
|
+
input,
|
|
22
|
+
personalization: { firstName: "Maya" },
|
|
23
|
+
}); }}
|
|
24
|
+
>
|
|
25
|
+
Generate video
|
|
26
|
+
</button>
|
|
27
|
+
<output data-testid="status">{video.status}</output>
|
|
28
|
+
{video.error && <p role="alert">{video.error.message}</p>}
|
|
29
|
+
<VideoPlayer {...video.playerProps} />
|
|
30
|
+
</main>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"incremental": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"plugins": [{ "name": "next" }]
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"next-env.d.ts",
|
|
20
|
+
"**/*.ts",
|
|
21
|
+
"**/*.tsx",
|
|
22
|
+
".next/types/**/*.ts",
|
|
23
|
+
".next/dev/types/**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": ["node_modules"]
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vanillaskyai/video",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Open-source video response SDK for personalized AI applications.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"generative-video",
|
|
7
|
+
"personalized-video",
|
|
8
|
+
"video-response",
|
|
9
|
+
"ai-output",
|
|
10
|
+
"contextual-ai",
|
|
11
|
+
"streaming",
|
|
12
|
+
"react",
|
|
13
|
+
"llm",
|
|
14
|
+
"video"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"license": "Apache-2.0",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/VanillaSkyAi/video.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/VanillaSkyAi/video/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://vanillasky.ai",
|
|
27
|
+
"files": [
|
|
28
|
+
"bin",
|
|
29
|
+
"dist",
|
|
30
|
+
"registry/items",
|
|
31
|
+
"examples/custom-template",
|
|
32
|
+
"examples/nextjs-quickstart/.env.example",
|
|
33
|
+
"examples/nextjs-quickstart/README.md",
|
|
34
|
+
"examples/nextjs-quickstart/next-env.d.ts",
|
|
35
|
+
"examples/nextjs-quickstart/package.json",
|
|
36
|
+
"examples/nextjs-quickstart/src",
|
|
37
|
+
"examples/nextjs-quickstart/tsconfig.json",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"SECURITY.md",
|
|
41
|
+
"SUPPORT.md",
|
|
42
|
+
"PUBLIC-API.md",
|
|
43
|
+
"docs/reference/provider-adapters.md",
|
|
44
|
+
"docs/reference/protocol.md",
|
|
45
|
+
"CHANGELOG.md",
|
|
46
|
+
"docs/*.md",
|
|
47
|
+
"docs/reference"
|
|
48
|
+
],
|
|
49
|
+
"bin": {
|
|
50
|
+
"vanillasky": "bin/vanillasky.js"
|
|
51
|
+
},
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"types": "./dist/index.d.ts",
|
|
55
|
+
"import": "./dist/index.js",
|
|
56
|
+
"default": "./dist/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./server": {
|
|
59
|
+
"types": "./dist/server.d.ts",
|
|
60
|
+
"import": "./dist/server.js",
|
|
61
|
+
"default": "./dist/server.js"
|
|
62
|
+
},
|
|
63
|
+
"./react": {
|
|
64
|
+
"types": "./dist/react.d.ts",
|
|
65
|
+
"import": "./dist/react.js",
|
|
66
|
+
"default": "./dist/react.js"
|
|
67
|
+
},
|
|
68
|
+
"./templates": {
|
|
69
|
+
"types": "./dist/templates.d.ts",
|
|
70
|
+
"import": "./dist/templates.js",
|
|
71
|
+
"default": "./dist/templates.js"
|
|
72
|
+
},
|
|
73
|
+
"./templates/catalog": {
|
|
74
|
+
"types": "./dist/template-catalog.d.ts",
|
|
75
|
+
"import": "./dist/template-catalog.js",
|
|
76
|
+
"default": "./dist/template-catalog.js"
|
|
77
|
+
},
|
|
78
|
+
"./test": {
|
|
79
|
+
"types": "./dist/test.d.ts",
|
|
80
|
+
"import": "./dist/test.js",
|
|
81
|
+
"default": "./dist/test.js"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"acceptance:live": "tsx scripts/acceptance/run-live.ts",
|
|
86
|
+
"acceptance:review": "tsx scripts/acceptance/review.ts",
|
|
87
|
+
"acceptance:replay": "tsx scripts/acceptance/run-replay.ts",
|
|
88
|
+
"browser:install": "playwright install chromium firefox webkit",
|
|
89
|
+
"browser:test": "playwright test",
|
|
90
|
+
"build": "tsup",
|
|
91
|
+
"example:install": "npm run examples:install-current",
|
|
92
|
+
"example:build": "npm run build --prefix examples/react-vite",
|
|
93
|
+
"example:dev": "npm run dev --prefix examples/react-vite",
|
|
94
|
+
"example:preview": "npm run preview --prefix examples/react-vite -- --host 127.0.0.1 --port 4187 --strictPort",
|
|
95
|
+
"examples:verify-documented": "node scripts/verify-documented-examples.mjs",
|
|
96
|
+
"examples:install-current": "npm run build && node scripts/install-current-examples.mjs",
|
|
97
|
+
"server-examples:typecheck": "npm run typecheck --prefix examples/server-integrations",
|
|
98
|
+
"server-examples:compat": "npm run ai-sdk:compat --prefix examples/server-integrations",
|
|
99
|
+
"lint": "eslint .",
|
|
100
|
+
"catalog:sync": "tsx scripts/generate-builtin-catalog.ts",
|
|
101
|
+
"catalog:check": "tsx scripts/generate-builtin-catalog.ts --check",
|
|
102
|
+
"registry:sync": "npm run catalog:sync && node scripts/sync-template-registry.mjs",
|
|
103
|
+
"registry:check": "node scripts/sync-template-registry.mjs --check && npm run catalog:check",
|
|
104
|
+
"template:check": "tsx scripts/template-check.ts",
|
|
105
|
+
"test": "vitest run",
|
|
106
|
+
"test:watch": "vitest",
|
|
107
|
+
"typecheck": "tsc --noEmit",
|
|
108
|
+
"prepack": "npm run build",
|
|
109
|
+
"verify:onboarding": "node scripts/verify-onboarding.mjs",
|
|
110
|
+
"verify:nextjs": "node scripts/verify-nextjs-onboarding.mjs",
|
|
111
|
+
"verify:published": "node scripts/verify-published-package.mjs",
|
|
112
|
+
"verify:package-size": "tsx scripts/verify-package-size.ts",
|
|
113
|
+
"verify:api": "npm run build && node scripts/verify-public-api-surface.mjs",
|
|
114
|
+
"verify:package": "npm run build && node scripts/verify-packed-package.mjs",
|
|
115
|
+
"release:dry-run": "node scripts/release-dry-run.mjs",
|
|
116
|
+
"release:preflight": "node scripts/release-preflight.mjs",
|
|
117
|
+
"release:check": "npm run verify:api && npm run release:dry-run"
|
|
118
|
+
},
|
|
119
|
+
"peerDependencies": {
|
|
120
|
+
"react": ">=18 <20",
|
|
121
|
+
"react-dom": ">=18 <20",
|
|
122
|
+
"tsx": ">=4.19 <5"
|
|
123
|
+
},
|
|
124
|
+
"peerDependenciesMeta": {
|
|
125
|
+
"react": {
|
|
126
|
+
"optional": true
|
|
127
|
+
},
|
|
128
|
+
"react-dom": {
|
|
129
|
+
"optional": true
|
|
130
|
+
},
|
|
131
|
+
"tsx": {
|
|
132
|
+
"optional": true
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"devDependencies": {
|
|
136
|
+
"@anthropic-ai/sdk": "^0.116.0",
|
|
137
|
+
"@eslint/js": "^9.39.1",
|
|
138
|
+
"@playwright/test": "1.62.0",
|
|
139
|
+
"@testing-library/react": "^16.3.0",
|
|
140
|
+
"@types/node": "^24.10.1",
|
|
141
|
+
"@types/react": "^18.3.28",
|
|
142
|
+
"@types/react-dom": "^18.3.7",
|
|
143
|
+
"eslint": "^9.39.1",
|
|
144
|
+
"es-module-lexer": "^1.7.0",
|
|
145
|
+
"globals": "^16.5.0",
|
|
146
|
+
"jsdom": "^26.1.0",
|
|
147
|
+
"openai": "^7.4.0",
|
|
148
|
+
"react": "^18.3.1",
|
|
149
|
+
"react-dom": "^18.3.1",
|
|
150
|
+
"tsup": "^8.5.1",
|
|
151
|
+
"typescript": "^5.9.3",
|
|
152
|
+
"typescript-eslint": "^8.48.1",
|
|
153
|
+
"tsx": "^4.23.12",
|
|
154
|
+
"vitest": "^3.2.4"
|
|
155
|
+
},
|
|
156
|
+
"overrides": {
|
|
157
|
+
"esbuild": "^0.28.2"
|
|
158
|
+
},
|
|
159
|
+
"engines": {
|
|
160
|
+
"node": ">=20"
|
|
161
|
+
},
|
|
162
|
+
"packageManager": "npm@11.17.0",
|
|
163
|
+
"publishConfig": {
|
|
164
|
+
"access": "public",
|
|
165
|
+
"provenance": true
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "backgrounds",
|
|
4
|
+
"type": "registry:lib",
|
|
5
|
+
"title": "Backgrounds",
|
|
6
|
+
"description": "Paint deterministic brand backdrops and keep their visual layer moving.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"@vanillasky/motion",
|
|
12
|
+
"@vanillasky/theme"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "src/visual-system/backgrounds/index.ts",
|
|
17
|
+
"type": "registry:lib",
|
|
18
|
+
"target": "vanillasky/backgrounds/index.ts",
|
|
19
|
+
"content": "/** Brand-safe scene backgrounds and continuous background motion. */\nexport {\n TOP_TEXT_AREA_RATIO,\n gradientBackground,\n BrandGradientOverlay,\n} from \"../scene-templates/color-utils\";\nexport * from \"../scene-templates/background-effect\";\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "src/visual-system/scene-templates/background-effect.ts",
|
|
23
|
+
"type": "registry:lib",
|
|
24
|
+
"target": "vanillasky/scene-templates/background-effect.ts",
|
|
25
|
+
"content": "/**\n * Background motion effects for templates.\n *\n * Applied to the background layer (media, gradient) throughout the entire scene.\n * Continuous motion — not tied to text timing.\n *\n * Templates with usesGlobalBackgroundEffect: true consume the video-level default.\n *\n * 9 effects, each with a distinct mood:\n * - static: No motion — intentional stillness\n * - slow-zoom-in: Draw attention inward, build focus\n * - slow-zoom-out: Reveal, establish, pull back\n * - ken-burns: Classic cinematic photo motion (zoom + pan, direction alternates per scene)\n * - drift: Gentle lateral pan (direction alternates per scene)\n * - pulse: Beat-synced energy\n * - breathe: Ambient dreamy float\n * - slow-tilt: Tension, Dutch angle drift\n * - camera-shake: Intensity, handheld drama\n */\n\nimport { interpolate } from \"../motion\";\n\nexport interface BackgroundTransform {\n transform: string;\n transformOrigin: string;\n}\n\nexport const BACKGROUND_EFFECTS = [\n \"static\",\n \"slow-zoom-in\",\n \"slow-zoom-out\",\n \"ken-burns\",\n \"drift\",\n \"pulse\",\n \"breathe\",\n \"slow-tilt\",\n \"camera-shake\",\n] as const;\n\n// ─── Directional variant helpers ────────────────────────────────\n// Ken Burns and Drift have internal direction variants that alternate\n// by scene index to create visual variety across a video.\n\ntype Direction = \"right\" | \"left\" | \"up\" | \"down\";\nconst DIRECTIONS: Direction[] = [\"right\", \"left\", \"up\", \"down\"];\n\nfunction getKenBurnsTransform(progress: number, direction: Direction): BackgroundTransform {\n const scale = interpolate(progress, [0, 1], [1.05, 1.15]);\n const mainAxis = direction === \"right\" || direction === \"left\";\n const sign = direction === \"right\" || direction === \"down\" ? 1 : -1;\n const primary = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);\n const secondary = interpolate(progress, [0, 1], [-0.5, 0.5]);\n\n const x = mainAxis ? primary : secondary;\n const y = mainAxis ? secondary : primary;\n\n return {\n transform: `scale(${scale}) translate(${x}%, ${y}%)`,\n transformOrigin: \"center\",\n };\n}\n\nfunction getDriftTransform(progress: number, direction: Direction): BackgroundTransform {\n const horizontal = direction === \"right\" || direction === \"left\";\n const sign = direction === \"right\" || direction === \"down\" ? 1 : -1;\n const offset = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);\n\n return {\n transform: horizontal\n ? `scale(1.1) translateX(${offset}%)`\n : `scale(1.1) translateY(${offset}%)`,\n transformOrigin: \"center\",\n };\n}\n\n// ─── Public API ─────────────────────────────────────────────────\n\n/**\n * Get background transform for the current progress.\n *\n * @param effect — background effect name\n * @param progress — scene progress 0→1\n * @param beatIntensity — 0→1 for beat-reactive effects\n * @param sceneIndex — used to alternate direction for ken-burns/drift\n */\nexport function getBackgroundTransform(\n effect: string | undefined,\n progress: number,\n beatIntensity = 0,\n sceneIndex = 0,\n): BackgroundTransform {\n const dir = DIRECTIONS[sceneIndex % DIRECTIONS.length];\n\n // Undefined falls through to slow-zoom-in. Previously the missing\n // case landed in the `default:` branch alongside `\"static\"`, returning\n // `transform: \"none\"` — so every video where the AI didn't set\n // backgroundEffect (i.e. nearly all of them) rendered with zero\n // motion while the editor UI displayed \"Slow zoom in\" as default.\n // Now renderer and UI agree: `undefined` means motion, `\"static\"`\n // is the explicit opt-out. Slow zoom in (scale 1.00 → 1.12, no\n // translate) is the safest default for 2-4 s scenes — visible\n // enough to register, gentle enough not to pull focus from text.\n // Ken Burns adds translate that's too fast on short clips; reserve\n // for longer photo scenes via an explicit opt-in.\n const resolved = effect ?? \"slow-zoom-in\";\n\n switch (resolved) {\n case \"slow-zoom-in\":\n return {\n transform: `scale(${interpolate(progress, [0, 1], [1, 1.12])})`,\n transformOrigin: \"center\",\n };\n\n case \"slow-zoom-out\":\n return {\n transform: `scale(${interpolate(progress, [0, 1], [1.12, 1])})`,\n transformOrigin: \"center\",\n };\n\n case \"ken-burns\":\n return getKenBurnsTransform(progress, dir);\n\n case \"drift\":\n return getDriftTransform(progress, dir);\n\n case \"pulse\": {\n // Subtle scale pulse synced to beat\n const base = 1 + beatIntensity * 0.04;\n const breathe = 1 + Math.sin(progress * Math.PI * 2) * 0.02;\n return {\n transform: `scale(${base * breathe})`,\n transformOrigin: \"center\",\n };\n }\n\n case \"breathe\": {\n // Slow ambient float — gentle scale + vertical drift, not beat-synced\n const s = 1 + Math.sin(progress * Math.PI) * 0.03;\n const y = Math.sin(progress * Math.PI) * 0.5;\n return {\n transform: `scale(${s}) translateY(${y}%)`,\n transformOrigin: \"center\",\n };\n }\n\n case \"slow-tilt\": {\n // Subtle Dutch angle drift\n const angle = interpolate(progress, [0, 1], [0, 2.5]);\n return {\n transform: `scale(1.08) rotate(${angle}deg)`,\n transformOrigin: \"center\",\n };\n }\n\n case \"camera-shake\": {\n // Rapid small offsets for handheld/dramatic feel\n // Deterministic noise based on progress for reproducibility\n const t = progress * 40;\n const sx = Math.sin(t * 2.3) * 0.4 + Math.sin(t * 5.7) * 0.2;\n const sy = Math.cos(t * 3.1) * 0.3 + Math.cos(t * 4.9) * 0.15;\n const sr = Math.sin(t * 1.7) * 0.3;\n return {\n transform: `scale(1.05) translate(${sx}%, ${sy}%) rotate(${sr}deg)`,\n transformOrigin: \"center\",\n };\n }\n\n case \"static\":\n default:\n return {\n transform: \"none\",\n transformOrigin: \"center\",\n };\n }\n}\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "src/visual-system/scene-templates/color-utils.ts",
|
|
29
|
+
"type": "registry:lib",
|
|
30
|
+
"target": "vanillasky/scene-templates/color-utils.ts",
|
|
31
|
+
"content": "/**\n * Color utilities for scene templates.\n *\n * Provides safe color manipulation that works with hex colors\n * and graceful fallbacks for non-hex inputs.\n */\nimport React from \"react\";\nimport { resolveTokens, darken, orderDarkToLight, type BackgroundFamily } from \"../theme\";\n\n// ─── Layout constants ───────────────────────────────────────\n//\n// Shared vertical layout for templates that place a TextOverlay at the top of\n// the scene (showcase-*, chart-*, infographic-*). Using a single constant\n// guarantees the headline sits in the same place across every template, so\n// scene-to-scene cuts don't visually jump.\n//\n// TextOverlay vertically centers itself inside its container, so the visual\n// midline of the headline = TOP_TEXT_AREA_RATIO / 2 of the scene height.\nexport const TOP_TEXT_AREA_RATIO = 0.32;\n\n/**\n * Gradient background — clean 2-color linear gradient with a subtle\n * animated breathing overlay. Matches the look of a plain CSS\n * `linear-gradient(90deg, colorA, colorB)` so palette chips preview\n * exactly what templates render.\n *\n * The animation is deliberately minimal: two low-opacity radial blobs\n * of the same two colors drift slowly, adding just enough motion for\n * video without introducing a third \"midtone\" color that muddies\n * the gradient (a common source of unwanted purple/brown tints).\n */\nexport function gradientBackground({\n colorA,\n colorB,\n solidBg,\n progress,\n sceneDuration,\n seed,\n family,\n}: {\n colorA: string;\n colorB: string;\n /**\n * When set, the function returns this color as a solid background\n * (no gradient, no animated radial overlays). Used when the user has\n * a scraped brand whose darkest color reads as a real surface — the\n * accent stays on text/CTAs, but the bg goes flat instead of pairing\n * with an unrelated brand color and producing a noisy gradient.\n */\n solidBg?: string;\n progress: number;\n sceneDuration?: number;\n seed: number;\n /**\n * Background family from the style preset. Omitted = \"mesh\", the original\n * look — so existing configs are untouched. All families are pure CSS\n * background strings (no `filter`, which the SVG export can't rasterize)\n * and stay deterministic in `progress`.\n */\n family?: BackgroundFamily;\n}): string {\n if (solidBg) return solidBg;\n // Every scene owns its foreground motion clock, but consecutive scenes that\n // use the same brand backdrop must not visibly reset that backdrop. Run one\n // eased closed loop per scene: the gradient has zero velocity and the exact\n // same pixels at progress 0 and 1, regardless of scene duration or content\n // seed. Seeded variation is strongest mid-scene and converges to zero at\n // both handoff edges.\n const normalizedProgress = Math.max(0, Math.min(1, progress));\n const atBoundary = normalizedProgress === 0 || normalizedProgress === 1;\n const easedLoop = normalizedProgress * normalizedProgress * (3 - 2 * normalizedProgress);\n const seedEnvelope = atBoundary ? 0 : Math.sin(normalizedProgress * Math.PI) ** 2;\n void sceneDuration;\n\n const seededRandom = (s: number): number => {\n const x = Math.sin(s * 12.9898 + s * 78.233) * 43758.5453;\n return x - Math.floor(x);\n };\n const phase = seededRandom(seed) * Math.PI * 2 * seedEnvelope;\n const t = (atBoundary ? 0 : easedLoop * Math.PI * 2) + phase;\n\n // Darker of the two colors anchors the top of the frame so text and logos\n // sit over the stronger value. 180deg = top→bottom.\n const [topColor, bottomColor] = orderDarkToLight(colorA, colorB);\n\n if (family === \"wash\") {\n // No radials: one calm vertical ramp that drifts a few percent over the\n // scene. Reads considered rather than energetic.\n const shift = Math.sin(t) * 6;\n return `linear-gradient(${175 + shift}deg, ${topColor} 0%, ${bottomColor} 100%)`;\n }\n\n if (family === \"spotlight\") {\n // A single hard pool of accent high in the frame over a near-black\n // surround — high contrast, very little colour area.\n const x = 50 + Math.sin(t) * 8;\n const y = 28 + Math.cos(t + 1) * 5;\n return `\n radial-gradient(ellipse 70% 55% at ${x}% ${y}%, ${colorA}66 0%, ${colorA}1a 45%, transparent 72%),\n linear-gradient(180deg, ${darken(topColor, 0.55)} 0%, ${darken(bottomColor, 0.7)} 100%)\n `;\n }\n\n const x1 = 25 + Math.sin(t) * 25;\n const y1 = 30 + Math.cos(t + 1.0) * 25;\n const x2 = 75 + Math.sin(t + 2.0) * 25;\n const y2 = 70 + Math.cos(t + 3.5) * 25;\n\n return `\n radial-gradient(ellipse 60% 60% at ${x1}% ${y1}%, ${colorA}4d 0%, transparent 65%),\n radial-gradient(ellipse 60% 60% at ${x2}% ${y2}%, ${colorB}4d 0%, transparent 65%),\n linear-gradient(180deg, ${topColor} 0%, ${bottomColor} 100%)\n `;\n}\n\n/**\n * Animated brand-background overlay. Foreground semantic colours never alter\n * this visual background.\n */\nexport const BrandGradientOverlay: React.FC<{\n style: Parameters<typeof resolveTokens>[0];\n progress: number;\n sceneDuration?: number;\n seed: number;\n}> = ({ style: globalStyle, progress, sceneDuration, seed }) => {\n const tokens = resolveTokens(globalStyle);\n const [first, second] = tokens.background.type === \"gradient\"\n ? tokens.background.colors\n : [tokens.background.color, tokens.background.color];\n const solidBg = tokens.background.type === \"solid\" ? tokens.background.color : undefined;\n\n return React.createElement(\"div\", {\n style: {\n position: \"absolute\" as const,\n inset: 0,\n background: gradientBackground({ colorA: first, colorB: second, solidBg, progress, sceneDuration, seed, family: tokens.preset.background }),\n pointerEvents: \"none\" as const,\n },\n });\n};\n"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"meta": {
|
|
35
|
+
"vanillasky": {
|
|
36
|
+
"layer": "lib",
|
|
37
|
+
"tier": "free",
|
|
38
|
+
"domain": "appearance",
|
|
39
|
+
"level": "composed",
|
|
40
|
+
"audiences": [
|
|
41
|
+
"custom-scenes",
|
|
42
|
+
"react-developers"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": [
|
|
45
|
+
"theme",
|
|
46
|
+
"motion"
|
|
47
|
+
],
|
|
48
|
+
"useWhen": "Use gradientBackground to paint the scene layer and getBackgroundTransform to add deterministic camera movement across the full scene duration.",
|
|
49
|
+
"avoidWhen": "Do not apply these transforms to text or interactive chrome, and do not use CSS filter effects in the export path."
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|