loopwind 0.10.3 → 0.10.4
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/REGISTRY_SETUP.md +5 -15
- package/TEMPLATE_SOURCES.md +15 -31
- package/package.json +5 -4
- package/test-templates/demo-intro-props.json +4 -0
- package/website/README.md +42 -16
- package/website/dist/.gitkeep +3 -0
- package/website/dist/_astro/agents.I1-fN38o.css +1 -0
- package/website/dist/agents/index.html +51 -0
- package/website/dist/animation/index.html +605 -0
- package/website/dist/fonts/index.html +42 -41
- package/website/dist/helpers/index.html +6 -8
- package/website/dist/images/index.html +33 -39
- package/website/dist/index.html +70 -115
- package/website/dist/llm.txt +954 -322
- package/website/dist/robots.txt +5 -0
- package/website/dist/sdk/index.html +435 -0
- package/website/dist/styling/index.html +6 -8
- package/website/dist/templates/index.html +19 -21
- package/website/dist/video/index.html +193 -79
- package/website/package-lock.json +7558 -2595
- package/website/public/.gitkeep +3 -0
- package/website/dist/_astro/fonts.DHdiHGBO.css +0 -1
package/website/dist/llm.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# loopwind - Complete Documentation for LLMs
|
|
2
2
|
|
|
3
|
-
This is a comprehensive, single-file documentation for
|
|
4
|
-
Generated: 2025-11-
|
|
3
|
+
This is a comprehensive, single-file documentation for loopwind, optimized for LLM consumption.
|
|
4
|
+
Generated: 2025-11-18T13:58:54.278Z
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -11,6 +11,38 @@ Generated: 2025-11-17T21:03:55.855Z
|
|
|
11
11
|
FILE: index.mdx
|
|
12
12
|
================================================================================
|
|
13
13
|
|
|
14
|
+
import CodeVideoDemo from '../components/CodeVideoDemo.astro';
|
|
15
|
+
|
|
16
|
+
export const videoCode = `export default function VideoIntro({ tw, title, subtitle }) {
|
|
17
|
+
return (
|
|
18
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700 gap-4')}>
|
|
19
|
+
<h1 style={tw('text-8xl font-bold text-white ease-out animate-bounce-in/0/0.4')}>
|
|
20
|
+
{title}
|
|
21
|
+
</h1>
|
|
22
|
+
<p style={tw('text-2xl text-white/80 ease-out animate-fade-in-up/0.3/0.7')}>
|
|
23
|
+
{subtitle}
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}`;
|
|
28
|
+
|
|
29
|
+
## Video Templates
|
|
30
|
+
|
|
31
|
+
<CodeVideoDemo
|
|
32
|
+
code={videoCode}
|
|
33
|
+
videoSrc="/demo-intro.mp4"
|
|
34
|
+
title="Animated Intro Example"
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
**Render it:**
|
|
38
|
+
```bash
|
|
39
|
+
loopwind render video-intro '{"title":"Welcome!","subtitle":"Built with loopwind"}' --out intro.mp4
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Output:** `_loopwind/outputs/intro.mp4` (1920×1080px, 3 seconds @ 30fps)
|
|
43
|
+
|
|
44
|
+
**Perfect for:** Social media intros, animated logos, tutorial overlays, and product showcases.
|
|
45
|
+
|
|
14
46
|
## Image Templates
|
|
15
47
|
|
|
16
48
|
```tsx
|
|
@@ -20,7 +52,7 @@ export default function OGImage({ tw, image, title, description, logo }) {
|
|
|
20
52
|
<div style={tw('flex w-full h-full bg-white')}>
|
|
21
53
|
<div style={tw('flex-1 flex flex-col justify-between p-12')}>
|
|
22
54
|
<img src={image(logo)} style={tw('h-12 w-auto')} />
|
|
23
|
-
|
|
55
|
+
|
|
24
56
|
<div>
|
|
25
57
|
<h1 style={tw('text-5xl font-bold text-gray-900 mb-4')}>
|
|
26
58
|
{title}
|
|
@@ -29,13 +61,13 @@ export default function OGImage({ tw, image, title, description, logo }) {
|
|
|
29
61
|
{description}
|
|
30
62
|
</p>
|
|
31
63
|
</div>
|
|
32
|
-
|
|
64
|
+
|
|
33
65
|
<p style={tw('text-gray-400')}>yoursite.com</p>
|
|
34
66
|
</div>
|
|
35
|
-
|
|
67
|
+
|
|
36
68
|
<div style={tw('flex-1')}>
|
|
37
|
-
<img
|
|
38
|
-
src={image(featuredImage)}
|
|
69
|
+
<img
|
|
70
|
+
src={image(featuredImage)}
|
|
39
71
|
style={tw('w-full h-full object-cover')}
|
|
40
72
|
/>
|
|
41
73
|
</div>
|
|
@@ -46,70 +78,39 @@ export default function OGImage({ tw, image, title, description, logo }) {
|
|
|
46
78
|
|
|
47
79
|
**Render it:**
|
|
48
80
|
```bash
|
|
49
|
-
|
|
81
|
+
loopwind render og-image '{"title":"Hello World","description":"My awesome blog post"}'
|
|
50
82
|
```
|
|
51
83
|
|
|
52
|
-
**Output:** `og-image.png` (1200×630px)
|
|
84
|
+
**Output:** `_loopwind/outputs/og-image.png` (1200×630px)
|
|
53
85
|
|
|
54
86
|
**Perfect for:** Open Graph images, Twitter cards, blog headers, product cards, and quote graphics.
|
|
55
87
|
|
|
56
|
-
## Video Templates
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
// Animated intro video
|
|
60
|
-
export default function VideoIntro({ tw, progress, title }) {
|
|
61
|
-
// Fade in animation
|
|
62
|
-
const titleOpacity = progress < 0.3 ? progress / 0.3 : 1;
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
|
|
66
|
-
<h1
|
|
67
|
-
style={{
|
|
68
|
-
...tw('text-8xl font-bold text-white'),
|
|
69
|
-
opacity: titleOpacity
|
|
70
|
-
}}
|
|
71
|
-
>
|
|
72
|
-
{title}
|
|
73
|
-
</h1>
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Render it:**
|
|
80
|
-
```bash
|
|
81
|
-
dsgn render video-intro '{"title":"Welcome!"}' --out intro.mp4
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**Output:** `intro.mp4` (1920×1080px, 3 seconds @ 30fps) in your current directory
|
|
85
|
-
|
|
86
|
-
**Perfect for:** Social media intros, animated logos, tutorial overlays, and product showcases.
|
|
87
|
-
|
|
88
88
|
## Features
|
|
89
89
|
|
|
90
90
|
- 🎨 **shadcn/ui Design System**: Beautiful, semantic colors out of the box (`text-primary`, `bg-card`, etc.)
|
|
91
91
|
- ✨ **Template-based**: Install design templates like you install UI components
|
|
92
|
+
- 🖼️ **Serverless Image Rendering**: Pure JavaScript rendering with Satori - works on Vercel, Netlify, Cloudflare Workers
|
|
93
|
+
- 🎬 **Serverless Video Rendering**: WASM-based MP4 encoding - 12x faster than traditional approaches
|
|
92
94
|
- 🎨 **Tailwind CSS Support**: Style templates with Tailwind utility classes + opacity modifiers (`bg-primary/50`)
|
|
93
95
|
- 📱 **Built-in Helpers**: QR codes, image embedding, video backgrounds, template composition
|
|
94
96
|
- ✅ **Smart Validation**: Automatic prop and template validation with helpful error messages
|
|
95
97
|
- 🚀 **Framework-agnostic**: Works with Node, Bun, Deno, Laravel, Python, and more
|
|
96
98
|
- 🤖 **Agent-friendly**: Machine-readable metadata for LLMs
|
|
97
|
-
- 📦 **Easy installation**: `npx
|
|
98
|
-
-
|
|
99
|
-
- ⚡ **Powered by Satori**: Generate images from React + Tailwind
|
|
99
|
+
- 📦 **Easy installation**: `npx loopwind add template-name`
|
|
100
|
+
- 🌐 **Pure JavaScript/WASM**: No native dependencies, works everywhere
|
|
100
101
|
|
|
101
102
|
## Quick Start
|
|
102
103
|
|
|
103
104
|
### Installation
|
|
104
105
|
|
|
105
106
|
```bash
|
|
106
|
-
npm install -g
|
|
107
|
+
npm install -g loopwind
|
|
107
108
|
```
|
|
108
109
|
|
|
109
110
|
Or use with npx:
|
|
110
111
|
|
|
111
112
|
```bash
|
|
112
|
-
npx
|
|
113
|
+
npx loopwind --help
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
### Initialize in Your Project
|
|
@@ -117,163 +118,110 @@ npx dsgn --help
|
|
|
117
118
|
Navigate to any project folder and run:
|
|
118
119
|
|
|
119
120
|
```bash
|
|
120
|
-
|
|
121
|
+
loopwind init
|
|
121
122
|
```
|
|
122
123
|
|
|
123
|
-
This creates a `
|
|
124
|
+
This creates a `_loopwind/` folder where templates and outputs will be located.
|
|
124
125
|
|
|
125
126
|
### Install a Template
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
#### 1. Official Registry (Coming Soon)
|
|
128
|
+
#### 1. Official Templates
|
|
130
129
|
|
|
131
130
|
```bash
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
loopwind add image-template
|
|
132
|
+
loopwind add video-template
|
|
134
133
|
```
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
# From a GitHub repo (looks for template.json in repo root)
|
|
140
|
-
dsgn add github:username/repo
|
|
141
|
-
|
|
142
|
-
# From a specific path in the repo
|
|
143
|
-
dsgn add github:username/repo/templates/banner-hero
|
|
144
|
-
|
|
145
|
-
# From an organization
|
|
146
|
-
dsgn add github:myorg/design-templates/social-media/og-image
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
#### 3. Direct URLs
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
# Install from any publicly accessible URL
|
|
153
|
-
dsgn add https://example.com/templates/my-template.json
|
|
154
|
-
dsgn add https://cdn.example.com/templates/awesome-banner.json
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**Requirements:**
|
|
158
|
-
- URL must return JSON in the registry template format
|
|
159
|
-
- Must be publicly accessible (no authentication)
|
|
160
|
-
|
|
161
|
-
#### 4. Local Filesystem
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
# Relative path
|
|
165
|
-
dsgn add ./my-templates/banner-hero
|
|
166
|
-
dsgn add ../shared-templates/product-card
|
|
167
|
-
|
|
168
|
-
# Absolute path
|
|
169
|
-
dsgn add /Users/you/templates/social-card
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
**Use cases:**
|
|
173
|
-
- Development and testing
|
|
174
|
-
- Private templates
|
|
175
|
-
- Shared team templates (monorepo)
|
|
176
|
-
- Before publishing to registry
|
|
177
|
-
|
|
178
|
-
Templates are installed to: `_dsgn/templates/<template>/` (customizable in `dsgn.json`)
|
|
135
|
+
Templates are installed to: `_loopwind/templates/<template>/` (customizable in `loopwind.json`)
|
|
179
136
|
|
|
180
137
|
**Benefits:**
|
|
181
|
-
- Templates are local to your project
|
|
182
|
-
- Different projects can use different template versions
|
|
138
|
+
- Templates are local to your project
|
|
183
139
|
- Version controlled with your project
|
|
184
140
|
- Easy to share within your team
|
|
185
|
-
- Works offline once installed
|
|
186
141
|
|
|
187
|
-
###
|
|
142
|
+
### Output a Template
|
|
188
143
|
|
|
189
144
|
```bash
|
|
190
|
-
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Output: `banner-hero.png` (1600x900px)
|
|
194
|
-
|
|
195
|
-
You can also use a props file:
|
|
196
|
-
```bash
|
|
197
|
-
dsgn render banner-hero props.json
|
|
145
|
+
loopwind render template-name '{"title":"Hello World","subtitle":"Built with loopwind"}'
|
|
198
146
|
```
|
|
199
147
|
|
|
200
148
|
## Commands
|
|
201
149
|
|
|
202
|
-
### `
|
|
150
|
+
### `loopwind add <source>`
|
|
203
151
|
|
|
204
152
|
Install a template from various sources:
|
|
205
153
|
|
|
206
154
|
```bash
|
|
207
155
|
# From registry
|
|
208
|
-
|
|
156
|
+
loopwind add banner-hero
|
|
209
157
|
|
|
210
158
|
# From GitHub
|
|
211
|
-
|
|
159
|
+
loopwind add github:username/repo/path/to/template
|
|
212
160
|
|
|
213
161
|
# From URL
|
|
214
|
-
|
|
162
|
+
loopwind add https://example.com/my-template.json
|
|
215
163
|
|
|
216
164
|
# From local path
|
|
217
|
-
|
|
165
|
+
loopwind add ./my-templates/custom-banner
|
|
218
166
|
```
|
|
219
167
|
|
|
220
|
-
### `
|
|
168
|
+
### `loopwind list`
|
|
221
169
|
|
|
222
170
|
List all installed templates:
|
|
223
171
|
|
|
224
172
|
```bash
|
|
225
|
-
|
|
173
|
+
loopwind list
|
|
226
174
|
```
|
|
227
175
|
|
|
228
|
-
### `
|
|
176
|
+
### `loopwind render <template> <props> [options]`
|
|
229
177
|
|
|
230
178
|
Render an image or video:
|
|
231
179
|
|
|
232
180
|
```bash
|
|
233
181
|
# Image with inline props
|
|
234
|
-
|
|
182
|
+
loopwind render banner-hero '{"title":"Hello World"}'
|
|
235
183
|
|
|
236
184
|
# Video with inline props
|
|
237
|
-
|
|
185
|
+
loopwind render video-intro '{"title":"Welcome"}' --out intro.mp4
|
|
238
186
|
|
|
239
187
|
# Using a props file
|
|
240
|
-
|
|
188
|
+
loopwind render banner-hero props.json
|
|
241
189
|
|
|
242
190
|
# Custom output
|
|
243
|
-
|
|
191
|
+
loopwind render banner-hero '{"title":"Hello"}' --out custom-name.png
|
|
244
192
|
|
|
245
193
|
# Different format
|
|
246
|
-
|
|
194
|
+
loopwind render banner-hero '{"title":"Hello"}' --format jpeg
|
|
247
195
|
```
|
|
248
196
|
|
|
249
197
|
Options:
|
|
250
|
-
- `--out, -o` - Output filename (default:
|
|
198
|
+
- `--out, -o` - Output filename (default: `_loopwind/outputs/<template>.<ext>`)
|
|
251
199
|
- `--format` - Output format: `png`, `jpeg`, `svg` (images only)
|
|
252
200
|
- `--quality` - JPEG quality 1-100 (default: 92)
|
|
253
201
|
|
|
254
|
-
### `
|
|
202
|
+
### `loopwind validate <template>`
|
|
255
203
|
|
|
256
204
|
Validate a template:
|
|
257
205
|
|
|
258
206
|
```bash
|
|
259
|
-
|
|
207
|
+
loopwind validate banner-hero
|
|
260
208
|
```
|
|
261
209
|
|
|
262
210
|
Checks:
|
|
263
211
|
- Template file exists and is valid React
|
|
264
|
-
- `meta
|
|
212
|
+
- `export const meta` exists and is valid
|
|
265
213
|
- Required props are defined
|
|
266
214
|
- Fonts exist (if specified)
|
|
267
215
|
|
|
268
|
-
### `
|
|
216
|
+
### `loopwind init`
|
|
269
217
|
|
|
270
|
-
Initialize
|
|
218
|
+
Initialize loopwind in a project:
|
|
271
219
|
|
|
272
220
|
```bash
|
|
273
|
-
|
|
221
|
+
loopwind init
|
|
274
222
|
```
|
|
275
223
|
|
|
276
|
-
Creates `
|
|
224
|
+
Creates `loopwind.json` configuration file.
|
|
277
225
|
|
|
278
226
|
## Next Steps
|
|
279
227
|
|
|
@@ -296,7 +244,7 @@ FILE: templates.mdx
|
|
|
296
244
|
|
|
297
245
|
## Overview
|
|
298
246
|
|
|
299
|
-
|
|
247
|
+
loopwind supports installing templates from multiple sources:
|
|
300
248
|
|
|
301
249
|
1. **Official Registry** (default)
|
|
302
250
|
2. **Direct URLs**
|
|
@@ -305,25 +253,25 @@ dsgn supports installing templates from multiple sources:
|
|
|
305
253
|
|
|
306
254
|
## 1. Official Registry (Default)
|
|
307
255
|
|
|
308
|
-
Install templates from the official
|
|
256
|
+
Install templates from the official loopwind registry at `https://loopwind.dev/r`
|
|
309
257
|
|
|
310
258
|
```bash
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
259
|
+
loopwind add banner-hero
|
|
260
|
+
loopwind add product-card
|
|
261
|
+
loopwind add social-og-image
|
|
314
262
|
```
|
|
315
263
|
|
|
316
264
|
**How it works:**
|
|
317
|
-
- Fetches from: `https://
|
|
265
|
+
- Fetches from: `https://loopwind.dev/r/banner-hero`
|
|
318
266
|
- Returns JSON with template files
|
|
319
|
-
- Installs to: `
|
|
267
|
+
- Installs to: `_loopwind/templates/banner-hero/`
|
|
320
268
|
|
|
321
269
|
### Custom Registry
|
|
322
270
|
|
|
323
271
|
Use a different registry:
|
|
324
272
|
|
|
325
273
|
```bash
|
|
326
|
-
|
|
274
|
+
loopwind add banner-hero --registry https://my-registry.com/templates
|
|
327
275
|
```
|
|
328
276
|
|
|
329
277
|
## 2. Direct URLs
|
|
@@ -331,8 +279,8 @@ dsgn add banner-hero --registry https://my-registry.com/templates
|
|
|
331
279
|
Install a template from any publicly accessible URL:
|
|
332
280
|
|
|
333
281
|
```bash
|
|
334
|
-
|
|
335
|
-
|
|
282
|
+
loopwind add https://example.com/templates/my-template.json
|
|
283
|
+
loopwind add https://cdn.example.com/templates/awesome-banner.json
|
|
336
284
|
```
|
|
337
285
|
|
|
338
286
|
**Requirements:**
|
|
@@ -365,13 +313,13 @@ Install templates directly from GitHub repos:
|
|
|
365
313
|
|
|
366
314
|
```bash
|
|
367
315
|
# From a GitHub repo (looks for template.json in repo root)
|
|
368
|
-
|
|
316
|
+
loopwind add github:username/repo
|
|
369
317
|
|
|
370
318
|
# From a specific path in the repo
|
|
371
|
-
|
|
319
|
+
loopwind add github:username/repo/templates/banner-hero
|
|
372
320
|
|
|
373
321
|
# From an organization
|
|
374
|
-
|
|
322
|
+
loopwind add github:myorg/design-templates/social-media/og-image
|
|
375
323
|
```
|
|
376
324
|
|
|
377
325
|
**How it works:**
|
|
@@ -404,11 +352,11 @@ Install templates from your local filesystem:
|
|
|
404
352
|
|
|
405
353
|
```bash
|
|
406
354
|
# Relative path
|
|
407
|
-
|
|
408
|
-
|
|
355
|
+
loopwind add ./my-templates/banner-hero
|
|
356
|
+
loopwind add ../shared-templates/product-card
|
|
409
357
|
|
|
410
358
|
# Absolute path
|
|
411
|
-
|
|
359
|
+
loopwind add /Users/you/templates/social-card
|
|
412
360
|
```
|
|
413
361
|
|
|
414
362
|
**Use cases:**
|
|
@@ -419,9 +367,9 @@ dsgn add /Users/you/templates/social-card
|
|
|
419
367
|
|
|
420
368
|
## Template Installation Directory
|
|
421
369
|
|
|
422
|
-
Templates are installed to `
|
|
370
|
+
Templates are installed to `_loopwind/templates/<template-name>/` by default.
|
|
423
371
|
|
|
424
|
-
Customize this in `
|
|
372
|
+
Customize this in `loopwind.json`:
|
|
425
373
|
|
|
426
374
|
```json
|
|
427
375
|
{
|
|
@@ -455,22 +403,22 @@ FILE: images.mdx
|
|
|
455
403
|
|
|
456
404
|
# Image Rendering
|
|
457
405
|
|
|
458
|
-
Generate beautiful images from React components using
|
|
406
|
+
Generate beautiful images from React components using loopwind's powerful image rendering engine powered by Satori.
|
|
459
407
|
|
|
460
408
|
## Quick Start
|
|
461
409
|
|
|
462
410
|
```bash
|
|
463
411
|
# Render an image template with inline props
|
|
464
|
-
|
|
412
|
+
loopwind render banner-hero '{"title":"Hello World","subtitle":"Welcome"}'
|
|
465
413
|
|
|
466
414
|
# Custom output name
|
|
467
|
-
|
|
415
|
+
loopwind render banner-hero '{"title":"Hello"}' --out custom-name.png
|
|
468
416
|
|
|
469
417
|
# Different format
|
|
470
|
-
|
|
418
|
+
loopwind render banner-hero '{"title":"Hello"}' --format jpeg --quality 95
|
|
471
419
|
|
|
472
420
|
# Or use a props file
|
|
473
|
-
|
|
421
|
+
loopwind render banner-hero props.json
|
|
474
422
|
```
|
|
475
423
|
|
|
476
424
|
## Image Template Structure
|
|
@@ -479,6 +427,14 @@ dsgn render banner-hero props.json
|
|
|
479
427
|
|
|
480
428
|
```tsx
|
|
481
429
|
// banner-hero.tsx
|
|
430
|
+
export const meta = {
|
|
431
|
+
name: "banner-hero",
|
|
432
|
+
type: "image",
|
|
433
|
+
description: "Hero banner with gradient background",
|
|
434
|
+
size: { width: 1600, height: 900 },
|
|
435
|
+
props: { title: "string", subtitle: "string" }
|
|
436
|
+
};
|
|
437
|
+
|
|
482
438
|
export default function BannerHero({ title, subtitle, tw }) {
|
|
483
439
|
return (
|
|
484
440
|
<div style={tw('flex flex-col justify-center items-center w-full h-full bg-gradient-to-br from-purple-600 to-blue-500 p-12')}>
|
|
@@ -493,29 +449,11 @@ export default function BannerHero({ title, subtitle, tw }) {
|
|
|
493
449
|
}
|
|
494
450
|
```
|
|
495
451
|
|
|
496
|
-
### Metadata (meta.json)
|
|
497
|
-
|
|
498
|
-
```json
|
|
499
|
-
{
|
|
500
|
-
"name": "banner-hero",
|
|
501
|
-
"type": "image",
|
|
502
|
-
"description": "Hero banner with gradient background",
|
|
503
|
-
"size": {
|
|
504
|
-
"width": 1600,
|
|
505
|
-
"height": 900
|
|
506
|
-
},
|
|
507
|
-
"props": {
|
|
508
|
-
"title": "string",
|
|
509
|
-
"subtitle": "string"
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
452
|
### Props File
|
|
515
453
|
|
|
516
454
|
```json
|
|
517
455
|
{
|
|
518
|
-
"title": "Welcome to
|
|
456
|
+
"title": "Welcome to loopwind",
|
|
519
457
|
"subtitle": "Generate beautiful images from React"
|
|
520
458
|
}
|
|
521
459
|
```
|
|
@@ -525,8 +463,8 @@ export default function BannerHero({ title, subtitle, tw }) {
|
|
|
525
463
|
### PNG (Default)
|
|
526
464
|
|
|
527
465
|
```bash
|
|
528
|
-
|
|
529
|
-
# Output: my-template.png
|
|
466
|
+
loopwind render my-template '{"title":"Hello"}'
|
|
467
|
+
# Output: _loopwind/outputs/my-template.png
|
|
530
468
|
```
|
|
531
469
|
|
|
532
470
|
**Best for:**
|
|
@@ -538,7 +476,7 @@ dsgn render my-template '{"title":"Hello"}'
|
|
|
538
476
|
### JPEG
|
|
539
477
|
|
|
540
478
|
```bash
|
|
541
|
-
|
|
479
|
+
loopwind render my-template '{"title":"Hello"}' --format jpeg --quality 92
|
|
542
480
|
```
|
|
543
481
|
|
|
544
482
|
**Options:**
|
|
@@ -553,7 +491,7 @@ dsgn render my-template '{"title":"Hello"}' --format jpeg --quality 92
|
|
|
553
491
|
### SVG
|
|
554
492
|
|
|
555
493
|
```bash
|
|
556
|
-
|
|
494
|
+
loopwind render my-template '{"title":"Hello"}' --format svg
|
|
557
495
|
```
|
|
558
496
|
|
|
559
497
|
**Best for:**
|
|
@@ -753,13 +691,13 @@ Render multiple images at once:
|
|
|
753
691
|
|
|
754
692
|
```bash
|
|
755
693
|
# Using a script with inline props
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
694
|
+
loopwind render my-template '{"title":"Image 1"}'
|
|
695
|
+
loopwind render my-template '{"title":"Image 2"}'
|
|
696
|
+
loopwind render my-template '{"title":"Image 3"}'
|
|
759
697
|
|
|
760
698
|
# Or loop through props files
|
|
761
699
|
for props in props/*.json; do
|
|
762
|
-
|
|
700
|
+
loopwind render my-template "$props"
|
|
763
701
|
done
|
|
764
702
|
```
|
|
765
703
|
|
|
@@ -794,18 +732,21 @@ Templates are cached after first load for faster subsequent renders.
|
|
|
794
732
|
|
|
795
733
|
### Fonts Not Rendering
|
|
796
734
|
|
|
797
|
-
Make sure fonts are properly
|
|
735
|
+
Make sure fonts are properly defined in your template's meta:
|
|
798
736
|
|
|
799
|
-
```
|
|
800
|
-
{
|
|
801
|
-
"
|
|
737
|
+
```tsx
|
|
738
|
+
export const meta = {
|
|
739
|
+
name: "my-template",
|
|
740
|
+
// ...
|
|
741
|
+
fonts: [
|
|
802
742
|
{
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
743
|
+
name: "Inter",
|
|
744
|
+
path: "fonts/Inter-Bold.woff",
|
|
745
|
+
weight: 700,
|
|
746
|
+
style: "normal"
|
|
806
747
|
}
|
|
807
748
|
]
|
|
808
|
-
}
|
|
749
|
+
};
|
|
809
750
|
```
|
|
810
751
|
|
|
811
752
|
### Images Not Loading
|
|
@@ -846,13 +787,13 @@ Create animated videos programmatically using React components. Perfect for auto
|
|
|
846
787
|
|
|
847
788
|
```bash
|
|
848
789
|
# Render a video template with inline props
|
|
849
|
-
|
|
790
|
+
loopwind render video-intro '{"title":"Welcome!"}' --out intro.mp4
|
|
850
791
|
|
|
851
792
|
# With custom encoding
|
|
852
|
-
|
|
793
|
+
loopwind render video-intro '{"title":"Welcome!"}' --preset ultrafast
|
|
853
794
|
|
|
854
795
|
# Or use a props file
|
|
855
|
-
|
|
796
|
+
loopwind render video-intro props.json --out intro.mp4
|
|
856
797
|
```
|
|
857
798
|
|
|
858
799
|
## Video Template Structure
|
|
@@ -861,13 +802,22 @@ dsgn render video-intro props.json --out intro.mp4
|
|
|
861
802
|
|
|
862
803
|
```tsx
|
|
863
804
|
// video-intro.tsx
|
|
805
|
+
export const meta = {
|
|
806
|
+
name: "video-intro",
|
|
807
|
+
type: "video",
|
|
808
|
+
description: "Animated intro with fade-in title",
|
|
809
|
+
size: { width: 1920, height: 1080 },
|
|
810
|
+
video: { fps: 30, duration: 3 },
|
|
811
|
+
props: { title: "string" }
|
|
812
|
+
};
|
|
813
|
+
|
|
864
814
|
export default function VideoIntro({ tw, title, frame, progress }) {
|
|
865
815
|
// Animate opacity based on progress
|
|
866
816
|
const titleOpacity = progress < 0.3 ? progress / 0.3 : 1;
|
|
867
|
-
|
|
817
|
+
|
|
868
818
|
return (
|
|
869
819
|
<div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
|
|
870
|
-
<h1
|
|
820
|
+
<h1
|
|
871
821
|
style={{
|
|
872
822
|
...tw('text-8xl font-bold text-white'),
|
|
873
823
|
opacity: titleOpacity
|
|
@@ -875,7 +825,7 @@ export default function VideoIntro({ tw, title, frame, progress }) {
|
|
|
875
825
|
>
|
|
876
826
|
{title}
|
|
877
827
|
</h1>
|
|
878
|
-
|
|
828
|
+
|
|
879
829
|
{/* Show frame counter */}
|
|
880
830
|
<div style={tw('absolute bottom-10 right-10 text-white text-sm')}>
|
|
881
831
|
Frame: {frame}
|
|
@@ -885,27 +835,6 @@ export default function VideoIntro({ tw, title, frame, progress }) {
|
|
|
885
835
|
}
|
|
886
836
|
```
|
|
887
837
|
|
|
888
|
-
### Metadata (meta.json)
|
|
889
|
-
|
|
890
|
-
```json
|
|
891
|
-
{
|
|
892
|
-
"name": "video-intro",
|
|
893
|
-
"type": "video",
|
|
894
|
-
"description": "Animated intro with fade-in title",
|
|
895
|
-
"size": {
|
|
896
|
-
"width": 1920,
|
|
897
|
-
"height": 1080
|
|
898
|
-
},
|
|
899
|
-
"video": {
|
|
900
|
-
"fps": 30,
|
|
901
|
-
"duration": 3
|
|
902
|
-
},
|
|
903
|
-
"props": {
|
|
904
|
-
"title": "string"
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
```
|
|
908
|
-
|
|
909
838
|
### Props File
|
|
910
839
|
|
|
911
840
|
```json
|
|
@@ -946,7 +875,48 @@ export default function MyVideo({ progress }) {
|
|
|
946
875
|
|
|
947
876
|
## Animation Patterns
|
|
948
877
|
|
|
949
|
-
###
|
|
878
|
+
### Pulse Animation
|
|
879
|
+
|
|
880
|
+
```tsx
|
|
881
|
+
export default function PulseVideo({ tw, title }) {
|
|
882
|
+
return (
|
|
883
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
|
|
884
|
+
<h1 style={tw('text-8xl font-bold text-white animate-pulse')}>
|
|
885
|
+
{title}
|
|
886
|
+
</h1>
|
|
887
|
+
</div>
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Spin Animation
|
|
893
|
+
|
|
894
|
+
```tsx
|
|
895
|
+
export default function SpinVideo({ tw, title }) {
|
|
896
|
+
return (
|
|
897
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-purple-600 to-pink-600 gap-8')}>
|
|
898
|
+
<div style={tw('w-32 h-32 border-8 border-white border-t-transparent rounded-full animate-spin')} />
|
|
899
|
+
<h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
|
|
900
|
+
</div>
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Bounce Animation
|
|
906
|
+
|
|
907
|
+
```tsx
|
|
908
|
+
export default function BounceVideo({ tw, title }) {
|
|
909
|
+
return (
|
|
910
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-blue-600')}>
|
|
911
|
+
<h1 style={tw('text-8xl font-bold text-white animate-bounce')}>
|
|
912
|
+
{title}
|
|
913
|
+
</h1>
|
|
914
|
+
</div>
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Fade with Progress
|
|
950
920
|
|
|
951
921
|
```tsx
|
|
952
922
|
export default function FadeVideo({ progress, title, tw }) {
|
|
@@ -1002,18 +972,76 @@ export default function ScaleVideo({ progress, tw }) {
|
|
|
1002
972
|
}
|
|
1003
973
|
```
|
|
1004
974
|
|
|
1005
|
-
### Rotation
|
|
975
|
+
### Progress-Based Rotation
|
|
1006
976
|
|
|
1007
977
|
```tsx
|
|
1008
978
|
export default function RotateVideo({ progress, tw }) {
|
|
1009
|
-
// Rotate 360 degrees
|
|
979
|
+
// Rotate 360 degrees based on progress
|
|
1010
980
|
const rotation = progress * 360;
|
|
1011
981
|
|
|
1012
982
|
return (
|
|
1013
|
-
<div style={
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
983
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
|
|
984
|
+
<div style={{
|
|
985
|
+
...tw('w-32 h-32 bg-blue-500 rounded-lg'),
|
|
986
|
+
transform: `rotate(${rotation}deg)`
|
|
987
|
+
}}>
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
### Using Tailwind Animations
|
|
995
|
+
|
|
996
|
+
Combine Tailwind's built-in animations with progress-based animations:
|
|
997
|
+
|
|
998
|
+
```tsx
|
|
999
|
+
export default function PulsingLogo({ tw, progress, image, logo }) {
|
|
1000
|
+
// Fade in based on progress
|
|
1001
|
+
const opacity = Math.min(progress / 0.3, 1);
|
|
1002
|
+
|
|
1003
|
+
return (
|
|
1004
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
|
|
1005
|
+
{/* Tailwind's animate-pulse + custom opacity */}
|
|
1006
|
+
<div style={{ ...tw('animate-pulse'), opacity }}>
|
|
1007
|
+
<img src={image(logo)} style={tw('w-64 h-64')} />
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
**Available Tailwind animations:**
|
|
1015
|
+
- `animate-spin` - Continuous rotation
|
|
1016
|
+
- `animate-pulse` - Subtle fade in/out
|
|
1017
|
+
- `animate-bounce` - Bouncing motion
|
|
1018
|
+
- `animate-ping` - Ripple effect (great for badges)
|
|
1019
|
+
|
|
1020
|
+
**Example with multiple animations:**
|
|
1021
|
+
|
|
1022
|
+
```tsx
|
|
1023
|
+
export default function NotificationVideo({ tw, progress, title }) {
|
|
1024
|
+
const slideX = -100 + (progress * 100); // Slide in from left
|
|
1025
|
+
|
|
1026
|
+
return (
|
|
1027
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-purple-600 to-blue-600')}>
|
|
1028
|
+
<div
|
|
1029
|
+
style={{
|
|
1030
|
+
...tw('flex items-center gap-4 bg-white rounded-2xl p-6 shadow-2xl'),
|
|
1031
|
+
transform: `translateX(${slideX}%)`
|
|
1032
|
+
}}
|
|
1033
|
+
>
|
|
1034
|
+
{/* Pinging indicator */}
|
|
1035
|
+
<div style={tw('relative')}>
|
|
1036
|
+
<div style={tw('w-3 h-3 bg-green-500 rounded-full animate-ping absolute')} />
|
|
1037
|
+
<div style={tw('w-3 h-3 bg-green-500 rounded-full')} />
|
|
1038
|
+
</div>
|
|
1039
|
+
|
|
1040
|
+
<div>
|
|
1041
|
+
<h2 style={tw('text-2xl font-bold text-gray-900')}>{title}</h2>
|
|
1042
|
+
<p style={tw('text-gray-600')}>New notification</p>
|
|
1043
|
+
</div>
|
|
1044
|
+
</div>
|
|
1017
1045
|
</div>
|
|
1018
1046
|
);
|
|
1019
1047
|
}
|
|
@@ -1078,7 +1106,7 @@ export default function VideoOverlay({ tw, video, title, background }) {
|
|
|
1078
1106
|
### How Video Sync Works
|
|
1079
1107
|
|
|
1080
1108
|
1. **First pass**: Template calls `video()` to register needed videos
|
|
1081
|
-
2. **Pre-processing**:
|
|
1109
|
+
2. **Pre-processing**: loopwind extracts all frames at template's FPS
|
|
1082
1110
|
3. **Rendering**: Each frame uses the corresponding video frame
|
|
1083
1111
|
4. **Caching**: Frames are cached in memory for fast access
|
|
1084
1112
|
|
|
@@ -1386,31 +1414,28 @@ export default function TutorialVideo({
|
|
|
1386
1414
|
|
|
1387
1415
|
### Common Frame Rates
|
|
1388
1416
|
|
|
1389
|
-
```
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
}
|
|
1417
|
+
```tsx
|
|
1418
|
+
// Film standard
|
|
1419
|
+
export const meta = {
|
|
1420
|
+
// ...
|
|
1421
|
+
video: { fps: 24, duration: 5 }
|
|
1422
|
+
};
|
|
1396
1423
|
```
|
|
1397
1424
|
|
|
1398
|
-
```
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1425
|
+
```tsx
|
|
1426
|
+
// YouTube/web standard
|
|
1427
|
+
export const meta = {
|
|
1428
|
+
// ...
|
|
1429
|
+
video: { fps: 30, duration: 3 }
|
|
1430
|
+
};
|
|
1405
1431
|
```
|
|
1406
1432
|
|
|
1407
|
-
```
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
}
|
|
1433
|
+
```tsx
|
|
1434
|
+
// Smooth animations
|
|
1435
|
+
export const meta = {
|
|
1436
|
+
// ...
|
|
1437
|
+
video: { fps: 60, duration: 2 }
|
|
1438
|
+
};
|
|
1414
1439
|
```
|
|
1415
1440
|
|
|
1416
1441
|
**Total frames = fps × duration**
|
|
@@ -1429,7 +1454,7 @@ export default function TutorialVideo({
|
|
|
1429
1454
|
### Default (Balanced)
|
|
1430
1455
|
|
|
1431
1456
|
```bash
|
|
1432
|
-
|
|
1457
|
+
loopwind render video-intro '{"title":"Welcome"}'
|
|
1433
1458
|
```
|
|
1434
1459
|
|
|
1435
1460
|
Uses H.264 codec with good quality/size balance.
|
|
@@ -1437,7 +1462,7 @@ Uses H.264 codec with good quality/size balance.
|
|
|
1437
1462
|
### Fast Encoding
|
|
1438
1463
|
|
|
1439
1464
|
```bash
|
|
1440
|
-
|
|
1465
|
+
loopwind render video-intro '{"title":"Welcome"}' --preset ultrafast
|
|
1441
1466
|
```
|
|
1442
1467
|
|
|
1443
1468
|
Faster encoding, slightly larger files. Good for previews.
|
|
@@ -1445,7 +1470,7 @@ Faster encoding, slightly larger files. Good for previews.
|
|
|
1445
1470
|
### High Quality
|
|
1446
1471
|
|
|
1447
1472
|
```bash
|
|
1448
|
-
|
|
1473
|
+
loopwind render video-intro '{"title":"Welcome"}' --preset slow
|
|
1449
1474
|
```
|
|
1450
1475
|
|
|
1451
1476
|
Better compression, slower encoding. Good for final output.
|
|
@@ -1481,44 +1506,41 @@ For long videos, consider rendering in segments.
|
|
|
1481
1506
|
|
|
1482
1507
|
## Common Video Templates
|
|
1483
1508
|
|
|
1484
|
-
###
|
|
1509
|
+
### Loading Spinner
|
|
1485
1510
|
|
|
1486
1511
|
```tsx
|
|
1487
|
-
export default function
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1512
|
+
export default function LoadingVideo({ tw, progress, title }) {
|
|
1513
|
+
// Show spinner for first 60%, then fade to title
|
|
1514
|
+
const spinnerOpacity = progress < 0.6 ? 1 : (1 - (progress - 0.6) / 0.4);
|
|
1515
|
+
const titleOpacity = progress > 0.6 ? (progress - 0.6) / 0.4 : 0;
|
|
1490
1516
|
|
|
1491
1517
|
return (
|
|
1492
|
-
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-
|
|
1518
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gray-900')}>
|
|
1519
|
+
{/* Tailwind's animate-spin */}
|
|
1520
|
+
<div style={{ opacity: spinnerOpacity }}>
|
|
1521
|
+
<div style={tw('w-32 h-32 border-8 border-blue-500 border-t-transparent rounded-full animate-spin')} />
|
|
1522
|
+
</div>
|
|
1523
|
+
|
|
1493
1524
|
<h1
|
|
1494
1525
|
style={{
|
|
1495
|
-
...tw('text-
|
|
1496
|
-
|
|
1526
|
+
...tw('text-6xl font-bold text-white mt-12'),
|
|
1527
|
+
opacity: titleOpacity
|
|
1497
1528
|
}}
|
|
1498
1529
|
>
|
|
1499
|
-
{
|
|
1530
|
+
{title}
|
|
1500
1531
|
</h1>
|
|
1501
|
-
|
|
1502
|
-
<p
|
|
1503
|
-
style={{
|
|
1504
|
-
...tw('text-3xl text-white'),
|
|
1505
|
-
opacity: taglineOpacity
|
|
1506
|
-
}}
|
|
1507
|
-
>
|
|
1508
|
-
{tagline}
|
|
1509
|
-
</p>
|
|
1510
1532
|
</div>
|
|
1511
1533
|
);
|
|
1512
1534
|
}
|
|
1513
1535
|
```
|
|
1514
1536
|
|
|
1515
|
-
### Progress Bar
|
|
1537
|
+
### Progress Bar with Pulse
|
|
1516
1538
|
|
|
1517
1539
|
```tsx
|
|
1518
1540
|
export default function ProgressVideo({ tw, progress, title, subtitle }) {
|
|
1519
1541
|
return (
|
|
1520
1542
|
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gray-900 p-12')}>
|
|
1521
|
-
<h2 style={tw('text-5xl font-bold text-white mb-12')}>
|
|
1543
|
+
<h2 style={tw('text-5xl font-bold text-white mb-12 animate-pulse')}>
|
|
1522
1544
|
{title}
|
|
1523
1545
|
</h2>
|
|
1524
1546
|
|
|
@@ -1540,7 +1562,7 @@ export default function ProgressVideo({ tw, progress, title, subtitle }) {
|
|
|
1540
1562
|
}
|
|
1541
1563
|
```
|
|
1542
1564
|
|
|
1543
|
-
### Countdown Timer
|
|
1565
|
+
### Countdown Timer with Pulse
|
|
1544
1566
|
|
|
1545
1567
|
```tsx
|
|
1546
1568
|
export default function CountdownVideo({ tw, frame, message }) {
|
|
@@ -1549,7 +1571,8 @@ export default function CountdownVideo({ tw, frame, message }) {
|
|
|
1549
1571
|
|
|
1550
1572
|
return (
|
|
1551
1573
|
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
|
|
1552
|
-
|
|
1574
|
+
{/* Add pulse animation when counting down */}
|
|
1575
|
+
<div style={tw('text-[200px] font-black text-white animate-pulse')}>
|
|
1553
1576
|
{secondsRemaining}
|
|
1554
1577
|
</div>
|
|
1555
1578
|
<p style={tw('text-3xl text-gray-400 mt-8')}>
|
|
@@ -1560,9 +1583,56 @@ export default function CountdownVideo({ tw, frame, message }) {
|
|
|
1560
1583
|
}
|
|
1561
1584
|
```
|
|
1562
1585
|
|
|
1563
|
-
## Output
|
|
1586
|
+
## Output Formats
|
|
1564
1587
|
|
|
1565
|
-
Videos
|
|
1588
|
+
Videos can be rendered in two formats:
|
|
1589
|
+
|
|
1590
|
+
### MP4 (Default)
|
|
1591
|
+
|
|
1592
|
+
```bash
|
|
1593
|
+
# MP4 - H.264 codec, smaller file size, best quality
|
|
1594
|
+
loopwind render video-intro '{"title":"Welcome"}' --out intro.mp4
|
|
1595
|
+
|
|
1596
|
+
# With custom quality (lower CRF = better quality)
|
|
1597
|
+
loopwind render video-intro '{"title":"Welcome"}' --crf 18
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
**Benefits:**
|
|
1601
|
+
- Smaller file sizes
|
|
1602
|
+
- Better color reproduction
|
|
1603
|
+
- Universal playback support
|
|
1604
|
+
- Best for social media, websites
|
|
1605
|
+
|
|
1606
|
+
### GIF
|
|
1607
|
+
|
|
1608
|
+
```bash
|
|
1609
|
+
# GIF - animated, works everywhere
|
|
1610
|
+
loopwind render video-intro '{"title":"Welcome"}' --format gif --out intro.gif
|
|
1611
|
+
|
|
1612
|
+
# Or just use .gif extension
|
|
1613
|
+
loopwind render video-intro '{"title":"Welcome"}' --out intro.gif
|
|
1614
|
+
```
|
|
1615
|
+
|
|
1616
|
+
**Benefits:**
|
|
1617
|
+
- Works in emails, GitHub READMEs, Slack
|
|
1618
|
+
- No video player needed
|
|
1619
|
+
- Auto-loops by default
|
|
1620
|
+
- Great for short animations
|
|
1621
|
+
|
|
1622
|
+
**Limitations:**
|
|
1623
|
+
- Limited to 256 colors per frame
|
|
1624
|
+
- Larger file sizes than MP4
|
|
1625
|
+
- Some color banding with gradients
|
|
1626
|
+
|
|
1627
|
+
**When to use GIF:**
|
|
1628
|
+
- Short loops (< 5 seconds)
|
|
1629
|
+
- Simple animations with solid colors
|
|
1630
|
+
- Platforms that don't support video (email, GitHub)
|
|
1631
|
+
|
|
1632
|
+
**When to use MP4:**
|
|
1633
|
+
- Longer videos
|
|
1634
|
+
- Complex gradients or photos
|
|
1635
|
+
- Best quality and smallest size
|
|
1566
1636
|
|
|
1567
1637
|
## Next Steps
|
|
1568
1638
|
|
|
@@ -1575,6 +1645,487 @@ Videos are always rendered as MP4 (H.264 codec) for maximum compatibility.
|
|
|
1575
1645
|
|
|
1576
1646
|
|
|
1577
1647
|
|
|
1648
|
+
================================================================================
|
|
1649
|
+
FILE: animation.mdx
|
|
1650
|
+
================================================================================
|
|
1651
|
+
|
|
1652
|
+
# Animation
|
|
1653
|
+
|
|
1654
|
+
loopwind provides **Tailwind-style animation classes** that work with `progress` and `frame` to create smooth video animations without writing custom code.
|
|
1655
|
+
|
|
1656
|
+
> **Note:** Animation classes only work with **video templates** and **GIFs**. For static images, animations will have no effect since there's no `progress` or `frame` context.
|
|
1657
|
+
|
|
1658
|
+
## Quick Start
|
|
1659
|
+
|
|
1660
|
+
```tsx
|
|
1661
|
+
export default function MyVideo({ tw, title, subtitle }) {
|
|
1662
|
+
return (
|
|
1663
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
|
|
1664
|
+
{/* Bounce in from below during first 40% */}
|
|
1665
|
+
<h1 style={tw('text-8xl font-bold text-white ease-out animate-bounce-in-up/0/0.4')}>
|
|
1666
|
+
{title}
|
|
1667
|
+
</h1>
|
|
1668
|
+
|
|
1669
|
+
{/* Fade in with upward motion from 30% to 70% */}
|
|
1670
|
+
<p style={tw('text-2xl text-white/80 mt-4 ease-out animate-fade-in-up/0.3/0.7')}>
|
|
1671
|
+
{subtitle}
|
|
1672
|
+
</p>
|
|
1673
|
+
</div>
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
## Transition Animations
|
|
1679
|
+
|
|
1680
|
+
Format: `animate-{type}/{start}/{end}`
|
|
1681
|
+
|
|
1682
|
+
- `{type}` - The animation type (fade-in, bounce-in-up, etc.)
|
|
1683
|
+
- `{start}` - Progress value to start (0-1)
|
|
1684
|
+
- `{end}` - Progress value to end (0-1)
|
|
1685
|
+
|
|
1686
|
+
### Fade Animations
|
|
1687
|
+
|
|
1688
|
+
Simple opacity transitions with optional direction.
|
|
1689
|
+
|
|
1690
|
+
```tsx
|
|
1691
|
+
// Fade in from 0% to 50% of the video
|
|
1692
|
+
<h1 style={tw('animate-fade-in/0/0.5')}>Hello</h1>
|
|
1693
|
+
|
|
1694
|
+
// Fade out from 50% to 100%
|
|
1695
|
+
<h1 style={tw('animate-fade-out/0.5/1')}>Goodbye</h1>
|
|
1696
|
+
```
|
|
1697
|
+
|
|
1698
|
+
| Class | Description |
|
|
1699
|
+
|-------|-------------|
|
|
1700
|
+
| `animate-fade-in/0/1` | Fade in (opacity 0 → 1) |
|
|
1701
|
+
| `animate-fade-out/0/1` | Fade out (opacity 1 → 0) |
|
|
1702
|
+
| `animate-fade-in-up/0/1` | Fade in + slide up (30px) |
|
|
1703
|
+
| `animate-fade-in-down/0/1` | Fade in + slide down (30px) |
|
|
1704
|
+
| `animate-fade-in-left/0/1` | Fade in + slide from left (30px) |
|
|
1705
|
+
| `animate-fade-in-right/0/1` | Fade in + slide from right (30px) |
|
|
1706
|
+
| `animate-fade-out-up/0/1` | Fade out + slide up |
|
|
1707
|
+
| `animate-fade-out-down/0/1` | Fade out + slide down |
|
|
1708
|
+
| `animate-fade-out-left/0/1` | Fade out + slide left |
|
|
1709
|
+
| `animate-fade-out-right/0/1` | Fade out + slide right |
|
|
1710
|
+
|
|
1711
|
+
### Slide Animations
|
|
1712
|
+
|
|
1713
|
+
Larger movement (100px) with fade.
|
|
1714
|
+
|
|
1715
|
+
```tsx
|
|
1716
|
+
// Slide in from left
|
|
1717
|
+
<div style={tw('animate-slide-left/0/0.5')}>Content</div>
|
|
1718
|
+
|
|
1719
|
+
// Slide up from bottom
|
|
1720
|
+
<div style={tw('animate-slide-up/0.2/0.8')}>Content</div>
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
| Class | Description |
|
|
1724
|
+
|-------|-------------|
|
|
1725
|
+
| `animate-slide-left/0/1` | Slide in from left (100px) |
|
|
1726
|
+
| `animate-slide-right/0/1` | Slide in from right (100px) |
|
|
1727
|
+
| `animate-slide-up/0/1` | Slide in from bottom (100px) |
|
|
1728
|
+
| `animate-slide-down/0/1` | Slide in from top (100px) |
|
|
1729
|
+
|
|
1730
|
+
### Bounce Animations
|
|
1731
|
+
|
|
1732
|
+
Playful entrance with overshoot effect.
|
|
1733
|
+
|
|
1734
|
+
```tsx
|
|
1735
|
+
// Bounce in with scale overshoot
|
|
1736
|
+
<h1 style={tw('animate-bounce-in/0/0.5')}>Bouncy!</h1>
|
|
1737
|
+
|
|
1738
|
+
// Bounce in from below
|
|
1739
|
+
<div style={tw('animate-bounce-in-up/0/0.6')}>Pop!</div>
|
|
1740
|
+
```
|
|
1741
|
+
|
|
1742
|
+
| Class | Description |
|
|
1743
|
+
|-------|-------------|
|
|
1744
|
+
| `animate-bounce-in/0/1` | Bounce in with scale overshoot |
|
|
1745
|
+
| `animate-bounce-in-up/0/1` | Bounce in from below |
|
|
1746
|
+
| `animate-bounce-in-down/0/1` | Bounce in from above |
|
|
1747
|
+
| `animate-bounce-in-left/0/1` | Bounce in from left |
|
|
1748
|
+
| `animate-bounce-in-right/0/1` | Bounce in from right |
|
|
1749
|
+
|
|
1750
|
+
### Scale & Zoom Animations
|
|
1751
|
+
|
|
1752
|
+
Size-based transitions.
|
|
1753
|
+
|
|
1754
|
+
```tsx
|
|
1755
|
+
// Scale in from 50%
|
|
1756
|
+
<div style={tw('animate-scale-in/0/0.5')}>Growing</div>
|
|
1757
|
+
|
|
1758
|
+
// Zoom in from 0%
|
|
1759
|
+
<div style={tw('animate-zoom-in/0/1')}>Zooming</div>
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
| Class | Description |
|
|
1763
|
+
|-------|-------------|
|
|
1764
|
+
| `animate-scale-in/0/1` | Scale up from 50% to 100% |
|
|
1765
|
+
| `animate-scale-out/0/1` | Scale up to 150% + fade out |
|
|
1766
|
+
| `animate-zoom-in/0/1` | Zoom in from 0% to 100% |
|
|
1767
|
+
| `animate-zoom-out/0/1` | Zoom out from 100% to 200% + fade |
|
|
1768
|
+
|
|
1769
|
+
### Rotate & Flip Animations
|
|
1770
|
+
|
|
1771
|
+
Rotation-based transitions.
|
|
1772
|
+
|
|
1773
|
+
```tsx
|
|
1774
|
+
// Rotate in 180 degrees
|
|
1775
|
+
<div style={tw('animate-rotate-in/0/0.5')}>Spinning</div>
|
|
1776
|
+
|
|
1777
|
+
// 3D flip on X axis
|
|
1778
|
+
<div style={tw('animate-flip-in-x/0/0.5')}>Flipping</div>
|
|
1779
|
+
```
|
|
1780
|
+
|
|
1781
|
+
| Class | Description |
|
|
1782
|
+
|-------|-------------|
|
|
1783
|
+
| `animate-rotate-in/0/1` | Rotate in from -180° |
|
|
1784
|
+
| `animate-rotate-out/0/1` | Rotate out to 180° |
|
|
1785
|
+
| `animate-flip-in-x/0/1` | 3D flip on horizontal axis |
|
|
1786
|
+
| `animate-flip-in-y/0/1` | 3D flip on vertical axis |
|
|
1787
|
+
|
|
1788
|
+
## Loop Animations
|
|
1789
|
+
|
|
1790
|
+
Format: `animate-{type}/{frameLength}`
|
|
1791
|
+
|
|
1792
|
+
Loop animations repeat every `{frameLength}` frames. At 30fps:
|
|
1793
|
+
- `/30` = 1 second loop
|
|
1794
|
+
- `/15` = 0.5 second loop
|
|
1795
|
+
- `/60` = 2 second loop
|
|
1796
|
+
|
|
1797
|
+
```tsx
|
|
1798
|
+
// Pulse opacity every 15 frames (0.5s at 30fps)
|
|
1799
|
+
<div style={tw('animate-pulse/15')}>Pulsing</div>
|
|
1800
|
+
|
|
1801
|
+
// Bounce every 20 frames
|
|
1802
|
+
<div style={tw('animate-bounce/20')}>Bouncing</div>
|
|
1803
|
+
|
|
1804
|
+
// Full rotation every 60 frames (2s)
|
|
1805
|
+
<div style={tw('animate-spin/60')}>Spinning</div>
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
| Class | Description |
|
|
1809
|
+
|-------|-------------|
|
|
1810
|
+
| `animate-pulse/{n}` | Opacity pulse (0.5 → 1 → 0.5) |
|
|
1811
|
+
| `animate-bounce/{n}` | Bounce up and down |
|
|
1812
|
+
| `animate-spin/{n}` | Full 360° rotation |
|
|
1813
|
+
| `animate-ping/{n}` | Scale up + fade out (radar effect) |
|
|
1814
|
+
| `animate-wiggle/{n}` | Side to side wiggle |
|
|
1815
|
+
| `animate-float/{n}` | Gentle up and down floating |
|
|
1816
|
+
|
|
1817
|
+
## Easing Functions
|
|
1818
|
+
|
|
1819
|
+
Add an easing class **before** the animation class to control the timing curve.
|
|
1820
|
+
|
|
1821
|
+
```tsx
|
|
1822
|
+
// Ease in (accelerate)
|
|
1823
|
+
<h1 style={tw('ease-in animate-fade-in/0/1')}>Accelerating</h1>
|
|
1824
|
+
|
|
1825
|
+
// Ease out (decelerate) - default
|
|
1826
|
+
<h1 style={tw('ease-out animate-fade-in/0/1')}>Decelerating</h1>
|
|
1827
|
+
|
|
1828
|
+
// Ease in-out (smooth)
|
|
1829
|
+
<h1 style={tw('ease-in-out animate-fade-in/0/1')}>Smooth</h1>
|
|
1830
|
+
|
|
1831
|
+
// Strong cubic easing
|
|
1832
|
+
<h1 style={tw('ease-out-cubic animate-bounce-in/0/0.5')}>Dramatic</h1>
|
|
1833
|
+
```
|
|
1834
|
+
|
|
1835
|
+
| Class | Description | Best For |
|
|
1836
|
+
|-------|-------------|----------|
|
|
1837
|
+
| `linear` | Constant speed | Mechanical motion |
|
|
1838
|
+
| `ease-in` | Slow start, fast end | Exit animations |
|
|
1839
|
+
| `ease-out` | Fast start, slow end (default) | Enter animations |
|
|
1840
|
+
| `ease-in-out` | Slow start and end | Subtle transitions |
|
|
1841
|
+
| `ease-in-cubic` | Strong slow start | Dramatic exits |
|
|
1842
|
+
| `ease-out-cubic` | Strong fast start | Impactful entrances |
|
|
1843
|
+
| `ease-in-out-cubic` | Strong both ends | Emphasis animations |
|
|
1844
|
+
| `ease-in-quart` | Very strong slow start | Powerful exits |
|
|
1845
|
+
| `ease-out-quart` | Very strong fast start | Punchy entrances |
|
|
1846
|
+
| `ease-in-out-quart` | Very strong both ends | Maximum drama |
|
|
1847
|
+
|
|
1848
|
+
## Staggered Animations
|
|
1849
|
+
|
|
1850
|
+
Create sequenced animations by offsetting the start/end times:
|
|
1851
|
+
|
|
1852
|
+
```tsx
|
|
1853
|
+
export default function StaggeredList({ tw, items }) {
|
|
1854
|
+
return (
|
|
1855
|
+
<div style={tw('flex flex-col gap-4')}>
|
|
1856
|
+
{/* First item: 0% to 30% */}
|
|
1857
|
+
<div style={tw('ease-out animate-fade-in-left/0/0.3')}>
|
|
1858
|
+
{items[0]}
|
|
1859
|
+
</div>
|
|
1860
|
+
|
|
1861
|
+
{/* Second item: 10% to 40% */}
|
|
1862
|
+
<div style={tw('ease-out animate-fade-in-left/0.1/0.4')}>
|
|
1863
|
+
{items[1]}
|
|
1864
|
+
</div>
|
|
1865
|
+
|
|
1866
|
+
{/* Third item: 20% to 50% */}
|
|
1867
|
+
<div style={tw('ease-out animate-fade-in-left/0.2/0.5')}>
|
|
1868
|
+
{items[2]}
|
|
1869
|
+
</div>
|
|
1870
|
+
</div>
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
### Dynamic Staggering
|
|
1876
|
+
|
|
1877
|
+
For dynamic lists, calculate the timing programmatically:
|
|
1878
|
+
|
|
1879
|
+
```tsx
|
|
1880
|
+
export default function DynamicStagger({ tw, items }) {
|
|
1881
|
+
return (
|
|
1882
|
+
<div style={tw('flex flex-col gap-4')}>
|
|
1883
|
+
{items.map((item, i) => {
|
|
1884
|
+
const start = i * 0.1; // Each item starts 10% later
|
|
1885
|
+
const end = start + 0.3; // Each animation lasts 30%
|
|
1886
|
+
|
|
1887
|
+
return (
|
|
1888
|
+
<div
|
|
1889
|
+
key={i}
|
|
1890
|
+
style={tw(`ease-out animate-fade-in-up/${start}/${end}`)}
|
|
1891
|
+
>
|
|
1892
|
+
{item}
|
|
1893
|
+
</div>
|
|
1894
|
+
);
|
|
1895
|
+
})}
|
|
1896
|
+
</div>
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
```
|
|
1900
|
+
|
|
1901
|
+
## Combining Animations
|
|
1902
|
+
|
|
1903
|
+
You can combine multiple animation classes. They will be applied together:
|
|
1904
|
+
|
|
1905
|
+
```tsx
|
|
1906
|
+
// Note: Multiple transforms will be combined
|
|
1907
|
+
<h1 style={tw('animate-fade-in/0/0.5 animate-scale-in/0/0.5')}>
|
|
1908
|
+
Fade + Scale
|
|
1909
|
+
</h1>
|
|
1910
|
+
```
|
|
1911
|
+
|
|
1912
|
+
For more complex combinations, use manual animation with `progress`:
|
|
1913
|
+
|
|
1914
|
+
```tsx
|
|
1915
|
+
export default function ComplexAnimation({ tw, progress, title }) {
|
|
1916
|
+
// Custom compound animation
|
|
1917
|
+
const opacity = Math.min(1, progress * 3);
|
|
1918
|
+
const scale = 0.8 + progress * 0.2;
|
|
1919
|
+
const rotation = (1 - progress) * -10;
|
|
1920
|
+
|
|
1921
|
+
return (
|
|
1922
|
+
<div style={tw('flex items-center justify-center w-full h-full')}>
|
|
1923
|
+
<h1 style={{
|
|
1924
|
+
...tw('text-8xl font-bold'),
|
|
1925
|
+
opacity,
|
|
1926
|
+
transform: `scale(${scale}) rotate(${rotation}deg)`
|
|
1927
|
+
}}>
|
|
1928
|
+
{title}
|
|
1929
|
+
</h1>
|
|
1930
|
+
</div>
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
```
|
|
1934
|
+
|
|
1935
|
+
## Common Patterns
|
|
1936
|
+
|
|
1937
|
+
### Intro Sequence
|
|
1938
|
+
|
|
1939
|
+
```tsx
|
|
1940
|
+
export default function IntroVideo({ tw, title, subtitle, logo }) {
|
|
1941
|
+
return (
|
|
1942
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
|
|
1943
|
+
{/* Logo appears first */}
|
|
1944
|
+
<img
|
|
1945
|
+
src={logo}
|
|
1946
|
+
style={tw('h-20 mb-8 ease-out animate-scale-in/0/0.3')}
|
|
1947
|
+
/>
|
|
1948
|
+
|
|
1949
|
+
{/* Title bounces in */}
|
|
1950
|
+
<h1 style={tw('text-7xl font-bold text-white ease-out animate-bounce-in-up/0.2/0.5')}>
|
|
1951
|
+
{title}
|
|
1952
|
+
</h1>
|
|
1953
|
+
|
|
1954
|
+
{/* Subtitle fades in last */}
|
|
1955
|
+
<p style={tw('text-2xl text-white/80 mt-4 ease-out animate-fade-in-up/0.4/0.7')}>
|
|
1956
|
+
{subtitle}
|
|
1957
|
+
</p>
|
|
1958
|
+
</div>
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
```
|
|
1962
|
+
|
|
1963
|
+
### Text Reveal
|
|
1964
|
+
|
|
1965
|
+
```tsx
|
|
1966
|
+
export default function TextReveal({ tw, words }) {
|
|
1967
|
+
return (
|
|
1968
|
+
<div style={tw('flex flex-wrap gap-2 justify-center')}>
|
|
1969
|
+
{words.split(' ').map((word, i) => (
|
|
1970
|
+
<span
|
|
1971
|
+
key={i}
|
|
1972
|
+
style={tw(`text-4xl font-bold ease-out animate-fade-in-up/${i * 0.1}/${i * 0.1 + 0.2}`)}
|
|
1973
|
+
>
|
|
1974
|
+
{word}
|
|
1975
|
+
</span>
|
|
1976
|
+
))}
|
|
1977
|
+
</div>
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
### Looping Background Element
|
|
1983
|
+
|
|
1984
|
+
```tsx
|
|
1985
|
+
export default function AnimatedBackground({ tw, children }) {
|
|
1986
|
+
return (
|
|
1987
|
+
<div style={tw('relative w-full h-full')}>
|
|
1988
|
+
{/* Floating background circles */}
|
|
1989
|
+
<div style={tw('absolute top-10 left-10 w-20 h-20 rounded-full bg-white/10 animate-float/60')} />
|
|
1990
|
+
<div style={tw('absolute bottom-20 right-20 w-32 h-32 rounded-full bg-white/10 animate-pulse/45')} />
|
|
1991
|
+
|
|
1992
|
+
{/* Main content */}
|
|
1993
|
+
<div style={tw('relative z-10')}>
|
|
1994
|
+
{children}
|
|
1995
|
+
</div>
|
|
1996
|
+
</div>
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
```
|
|
2000
|
+
|
|
2001
|
+
### Exit Animation
|
|
2002
|
+
|
|
2003
|
+
```tsx
|
|
2004
|
+
export default function ExitAnimation({ tw, title }) {
|
|
2005
|
+
return (
|
|
2006
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-black')}>
|
|
2007
|
+
{/* Visible from 0-70%, then exits */}
|
|
2008
|
+
<h1 style={tw('text-8xl font-bold text-white ease-in animate-fade-out-up/0.7/1')}>
|
|
2009
|
+
{title}
|
|
2010
|
+
</h1>
|
|
2011
|
+
</div>
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
```
|
|
2015
|
+
|
|
2016
|
+
## Manual Animation with `progress` and `frame`
|
|
2017
|
+
|
|
2018
|
+
For complete control beyond animation classes, use `progress` and `frame` directly.
|
|
2019
|
+
|
|
2020
|
+
### Available Props
|
|
2021
|
+
|
|
2022
|
+
| Prop | Type | Description |
|
|
2023
|
+
|------|------|-------------|
|
|
2024
|
+
| `progress` | `number` | 0 to 1 through the video (0% to 100%) |
|
|
2025
|
+
| `frame` | `number` | Current frame number (0, 1, 2, ... totalFrames-1) |
|
|
2026
|
+
|
|
2027
|
+
These are **only available in video templates**. Use them when animation classes aren't flexible enough.
|
|
2028
|
+
|
|
2029
|
+
### Using `progress`
|
|
2030
|
+
|
|
2031
|
+
```tsx
|
|
2032
|
+
export default function ProgressAnimation({ tw, progress, title }) {
|
|
2033
|
+
// Custom fade based on progress
|
|
2034
|
+
const opacity = progress < 0.3 ? progress / 0.3 : 1;
|
|
2035
|
+
|
|
2036
|
+
// Custom scale based on progress
|
|
2037
|
+
const scale = 0.8 + progress * 0.2; // 0.8 to 1.0
|
|
2038
|
+
|
|
2039
|
+
return (
|
|
2040
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
|
|
2041
|
+
<h1 style={{
|
|
2042
|
+
...tw('text-8xl font-bold text-white'),
|
|
2043
|
+
opacity,
|
|
2044
|
+
transform: `scale(${scale})`
|
|
2045
|
+
}}>
|
|
2046
|
+
{title}
|
|
2047
|
+
</h1>
|
|
2048
|
+
</div>
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
```
|
|
2052
|
+
|
|
2053
|
+
### Using `frame`
|
|
2054
|
+
|
|
2055
|
+
```tsx
|
|
2056
|
+
export default function FrameAnimation({ tw, frame, title }) {
|
|
2057
|
+
// Color cycling using frame number
|
|
2058
|
+
const hue = (frame * 5) % 360; // Cycle through colors
|
|
2059
|
+
|
|
2060
|
+
// Pulsing based on frame
|
|
2061
|
+
const fps = 30;
|
|
2062
|
+
const pulse = Math.sin(frame / fps * Math.PI * 2) * 0.2 + 0.8; // 0.6 to 1.0
|
|
2063
|
+
|
|
2064
|
+
return (
|
|
2065
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-black')}>
|
|
2066
|
+
<h1 style={{
|
|
2067
|
+
...tw('text-8xl font-bold'),
|
|
2068
|
+
color: `hsl(${hue}, 70%, 60%)`,
|
|
2069
|
+
transform: `scale(${pulse})`
|
|
2070
|
+
}}>
|
|
2071
|
+
{title}
|
|
2072
|
+
</h1>
|
|
2073
|
+
</div>
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
```
|
|
2077
|
+
|
|
2078
|
+
### Custom Easing
|
|
2079
|
+
|
|
2080
|
+
```tsx
|
|
2081
|
+
export default function CustomEasing({ tw, progress, title }) {
|
|
2082
|
+
// Smoothstep easing
|
|
2083
|
+
const eased = progress * progress * (3 - 2 * progress);
|
|
2084
|
+
|
|
2085
|
+
// Elastic easing
|
|
2086
|
+
const elastic = Math.pow(2, -10 * progress) * Math.sin((progress - 0.075) * (2 * Math.PI) / 0.3) + 1;
|
|
2087
|
+
|
|
2088
|
+
return (
|
|
2089
|
+
<div style={tw('flex items-center justify-center w-full h-full')}>
|
|
2090
|
+
<h1 style={{
|
|
2091
|
+
...tw('text-8xl font-bold'),
|
|
2092
|
+
opacity: eased,
|
|
2093
|
+
transform: `translateY(${(1 - elastic) * 100}px)`
|
|
2094
|
+
}}>
|
|
2095
|
+
{title}
|
|
2096
|
+
</h1>
|
|
2097
|
+
</div>
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
```
|
|
2101
|
+
|
|
2102
|
+
### When to Use Manual Animation
|
|
2103
|
+
|
|
2104
|
+
Use `progress`/`frame` instead of animation classes when you need:
|
|
2105
|
+
- **Custom easing functions** (elastic, spring, bounce with specific curves)
|
|
2106
|
+
- **Color cycling or gradients** based on time
|
|
2107
|
+
- **Mathematical animations** (sine waves, spirals, etc.)
|
|
2108
|
+
- **Complex multi-property animations** that need precise coordination
|
|
2109
|
+
- **Conditional logic** based on specific frame numbers
|
|
2110
|
+
|
|
2111
|
+
For everything else, prefer animation classes - they're simpler and more maintainable.
|
|
2112
|
+
|
|
2113
|
+
## Performance Tips
|
|
2114
|
+
|
|
2115
|
+
1. **Use Tailwind classes** when possible - they're optimized for the renderer
|
|
2116
|
+
2. **Avoid too many nested animations** - each adds computation per frame
|
|
2117
|
+
3. **Use loop animations sparingly** - they're computed every frame
|
|
2118
|
+
4. **Prefer opacity and transform** - they're the most performant properties
|
|
2119
|
+
|
|
2120
|
+
## Next Steps
|
|
2121
|
+
|
|
2122
|
+
- [Video Templates](/video) - Creating video templates
|
|
2123
|
+
- [SDK](/sdk) - Programmatic rendering with animations
|
|
2124
|
+
- [Helpers](/helpers) - QR codes, images, and more
|
|
2125
|
+
|
|
2126
|
+
|
|
2127
|
+
|
|
2128
|
+
|
|
1578
2129
|
================================================================================
|
|
1579
2130
|
FILE: helpers.mdx
|
|
1580
2131
|
================================================================================
|
|
@@ -1585,7 +2136,7 @@ Additional helpers for creating powerful, composable templates.
|
|
|
1585
2136
|
|
|
1586
2137
|
## Overview
|
|
1587
2138
|
|
|
1588
|
-
Beyond the basics,
|
|
2139
|
+
Beyond the basics, loopwind provides:
|
|
1589
2140
|
- `template()` - Compose templates together
|
|
1590
2141
|
- `qr()` - Generate QR codes on the fly
|
|
1591
2142
|
- `config` - Access user configuration
|
|
@@ -1710,11 +2261,11 @@ You can customize QR code appearance:
|
|
|
1710
2261
|
|
|
1711
2262
|
## User Configuration
|
|
1712
2263
|
|
|
1713
|
-
Access user settings from `
|
|
2264
|
+
Access user settings from `loopwind.json` using the `config` prop:
|
|
1714
2265
|
|
|
1715
2266
|
```tsx
|
|
1716
2267
|
export default function BrandedTemplate({ tw, config, title }) {
|
|
1717
|
-
// Access custom colors from
|
|
2268
|
+
// Access custom colors from loopwind.json
|
|
1718
2269
|
const primaryColor = config?.colors?.brand || '#6366f1';
|
|
1719
2270
|
|
|
1720
2271
|
return (
|
|
@@ -1730,7 +2281,7 @@ export default function BrandedTemplate({ tw, config, title }) {
|
|
|
1730
2281
|
}
|
|
1731
2282
|
```
|
|
1732
2283
|
|
|
1733
|
-
**User's
|
|
2284
|
+
**User's loopwind.json:**
|
|
1734
2285
|
```json
|
|
1735
2286
|
{
|
|
1736
2287
|
"colors": {
|
|
@@ -1754,7 +2305,7 @@ export default function MyTemplate({
|
|
|
1754
2305
|
tw, // Tailwind class converter
|
|
1755
2306
|
qr, // QR code generator (this page)
|
|
1756
2307
|
template, // Template composer (this page)
|
|
1757
|
-
config, // User config from
|
|
2308
|
+
config, // User config from loopwind.json (this page)
|
|
1758
2309
|
|
|
1759
2310
|
// Media helpers (see dedicated pages)
|
|
1760
2311
|
image, // Image embedder → see /images
|
|
@@ -1860,7 +2411,7 @@ export default function CustomGradient({ title, tw }) {
|
|
|
1860
2411
|
|
|
1861
2412
|
## shadcn/ui Design System
|
|
1862
2413
|
|
|
1863
|
-
|
|
2414
|
+
loopwind uses **shadcn/ui's design system** by default, providing semantic color tokens for beautiful, consistent designs.
|
|
1864
2415
|
|
|
1865
2416
|
### Default Color Palette
|
|
1866
2417
|
|
|
@@ -2099,7 +2650,7 @@ export default function GradientCard({ title, tw }) {
|
|
|
2099
2650
|
|
|
2100
2651
|
## Custom Theme Colors
|
|
2101
2652
|
|
|
2102
|
-
Override default colors in your `
|
|
2653
|
+
Override default colors in your `loopwind.json`:
|
|
2103
2654
|
|
|
2104
2655
|
```json
|
|
2105
2656
|
{
|
|
@@ -2123,13 +2674,13 @@ tw('bg-primary') // Uses your custom primary color
|
|
|
2123
2674
|
|
|
2124
2675
|
## Auto-Detection from tailwind.config.js
|
|
2125
2676
|
|
|
2126
|
-
|
|
2677
|
+
loopwind automatically detects and loads your project's Tailwind configuration:
|
|
2127
2678
|
|
|
2128
2679
|
```
|
|
2129
2680
|
your-project/
|
|
2130
2681
|
├── tailwind.config.js ← Automatically detected
|
|
2131
|
-
├──
|
|
2132
|
-
└──
|
|
2682
|
+
├── loopwind.json
|
|
2683
|
+
└── _loopwind/templates/
|
|
2133
2684
|
```
|
|
2134
2685
|
|
|
2135
2686
|
This includes:
|
|
@@ -2218,15 +2769,15 @@ export default function ModernCard({
|
|
|
2218
2769
|
FILE: fonts.mdx
|
|
2219
2770
|
================================================================================
|
|
2220
2771
|
|
|
2221
|
-
# Font Handling in
|
|
2772
|
+
# Font Handling in loopwind
|
|
2222
2773
|
|
|
2223
|
-
The recommended way to use fonts is through `
|
|
2774
|
+
The recommended way to use fonts is through `loopwind.json` - configure fonts once, use everywhere.
|
|
2224
2775
|
|
|
2225
|
-
## Using Fonts from
|
|
2776
|
+
## Using Fonts from loopwind.json (Recommended)
|
|
2226
2777
|
|
|
2227
|
-
Configure fonts in your `
|
|
2778
|
+
Configure fonts in your `loopwind.json` and use Tailwind classes in templates.
|
|
2228
2779
|
|
|
2229
|
-
### Simple Setup
|
|
2780
|
+
### Simple Setup
|
|
2230
2781
|
|
|
2231
2782
|
Define font families without loading custom fonts (uses system fonts):
|
|
2232
2783
|
|
|
@@ -2245,12 +2796,12 @@ Define font families without loading custom fonts (uses system fonts):
|
|
|
2245
2796
|
export default function({ title, tw }) {
|
|
2246
2797
|
return (
|
|
2247
2798
|
<div style={tw('w-full h-full')}>
|
|
2248
|
-
{/* Uses fonts.sans from
|
|
2799
|
+
{/* Uses fonts.sans from loopwind.json */}
|
|
2249
2800
|
<h1 style={tw('font-sans text-6xl font-bold')}>
|
|
2250
2801
|
{title}
|
|
2251
2802
|
</h1>
|
|
2252
2803
|
|
|
2253
|
-
{/* Uses fonts.mono from
|
|
2804
|
+
{/* Uses fonts.mono from loopwind.json */}
|
|
2254
2805
|
<code style={tw('font-mono text-sm')}>
|
|
2255
2806
|
{code}
|
|
2256
2807
|
</code>
|
|
@@ -2288,27 +2839,27 @@ Load custom font files for brand-specific typography:
|
|
|
2288
2839
|
**Project structure:**
|
|
2289
2840
|
```
|
|
2290
2841
|
your-project/
|
|
2291
|
-
├──
|
|
2842
|
+
├── loopwind.json
|
|
2292
2843
|
├── fonts/
|
|
2293
2844
|
│ ├── Inter-Regular.woff
|
|
2294
2845
|
│ ├── Inter-Bold.woff
|
|
2295
2846
|
│ └── JetBrainsMono-Regular.woff
|
|
2296
|
-
└──
|
|
2847
|
+
└── _loopwind/
|
|
2297
2848
|
└── templates/
|
|
2298
2849
|
```
|
|
2299
2850
|
|
|
2300
2851
|
**Template usage (same as before):**
|
|
2301
2852
|
```tsx
|
|
2302
2853
|
<h1 style={tw('font-sans font-bold')}>
|
|
2303
|
-
{/* Uses Inter Bold from
|
|
2854
|
+
{/* Uses Inter Bold from loopwind.json */}
|
|
2304
2855
|
{title}
|
|
2305
2856
|
</h1>
|
|
2306
2857
|
```
|
|
2307
2858
|
|
|
2308
2859
|
**Available classes:**
|
|
2309
|
-
- `font-sans` - Uses `fonts.sans` from
|
|
2310
|
-
- `font-serif` - Uses `fonts.serif` from
|
|
2311
|
-
- `font-mono` - Uses `fonts.mono` from
|
|
2860
|
+
- `font-sans` - Uses `fonts.sans` from loopwind.json
|
|
2861
|
+
- `font-serif` - Uses `fonts.serif` from loopwind.json
|
|
2862
|
+
- `font-mono` - Uses `fonts.mono` from loopwind.json
|
|
2312
2863
|
|
|
2313
2864
|
**Supported formats:**
|
|
2314
2865
|
- ✅ **WOFF** (`.woff`) - Recommended
|
|
@@ -2322,59 +2873,60 @@ For templates that need unique fonts not shared across the project:
|
|
|
2322
2873
|
|
|
2323
2874
|
**Template structure:**
|
|
2324
2875
|
```
|
|
2325
|
-
|
|
2326
|
-
├──
|
|
2327
|
-
├── meta.json
|
|
2876
|
+
_loopwind/templates/my-template/
|
|
2877
|
+
├── template.tsx
|
|
2328
2878
|
└── fonts/
|
|
2329
2879
|
└── SpecialFont.woff
|
|
2330
2880
|
```
|
|
2331
2881
|
|
|
2332
|
-
**
|
|
2333
|
-
```
|
|
2334
|
-
{
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
"
|
|
2882
|
+
**template.tsx:**
|
|
2883
|
+
```tsx
|
|
2884
|
+
export const meta = {
|
|
2885
|
+
name: "my-template",
|
|
2886
|
+
type: "image",
|
|
2887
|
+
size: { width: 1200, height: 630 },
|
|
2888
|
+
props: { title: "string" },
|
|
2889
|
+
fonts: [
|
|
2339
2890
|
{
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2891
|
+
name: "Special Font",
|
|
2892
|
+
path: "fonts/SpecialFont.woff",
|
|
2893
|
+
weight: 400,
|
|
2894
|
+
style: "normal"
|
|
2344
2895
|
}
|
|
2345
2896
|
]
|
|
2346
|
-
}
|
|
2347
|
-
```
|
|
2897
|
+
};
|
|
2348
2898
|
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
<h1 style={{ fontFamily: 'Special Font', fontWeight: 400 }}>
|
|
2352
|
-
|
|
2353
|
-
</h1>
|
|
2899
|
+
export default function Template({ title, tw }) {
|
|
2900
|
+
return (
|
|
2901
|
+
<h1 style={{ fontFamily: 'Special Font', fontWeight: 400 }}>
|
|
2902
|
+
{title}
|
|
2903
|
+
</h1>
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2354
2906
|
```
|
|
2355
2907
|
|
|
2356
2908
|
## Font Loading Priority
|
|
2357
2909
|
|
|
2358
|
-
|
|
2910
|
+
loopwind loads fonts in this order:
|
|
2359
2911
|
|
|
2360
|
-
1. **
|
|
2361
|
-
2. **Template meta
|
|
2912
|
+
1. **loopwind.json fonts** (if configured with `files`)
|
|
2913
|
+
2. **Template meta fonts** (if specified in `export const meta`)
|
|
2362
2914
|
3. **Default Noto Sans** (from CDN)
|
|
2363
2915
|
|
|
2364
|
-
This means
|
|
2916
|
+
This means loopwind.json fonts override template fonts, ensuring consistency.
|
|
2365
2917
|
|
|
2366
2918
|
## Default Fonts
|
|
2367
2919
|
|
|
2368
|
-
If no fonts are configured,
|
|
2920
|
+
If no fonts are configured, loopwind automatically fetches **Noto Sans** from jsDelivr CDN.
|
|
2369
2921
|
|
|
2370
2922
|
## Best Practices
|
|
2371
2923
|
|
|
2372
|
-
1. ✅ **Use
|
|
2924
|
+
1. ✅ **Use loopwind.json for project-wide fonts** - Configure once, use everywhere
|
|
2373
2925
|
2. ✅ **Use font classes** - `tw('font-sans')` instead of `fontFamily: 'Inter'`
|
|
2374
2926
|
3. ✅ **Include fallbacks** - Always add system fonts: `["Inter", "system-ui", "sans-serif"]`
|
|
2375
2927
|
4. ✅ **Match names** - First font in `family` array is used as the loaded font name
|
|
2376
|
-
5. ✅ **Relative paths** - Font paths are relative to `
|
|
2377
|
-
6. ⚠️ **Template fonts for special cases** - Only use meta
|
|
2928
|
+
5. ✅ **Relative paths** - Font paths are relative to `loopwind.json` location
|
|
2929
|
+
6. ⚠️ **Template fonts for special cases** - Only use template meta fonts for template-specific typography
|
|
2378
2930
|
|
|
2379
2931
|
## Examples
|
|
2380
2932
|
|
|
@@ -2446,3 +2998,83 @@ Loads different fonts for each style class.
|
|
|
2446
2998
|
- [Video Rendering](/video)
|
|
2447
2999
|
|
|
2448
3000
|
|
|
3001
|
+
|
|
3002
|
+
|
|
3003
|
+
================================================================================
|
|
3004
|
+
FILE: agents.mdx
|
|
3005
|
+
================================================================================
|
|
3006
|
+
|
|
3007
|
+
# AI Agents
|
|
3008
|
+
|
|
3009
|
+
loopwind is designed to work with AI coding assistants. Simply add the `_loopwind` folder to context.
|
|
3010
|
+
|
|
3011
|
+
## Using with AI Code Agents
|
|
3012
|
+
|
|
3013
|
+
In Cursor or Claude Code (Cline), type `@_loopwind` to add the folder to context, then ask:
|
|
3014
|
+
|
|
3015
|
+
```
|
|
3016
|
+
Generate an Open Graph image for my blog post about React hooks
|
|
3017
|
+
```
|
|
3018
|
+
|
|
3019
|
+
The AI will read the templates and generate the appropriate command.
|
|
3020
|
+
|
|
3021
|
+
## The AGENTS.md File
|
|
3022
|
+
|
|
3023
|
+
When you run `loopwind init`, a `_loopwind/AGENTS.md` file is created with:
|
|
3024
|
+
- Essential commands
|
|
3025
|
+
- Template helpers reference
|
|
3026
|
+
- Common workflows
|
|
3027
|
+
|
|
3028
|
+
This gives AI agents everything they need to use loopwind effectively.
|
|
3029
|
+
|
|
3030
|
+
## Example Workflows
|
|
3031
|
+
|
|
3032
|
+
### Simple Image Generation
|
|
3033
|
+
|
|
3034
|
+
**Prompt:**
|
|
3035
|
+
```
|
|
3036
|
+
Create a social media card using the og-image template:
|
|
3037
|
+
- Title: "10 React Tips"
|
|
3038
|
+
- Description: "Learn advanced patterns"
|
|
3039
|
+
```
|
|
3040
|
+
|
|
3041
|
+
**AI generates:**
|
|
3042
|
+
```bash
|
|
3043
|
+
loopwind render og-image '{"title":"10 React Tips","description":"Learn advanced patterns"}'
|
|
3044
|
+
```
|
|
3045
|
+
|
|
3046
|
+
**Output:** `_loopwind/outputs/og-image.png`
|
|
3047
|
+
|
|
3048
|
+
### Changelog Image
|
|
3049
|
+
|
|
3050
|
+
**Prompt:**
|
|
3051
|
+
```
|
|
3052
|
+
Create a changelog image for 3 recent changes:
|
|
3053
|
+
- Added video rendering support
|
|
3054
|
+
- Improved template validation
|
|
3055
|
+
- Fixed font loading issues
|
|
3056
|
+
```
|
|
3057
|
+
|
|
3058
|
+
**AI generates:**
|
|
3059
|
+
```bash
|
|
3060
|
+
loopwind render changelog-card '{
|
|
3061
|
+
"title":"Version 1.2.0",
|
|
3062
|
+
"changes":[
|
|
3063
|
+
"Added video rendering support",
|
|
3064
|
+
"Improved template validation",
|
|
3065
|
+
"Fixed font loading issues"
|
|
3066
|
+
]
|
|
3067
|
+
}'
|
|
3068
|
+
```
|
|
3069
|
+
|
|
3070
|
+
**Output:** `_loopwind/outputs/changelog-card.png`
|
|
3071
|
+
|
|
3072
|
+
## Next Steps
|
|
3073
|
+
|
|
3074
|
+
- [Learn about Templates](/templates)
|
|
3075
|
+
- [Image Rendering](/images)
|
|
3076
|
+
- [Video Rendering](/video)
|
|
3077
|
+
- [Built-in Helpers](/helpers)
|
|
3078
|
+
|
|
3079
|
+
|
|
3080
|
+
|