loopwind 0.18.1 → 0.20.1

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 (69) hide show
  1. package/README.md +83 -0
  2. package/dist/commands/preview.d.ts.map +1 -1
  3. package/dist/commands/preview.js +2 -1
  4. package/dist/commands/preview.js.map +1 -1
  5. package/dist/commands/render.d.ts.map +1 -1
  6. package/dist/commands/render.js +2 -0
  7. package/dist/commands/render.js.map +1 -1
  8. package/dist/default-templates/AGENTS.md +54 -0
  9. package/dist/lib/renderer.d.ts.map +1 -1
  10. package/dist/lib/renderer.js +3 -0
  11. package/dist/lib/renderer.js.map +1 -1
  12. package/dist/lib/tailwind-browser.d.ts.map +1 -1
  13. package/dist/lib/tailwind-browser.js +169 -6
  14. package/dist/lib/tailwind-browser.js.map +1 -1
  15. package/dist/lib/tailwind.d.ts.map +1 -1
  16. package/dist/lib/tailwind.js +178 -7
  17. package/dist/lib/tailwind.js.map +1 -1
  18. package/dist/lib/video-preview.d.ts +1 -1
  19. package/dist/lib/video-preview.d.ts.map +1 -1
  20. package/dist/lib/video-preview.js +266 -249
  21. package/dist/lib/video-preview.js.map +1 -1
  22. package/dist/lib/video-renderer.d.ts +2 -0
  23. package/dist/lib/video-renderer.d.ts.map +1 -1
  24. package/dist/lib/video-renderer.js +4 -4
  25. package/dist/lib/video-renderer.js.map +1 -1
  26. package/dist/sdk/compiler.d.ts +94 -0
  27. package/dist/sdk/compiler.d.ts.map +1 -0
  28. package/dist/sdk/compiler.js +122 -0
  29. package/dist/sdk/compiler.js.map +1 -0
  30. package/dist/sdk/index.d.ts +3 -1
  31. package/dist/sdk/index.d.ts.map +1 -1
  32. package/dist/sdk/index.js +2 -1
  33. package/dist/sdk/index.js.map +1 -1
  34. package/dist/sdk/preview.d.ts +65 -0
  35. package/dist/sdk/preview.d.ts.map +1 -0
  36. package/dist/sdk/preview.js +262 -0
  37. package/dist/sdk/preview.js.map +1 -0
  38. package/dist/sdk/template.d.ts +47 -24
  39. package/dist/sdk/template.d.ts.map +1 -1
  40. package/dist/sdk/template.js +53 -93
  41. package/dist/sdk/template.js.map +1 -1
  42. package/examples/nextjs-template-import.ts +2 -2
  43. package/examples/sdk-video-preview.tsx +120 -0
  44. package/examples/template-compiler-workflow.ts +251 -0
  45. package/package.json +6 -2
  46. package/render-examples-600x400.mjs +161 -0
  47. package/render-spring-variants-fixed.mjs +60 -0
  48. package/render-staggered-text.mjs +56 -0
  49. package/test-jsx-support.mjs +32 -6
  50. package/test-sdk-config.mjs +138 -81
  51. package/test-sdk-source-config.mjs +427 -0
  52. package/test-static-debug.tsx +19 -0
  53. package/test-templates/config-test.mjs +17 -0
  54. package/test-templates/test-sdk.mjs +46 -22
  55. package/test-video-props.json +3 -0
  56. package/website/DEPLOYMENT.md +1 -0
  57. package/website/OG_IMAGES.md +1 -0
  58. package/website/astro.config.mjs +18 -2
  59. package/website/dist/.gitkeep +1 -0
  60. package/website/dist/_worker.js/index.js +1 -1
  61. package/website/dist/_worker.js/{manifest_BAAoOzaU.mjs → manifest_CT_D-YDe.mjs} +1 -1
  62. package/website/dist/llm.txt +1 -1
  63. package/website/dist/sdk/index.html +405 -102
  64. package/website/dist/sitemap.xml +12 -12
  65. package/website/package-lock.json +2866 -7080
  66. package/website/package.json +1 -2
  67. package/website/public/.gitkeep +1 -0
  68. package/website/templates/og-image.tsx +20 -21
  69. package/website/test-playground.mjs +45 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Example: Using the SDK Video Preview Component
3
+ *
4
+ * This demonstrates how to use previewVideo() and <VideoPreview>
5
+ * to display video templates in React applications.
6
+ */
7
+
8
+ import { defineTemplate, previewVideo, VideoPreview, type PreviewFrames } from 'loopwind/sdk';
9
+ import { useState, useEffect } from 'react';
10
+
11
+ // Define a video template
12
+ const introTemplate = defineTemplate({
13
+ name: 'intro-animation',
14
+ type: 'video',
15
+ size: { width: 1920, height: 1080 },
16
+ video: { fps: 30, duration: 3 },
17
+ render: ({ tw, progress, title = 'Hello World' }) => (
18
+ <div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-r from-blue-500 to-purple-600')}>
19
+ <h1
20
+ style={{
21
+ ...tw('text-8xl font-bold text-white enter-fade-in/0/1000'),
22
+ opacity: progress
23
+ }}
24
+ >
25
+ {title}
26
+ </h1>
27
+ </div>
28
+ )
29
+ });
30
+
31
+ /**
32
+ * Example 1: Pre-render at build time or on server
33
+ * (Next.js getStaticProps, getServerSideProps, etc.)
34
+ */
35
+ export async function getStaticProps() {
36
+ // Pre-render all frames on the server
37
+ const frames = await previewVideo(introTemplate, {
38
+ title: 'Welcome!'
39
+ });
40
+
41
+ return {
42
+ props: {
43
+ frames // Pass serialized frames to client
44
+ }
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Example 2: Client component that displays pre-rendered frames
50
+ */
51
+ export function IntroPreview({ frames }: { frames: PreviewFrames }) {
52
+ return (
53
+ <div className="container">
54
+ <h2>Video Preview</h2>
55
+ <VideoPreview
56
+ frames={frames}
57
+ autoPlay={true}
58
+ loop={true}
59
+ controls={true}
60
+ onFrameChange={(frame) => console.log(`Current frame: ${frame}`)}
61
+ />
62
+ </div>
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Example 3: Loading frames on demand (client-side)
68
+ * Note: This only works if you can run Node.js code (e.g., Electron, desktop apps)
69
+ */
70
+ export function DynamicPreview() {
71
+ const [frames, setFrames] = useState<PreviewFrames | null>(null);
72
+ const [loading, setLoading] = useState(true);
73
+
74
+ useEffect(() => {
75
+ // Load frames (only works in Node.js environment)
76
+ previewVideo(introTemplate, { title: 'Dynamic!' })
77
+ .then(setFrames)
78
+ .finally(() => setLoading(false));
79
+ }, []);
80
+
81
+ if (loading) return <div>Rendering preview...</div>;
82
+ if (!frames) return <div>Failed to load preview</div>;
83
+
84
+ return <VideoPreview frames={frames} autoPlay loop />;
85
+ }
86
+
87
+ /**
88
+ * Example 4: Minimal preview without controls
89
+ */
90
+ export function MinimalPreview({ frames }: { frames: PreviewFrames }) {
91
+ return (
92
+ <VideoPreview
93
+ frames={frames}
94
+ controls={false}
95
+ autoPlay={true}
96
+ loop={true}
97
+ style={{ border: '2px solid #7c3aed', borderRadius: '8px' }}
98
+ />
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Example 5: Next.js API Route for on-demand preview generation
104
+ */
105
+ // pages/api/preview/[template].ts
106
+ export async function handler(req, res) {
107
+ const { template } = req.query;
108
+ const props = req.body;
109
+
110
+ try {
111
+ // Generate preview frames on-demand
112
+ const frames = await previewVideo(introTemplate, props, {
113
+ format: 'data-url' // Use data URLs for easy JSON serialization
114
+ });
115
+
116
+ res.json(frames);
117
+ } catch (error) {
118
+ res.status(500).json({ error: error.message });
119
+ }
120
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Example: Template Compiler Workflow
3
+ *
4
+ * This example demonstrates the recommended production workflow for JSX templates:
5
+ *
6
+ * 1. ADMIN PANEL: Use compileTemplate() to transform JSX → JavaScript
7
+ * 2. DATABASE: Store the compiled JavaScript
8
+ * 3. PRODUCTION API: Use defineTemplateFromSource() to render
9
+ *
10
+ * Benefits:
11
+ * - JSX developer experience in admin panel
12
+ * - No @babel/standalone in production (36 MB smaller!)
13
+ * - Faster cold starts in serverless functions
14
+ * - Syntax errors caught at save time, not render time
15
+ */
16
+
17
+ import { compileTemplate, containsJSX } from 'loopwind/sdk/compiler';
18
+ import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
19
+ import fs from 'fs/promises';
20
+
21
+ // ============================================================================
22
+ // PART 1: Admin Panel - Compile and Save
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Simulates an admin panel API where users edit templates in a code editor
27
+ */
28
+ async function adminPanelSaveTemplate(templateId: string, userCode: string) {
29
+ console.log('📝 Admin Panel: User saved template\n');
30
+ console.log('User-provided JSX:');
31
+ console.log(userCode.substring(0, 200) + '...\n');
32
+
33
+ try {
34
+ // Check if the code contains JSX
35
+ if (containsJSX(userCode)) {
36
+ console.log('✓ Detected JSX syntax, compiling...');
37
+
38
+ // Compile JSX to JavaScript
39
+ const compiled = compileTemplate(userCode);
40
+
41
+ console.log('✓ Compilation successful!');
42
+ console.log(' Original size:', userCode.length, 'bytes');
43
+ console.log(' Compiled size:', compiled.length, 'bytes\n');
44
+
45
+ // In a real app, you would save to a database:
46
+ // await db.templates.update(templateId, {
47
+ // sourceCode: compiled,
48
+ // updatedAt: new Date()
49
+ // });
50
+
51
+ // For this example, save to a file
52
+ await fs.writeFile(`template-${templateId}-compiled.js`, compiled);
53
+ console.log('✓ Saved compiled template to database\n');
54
+
55
+ return { success: true, compiled };
56
+ } else {
57
+ console.log('ℹ No JSX detected, saving as-is\n');
58
+ return { success: true, compiled: userCode };
59
+ }
60
+ } catch (error) {
61
+ console.error('✗ Compilation failed:', (error as Error).message);
62
+ return {
63
+ success: false,
64
+ error: (error as Error).message,
65
+ };
66
+ }
67
+ }
68
+
69
+ // ============================================================================
70
+ // PART 2: Production API - Load and Render
71
+ // ============================================================================
72
+
73
+ /**
74
+ * Simulates a production API endpoint that renders templates
75
+ * This runs in Vercel, AWS Lambda, or any serverless function
76
+ */
77
+ async function productionApiRenderTemplate(
78
+ templateId: string,
79
+ props: Record<string, any>
80
+ ) {
81
+ console.log('🚀 Production API: Rendering template\n');
82
+
83
+ try {
84
+ // Load pre-compiled template from database
85
+ // In a real app:
86
+ // const template = await db.templates.findById(templateId);
87
+ // const compiledCode = template.sourceCode;
88
+
89
+ // For this example, load from file
90
+ const compiledCode = await fs.readFile(
91
+ `template-${templateId}-compiled.js`,
92
+ 'utf-8'
93
+ );
94
+
95
+ console.log('✓ Loaded pre-compiled template from database');
96
+ console.log(' Size:', compiledCode.length, 'bytes');
97
+ console.log(' No Babel needed! 🎉\n');
98
+
99
+ // Define template from pre-compiled JavaScript
100
+ // This is FAST - no JSX transformation at runtime
101
+ const templateDef = defineTemplateFromSource(compiledCode, {
102
+ config: {
103
+ colors: {
104
+ primary: '#3b82f6',
105
+ secondary: '#8b5cf6',
106
+ },
107
+ },
108
+ });
109
+
110
+ console.log('✓ Template loaded');
111
+ console.log(' Name:', templateDef.name);
112
+ console.log(' Type:', templateDef.type);
113
+ console.log(' Size:', templateDef.size.width, 'x', templateDef.size.height, '\n');
114
+
115
+ // Render the image
116
+ const png = await renderImage(templateDef, props);
117
+
118
+ console.log('✓ Image rendered');
119
+ console.log(' PNG size:', png.length, 'bytes\n');
120
+
121
+ return png;
122
+ } catch (error) {
123
+ console.error('✗ Rendering failed:', (error as Error).message);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ // ============================================================================
129
+ // DEMO: Full Workflow
130
+ // ============================================================================
131
+
132
+ async function demo() {
133
+ console.log('='.repeat(70));
134
+ console.log('🎨 Template Compiler Workflow Demo');
135
+ console.log('='.repeat(70) + '\n');
136
+
137
+ // Example JSX template that a user might write in a code editor
138
+ const userJSX = `
139
+ export const meta = {
140
+ name: 'social-card',
141
+ type: 'image',
142
+ size: { width: 1200, height: 630 }
143
+ };
144
+
145
+ export default ({ tw, title, description, author }) => (
146
+ <div style={tw('flex flex-col justify-between w-full h-full bg-gradient-to-br from-primary to-secondary p-12')}>
147
+ <div style={tw('flex flex-col')}>
148
+ <h1 style={tw('text-6xl font-bold text-white mb-4')}>
149
+ {title}
150
+ </h1>
151
+ <p style={tw('text-2xl text-white/90')}>
152
+ {description}
153
+ </p>
154
+ </div>
155
+ <div style={tw('flex items-center gap-4')}>
156
+ <div style={tw('w-16 h-16 rounded-full bg-white/20')} />
157
+ <span style={tw('text-xl text-white/80')}>{author}</span>
158
+ </div>
159
+ </div>
160
+ );
161
+ `;
162
+
163
+ const templateId = 'card-123';
164
+
165
+ // STEP 1: Admin panel compiles and saves
166
+ console.log('STEP 1: Admin Panel Workflow');
167
+ console.log('-'.repeat(70) + '\n');
168
+ await adminPanelSaveTemplate(templateId, userJSX);
169
+
170
+ // STEP 2: Production API loads and renders
171
+ console.log('STEP 2: Production API Workflow');
172
+ console.log('-'.repeat(70) + '\n');
173
+ const png = await productionApiRenderTemplate(templateId, {
174
+ title: 'Building with loopwind',
175
+ description: 'Generate images and videos programmatically',
176
+ author: 'Tommy Vedvik',
177
+ });
178
+
179
+ // Save the final result
180
+ await fs.writeFile('social-card-demo.png', png);
181
+ console.log('✅ Saved final image to social-card-demo.png\n');
182
+
183
+ // Cleanup
184
+ await fs.unlink(`template-${templateId}-compiled.js`);
185
+
186
+ console.log('='.repeat(70));
187
+ console.log('📊 Workflow Summary');
188
+ console.log('='.repeat(70));
189
+ console.log('\n✅ Benefits of this approach:\n');
190
+ console.log('1. Users write natural JSX syntax in admin panel');
191
+ console.log('2. JSX compiled once at save time (not every render)');
192
+ console.log('3. Compiled templates stored in database');
193
+ console.log('4. Production bundle 36 MB smaller (no @babel/standalone)');
194
+ console.log('5. Faster cold starts in serverless functions');
195
+ console.log('6. Syntax errors caught early (at save, not production)\n');
196
+
197
+ console.log('🔧 For Vercel/Lambda deployments:\n');
198
+ console.log('- Admin panel imports: loopwind/sdk/compiler');
199
+ console.log('- Production API imports: loopwind/sdk');
200
+ console.log('- Result: Only admin panel includes Babel\n');
201
+ }
202
+
203
+ // ============================================================================
204
+ // Example Integration Code
205
+ // ============================================================================
206
+
207
+ /**
208
+ * Example: Next.js Admin Panel API Route
209
+ */
210
+ async function exampleNextJSAdminRoute(req: any, res: any) {
211
+ const { templateId, code } = req.body;
212
+
213
+ try {
214
+ // Compile JSX
215
+ const compiled = compileTemplate(code);
216
+
217
+ // Save to database
218
+ // await db.templates.update(templateId, { sourceCode: compiled });
219
+
220
+ res.json({ success: true });
221
+ } catch (error) {
222
+ res.status(400).json({
223
+ error: 'Compilation failed',
224
+ message: (error as Error).message,
225
+ });
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Example: Next.js Production API Route
231
+ */
232
+ async function exampleNextJSProductionRoute(req: any, res: any) {
233
+ const { templateId, props } = req.body;
234
+
235
+ try {
236
+ // Load pre-compiled template
237
+ // const template = await db.templates.findById(templateId);
238
+ // const templateDef = defineTemplateFromSource(template.sourceCode);
239
+
240
+ // Render
241
+ // const png = await renderImage(templateDef, props);
242
+
243
+ // res.setHeader('Content-Type', 'image/png');
244
+ // res.send(png);
245
+ } catch (error) {
246
+ res.status(500).json({ error: 'Rendering failed' });
247
+ }
248
+ }
249
+
250
+ // Run the demo
251
+ demo().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopwind",
3
- "version": "0.18.1",
3
+ "version": "0.20.1",
4
4
  "description": "A CLI tool for AI code agents and developers for generating images and videos.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,10 @@
15
15
  "./sdk": {
16
16
  "types": "./dist/sdk/index.d.ts",
17
17
  "default": "./dist/sdk/index.js"
18
+ },
19
+ "./sdk/compiler": {
20
+ "types": "./dist/sdk/compiler.d.ts",
21
+ "default": "./dist/sdk/compiler.js"
18
22
  }
19
23
  },
20
24
  "scripts": {
@@ -46,7 +50,6 @@
46
50
  "url": "https://github.com/tomtev/loopwind/issues"
47
51
  },
48
52
  "dependencies": {
49
- "@babel/standalone": "^7.28.5",
50
53
  "@resvg/resvg-js": "^2.6.0",
51
54
  "@vitejs/plugin-react": "^5.1.1",
52
55
  "boxen": "^7.1.1",
@@ -75,6 +78,7 @@
75
78
  "vite": "^7.2.2"
76
79
  },
77
80
  "devDependencies": {
81
+ "@babel/standalone": "^7.28.5",
78
82
  "@types/babel__standalone": "^7.1.9",
79
83
  "@types/chalk-animation": "^1.6.3",
80
84
  "@types/express": "^5.0.5",
@@ -0,0 +1,161 @@
1
+ import { defineTemplate, renderVideo } from './dist/sdk/index.js';
2
+ import fs from 'fs/promises';
3
+ import React from 'react';
4
+
5
+ const { createElement: h } = React;
6
+
7
+ function createTemplateModule(meta, render) {
8
+ return { meta, default: render };
9
+ }
10
+
11
+ const templates = [
12
+ // 1. Bouncing ball with spring
13
+ createTemplateModule(
14
+ {
15
+ name: 'bouncing-ball',
16
+ description: 'Bouncing ball with spring easing animation',
17
+ type: 'video',
18
+ size: { width: 600, height: 400 },
19
+ video: { fps: 60, duration: 4 },
20
+ },
21
+ ({ tw }) =>
22
+ h('div', { style: tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-blue-900 to-purple-900') },
23
+ h('div', { style: tw('w-48 h-48 rounded-full bg-gradient-to-br from-yellow-400 to-orange-500 shadow-2xl spring loop-translate-y-16/1000') })
24
+ )
25
+ ),
26
+
27
+ // 2. Linear easing
28
+ createTemplateModule(
29
+ {
30
+ name: 'easing-linear',
31
+ description: 'Bouncing ball with linear easing (constant speed)',
32
+ type: 'video',
33
+ size: { width: 600, height: 400 },
34
+ video: { fps: 60, duration: 4 },
35
+ },
36
+ ({ tw }) =>
37
+ h('div', { style: tw('flex flex-col items-center justify-center gap-6 w-full h-full bg-gradient-to-br from-slate-900 to-slate-800') },
38
+ h('div', { style: tw('w-32 h-32 rounded-full bg-gradient-to-br from-cyan-400 to-blue-500 shadow-2xl linear loop-translate-y-12/1000') }),
39
+ h('div', { style: tw('text-white text-2xl font-bold') }, 'Linear Easing'),
40
+ h('div', { style: tw('text-white/60 text-sm text-center max-w-md px-4') }, 'Constant speed')
41
+ )
42
+ ),
43
+
44
+ // 3. Ease-in
45
+ createTemplateModule(
46
+ {
47
+ name: 'easing-ease-in',
48
+ description: 'Bouncing ball with ease-in (accelerating)',
49
+ type: 'video',
50
+ size: { width: 600, height: 400 },
51
+ video: { fps: 60, duration: 4 },
52
+ },
53
+ ({ tw }) =>
54
+ h('div', { style: tw('flex flex-col items-center justify-center gap-6 w-full h-full bg-gradient-to-br from-emerald-900 to-teal-800') },
55
+ h('div', { style: tw('w-32 h-32 rounded-full bg-gradient-to-br from-emerald-400 to-green-500 shadow-2xl ease-in loop-translate-y-12/1000') }),
56
+ h('div', { style: tw('text-white text-2xl font-bold') }, 'Ease In'),
57
+ h('div', { style: tw('text-white/60 text-sm text-center max-w-md px-4') }, 'Slow start, fast end')
58
+ )
59
+ ),
60
+
61
+ // 4. Ease-out
62
+ createTemplateModule(
63
+ {
64
+ name: 'easing-ease-out',
65
+ description: 'Bouncing ball with ease-out (decelerating)',
66
+ type: 'video',
67
+ size: { width: 600, height: 400 },
68
+ video: { fps: 60, duration: 4 },
69
+ },
70
+ ({ tw }) =>
71
+ h('div', { style: tw('flex flex-col items-center justify-center gap-6 w-full h-full bg-gradient-to-br from-rose-900 to-pink-800') },
72
+ h('div', { style: tw('w-32 h-32 rounded-full bg-gradient-to-br from-rose-400 to-pink-500 shadow-2xl ease-out loop-translate-y-12/1000') }),
73
+ h('div', { style: tw('text-white text-2xl font-bold') }, 'Ease Out'),
74
+ h('div', { style: tw('text-white/60 text-sm text-center max-w-md px-4') }, 'Fast start, slow end')
75
+ )
76
+ ),
77
+
78
+ // 5. Easing comparison (3 balls)
79
+ createTemplateModule(
80
+ {
81
+ name: 'easing-comparison',
82
+ description: 'Three balls with different easings side-by-side',
83
+ type: 'video',
84
+ size: { width: 600, height: 400 },
85
+ video: { fps: 60, duration: 4 },
86
+ },
87
+ ({ tw }) =>
88
+ h('div', { style: tw('flex items-center justify-center gap-8 w-full h-full bg-gradient-to-br from-slate-900 to-gray-900 px-8') },
89
+ h('div', { style: tw('flex flex-col items-center gap-4') },
90
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-cyan-400 to-blue-500 shadow-2xl linear loop-translate-y-10/800') }),
91
+ h('div', { style: tw('text-white text-sm font-bold') }, 'Linear'),
92
+ h('div', { style: tw('text-white/50 text-xs') }, '800ms')
93
+ ),
94
+ h('div', { style: tw('flex flex-col items-center gap-4') },
95
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 shadow-2xl ease-out loop-translate-y-10/1000') }),
96
+ h('div', { style: tw('text-white text-sm font-bold') }, 'Ease Out'),
97
+ h('div', { style: tw('text-white/50 text-xs') }, '1000ms')
98
+ ),
99
+ h('div', { style: tw('flex flex-col items-center gap-4') },
100
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-yellow-400 to-orange-500 shadow-2xl spring loop-translate-y-10/1200') }),
101
+ h('div', { style: tw('text-white text-sm font-bold') }, 'Spring'),
102
+ h('div', { style: tw('text-white/50 text-xs') }, '1200ms')
103
+ )
104
+ )
105
+ ),
106
+
107
+ // 6. Spring variants
108
+ createTemplateModule(
109
+ {
110
+ name: 'spring-variants',
111
+ description: 'Different spring configurations with varying bounce',
112
+ type: 'video',
113
+ size: { width: 600, height: 400 },
114
+ video: { fps: 60, duration: 4 },
115
+ },
116
+ ({ tw }) =>
117
+ h('div', { style: tw('flex items-center justify-center gap-6 w-full h-full bg-gradient-to-br from-indigo-900 to-purple-900 px-6') },
118
+ h('div', { style: tw('flex flex-col items-center gap-3') },
119
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-blue-400 to-cyan-500 shadow-2xl spring/1/100/10 loop-translate-y-10/1000') }),
120
+ h('div', { style: tw('text-white text-xs font-bold') }, 'Gentle'),
121
+ h('div', { style: tw('text-white/50 text-[10px]') }, '1/100/10')
122
+ ),
123
+ h('div', { style: tw('flex flex-col items-center gap-3') },
124
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 shadow-2xl spring/1/170/8 loop-translate-y-10/1000') }),
125
+ h('div', { style: tw('text-white text-xs font-bold') }, 'Bouncy'),
126
+ h('div', { style: tw('text-white/50 text-[10px]') }, '1/170/8')
127
+ ),
128
+ h('div', { style: tw('flex flex-col items-center gap-3') },
129
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-emerald-400 to-green-500 shadow-2xl spring/1/200/15 loop-translate-y-10/1000') }),
130
+ h('div', { style: tw('text-white text-xs font-bold') }, 'Snappy'),
131
+ h('div', { style: tw('text-white/50 text-[10px]') }, '1/200/15')
132
+ )
133
+ )
134
+ ),
135
+ ];
136
+
137
+ async function renderAll() {
138
+ console.log('Rendering all examples at 600x400...\n');
139
+
140
+ for (let i = 0; i < templates.length; i++) {
141
+ const templateModule = templates[i];
142
+ const template = defineTemplate(templateModule);
143
+ const outputName = `example-${templateModule.meta.name}.mp4`;
144
+ const outputPath = `output/${outputName}`;
145
+
146
+ console.log(`📹 Rendering ${outputName}...`);
147
+
148
+ try {
149
+ const buffer = await renderVideo(template, {}, { quality: 20 });
150
+ await fs.writeFile(outputPath, buffer);
151
+ console.log(` ✔ ${outputName} (${(buffer.length / 1024).toFixed(1)}KB)\n`);
152
+ } catch (err) {
153
+ console.log(` ✖ Failed: ${err.message}\n`);
154
+ console.error(err);
155
+ }
156
+ }
157
+
158
+ console.log('✅ All examples rendered at 600x400!');
159
+ }
160
+
161
+ renderAll().catch(console.error);
@@ -0,0 +1,60 @@
1
+ import { defineTemplate, renderVideo } from './dist/sdk/index.js';
2
+ import fs from 'fs/promises';
3
+ import React from 'react';
4
+
5
+ const { createElement: h } = React;
6
+
7
+ function createTemplateModule(meta, render) {
8
+ return { meta, default: render };
9
+ }
10
+
11
+ // Spring variants with enter animations to show the bounce better
12
+ const springVariantsTemplate = createTemplateModule(
13
+ {
14
+ name: 'spring-variants',
15
+ description: 'Different spring configurations with varying bounce',
16
+ type: 'video',
17
+ size: { width: 600, height: 400 },
18
+ video: { fps: 60, duration: 3 },
19
+ },
20
+ ({ tw }) =>
21
+ h('div', { style: tw('flex items-center justify-center gap-6 w-full h-full bg-gradient-to-br from-indigo-900 to-purple-900 px-6') },
22
+ h('div', { style: tw('flex flex-col items-center gap-3') },
23
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-blue-400 to-cyan-500 shadow-2xl opacity-0 enter-ease-spring/1/100/10 enter-fade-in/0/1500 enter--translate-y-32/0/1500') }),
24
+ h('div', { style: tw('text-white text-xs font-bold opacity-0 enter-fade-in/0/800') }, 'Gentle'),
25
+ h('div', { style: tw('text-white/50 text-[10px] opacity-0 enter-fade-in/0/800') }, '1/100/10')
26
+ ),
27
+ h('div', { style: tw('flex flex-col items-center gap-3') },
28
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 shadow-2xl opacity-0 enter-ease-spring/1/170/8 enter-fade-in/0/1500 enter--translate-y-32/0/1500') }),
29
+ h('div', { style: tw('text-white text-xs font-bold opacity-0 enter-fade-in/200/800') }, 'Bouncy'),
30
+ h('div', { style: tw('text-white/50 text-[10px] opacity-0 enter-fade-in/200/800') }, '1/170/8')
31
+ ),
32
+ h('div', { style: tw('flex flex-col items-center gap-3') },
33
+ h('div', { style: tw('w-24 h-24 rounded-full bg-gradient-to-br from-emerald-400 to-green-500 shadow-2xl opacity-0 enter-ease-spring/1/200/15 enter-fade-in/0/1500 enter--translate-y-32/0/1500') }),
34
+ h('div', { style: tw('text-white text-xs font-bold opacity-0 enter-fade-in/400/800') }, 'Snappy'),
35
+ h('div', { style: tw('text-white/50 text-[10px] opacity-0 enter-fade-in/400/800') }, '1/200/15')
36
+ )
37
+ )
38
+ );
39
+
40
+ async function render() {
41
+ console.log('Rendering spring-variants with enter animations...\n');
42
+
43
+ const template = defineTemplate(springVariantsTemplate);
44
+ const outputPath = 'output/example-spring-variants.mp4';
45
+
46
+ try {
47
+ const buffer = await renderVideo(template, {}, { quality: 20 });
48
+ await fs.writeFile(outputPath, buffer);
49
+ console.log(`✔ example-spring-variants.mp4 (${(buffer.length / 1024).toFixed(1)}KB)\n`);
50
+ console.log('✅ Now you can see the spring differences!');
51
+ console.log(' - Gentle: smooth drop with slight overshoot bounce');
52
+ console.log(' - Bouncy: exaggerated drop with lots of bounce');
53
+ console.log(' - Snappy: fast drop with minimal/no bounce');
54
+ } catch (err) {
55
+ console.log(`✖ Failed: ${err.message}\n`);
56
+ console.error(err);
57
+ }
58
+ }
59
+
60
+ render().catch(console.error);
@@ -0,0 +1,56 @@
1
+ import { defineTemplate, renderVideo } from './dist/sdk/index.js';
2
+ import fs from 'fs/promises';
3
+ import React from 'react';
4
+
5
+ const { createElement: h } = React;
6
+
7
+ function createTemplateModule(meta, render) {
8
+ return { meta, default: render };
9
+ }
10
+
11
+ const staggeredTextTemplate = createTemplateModule(
12
+ {
13
+ name: 'staggered-text',
14
+ description: 'Staggered text animation with spring easing',
15
+ type: 'video',
16
+ size: { width: 600, height: 400 },
17
+ video: { fps: 60, duration: 3 },
18
+ },
19
+ ({ tw }) => {
20
+ const text = 'loopwind';
21
+ const letters = text.split('');
22
+ const staggerDelay = 80; // ms between each letter
23
+
24
+ return h('div', { style: tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-violet-900 via-purple-900 to-fuchsia-900') },
25
+ h('div', { style: tw('flex items-center justify-center gap-1') },
26
+ ...letters.map((letter, i) =>
27
+ h('span', {
28
+ key: i,
29
+ style: tw(`text-8xl font-black bg-clip-text text-transparent bg-gradient-to-br from-cyan-400 via-blue-400 to-purple-400 opacity-0 enter-ease-spring enter-fade-in/${i * staggerDelay}/600 enter--translate-y-12/${i * staggerDelay}/600`)
30
+ }, letter)
31
+ )
32
+ )
33
+ );
34
+ }
35
+ );
36
+
37
+ async function render() {
38
+ console.log('Rendering staggered-text animation...\n');
39
+
40
+ const template = defineTemplate(staggeredTextTemplate);
41
+ const outputPath = 'output/example-staggered-text.mp4';
42
+
43
+ try {
44
+ const buffer = await renderVideo(template, {}, { quality: 20 });
45
+ await fs.writeFile(outputPath, buffer);
46
+ console.log(`✔ example-staggered-text.mp4 (${(buffer.length / 1024).toFixed(1)}KB)\n`);
47
+ console.log('✅ Staggered text animation complete!');
48
+ console.log(' Each letter animates in with spring easing');
49
+ console.log(' 80ms delay between each letter');
50
+ } catch (err) {
51
+ console.log(`✖ Failed: ${err.message}\n`);
52
+ console.error(err);
53
+ }
54
+ }
55
+
56
+ render().catch(console.error);