loopwind 0.9.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.
- package/FONTS.md +156 -0
- package/HELPERS_DEMO.md +134 -0
- package/PROJECT_STRUCTURE.md +286 -0
- package/PUBLISHING.md +171 -0
- package/README.md +1020 -0
- package/REGISTRY_SETUP.md +427 -0
- package/SHADCN_INTEGRATION.md +269 -0
- package/TAILWIND.md +228 -0
- package/TEMPLATE_SOURCES.md +363 -0
- package/_dsgn/templates/banner-hero/banner-hero.tsx +57 -0
- package/_dsgn/templates/banner-hero/meta.json +14 -0
- package/_dsgn/templates/composite-card/meta.json +16 -0
- package/_dsgn/templates/composite-card/template.tsx +44 -0
- package/_dsgn/templates/image/meta.json +13 -0
- package/_dsgn/templates/image/template.tsx +28 -0
- package/_dsgn/templates/kitchen-sink/meta.json +13 -0
- package/_dsgn/templates/kitchen-sink/template.tsx +72 -0
- package/_dsgn/templates/qr-card/meta.json +14 -0
- package/_dsgn/templates/qr-card/template.tsx +39 -0
- package/_dsgn/templates/test-parent/child/meta.json +11 -0
- package/_dsgn/templates/test-parent/child/template.tsx +27 -0
- package/_dsgn/templates/test-parent/meta.json +12 -0
- package/_dsgn/templates/test-parent/template.tsx +30 -0
- package/_dsgn/templates/test-sibling/meta.json +11 -0
- package/_dsgn/templates/test-sibling/template.tsx +20 -0
- package/_dsgn/templates/video/.tmp/template-1763421345296.mjs +43 -0
- package/_dsgn/templates/video/.tmp/template-1763421362228.mjs +43 -0
- package/_dsgn/templates/video/.tmp/template-1763421377706.mjs +43 -0
- package/_dsgn/templates/video/meta.json +17 -0
- package/_dsgn/templates/video/template.tsx +48 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +86 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/default.d.ts +2 -0
- package/dist/commands/default.d.ts.map +1 -0
- package/dist/commands/default.js +69 -0
- package/dist/commands/default.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +75 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +83 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/preview.d.ts +3 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +296 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/render.d.ts +10 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +204 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +107 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/default-templates/AGENTS.md +229 -0
- package/dist/default-templates/image/meta.json +13 -0
- package/dist/default-templates/image/template.d.ts +20 -0
- package/dist/default-templates/image/template.d.ts.map +1 -0
- package/dist/default-templates/image/template.js +18 -0
- package/dist/default-templates/image/template.js.map +1 -0
- package/dist/default-templates/image/template.tsx +20 -0
- package/dist/default-templates/image-template/meta.json +13 -0
- package/dist/default-templates/image-template/template.tsx +19 -0
- package/dist/default-templates/kitchen-sink/meta.json +13 -0
- package/dist/default-templates/kitchen-sink/template.tsx +64 -0
- package/dist/default-templates/page/meta.json +17 -0
- package/dist/default-templates/page/template.tsx +37 -0
- package/dist/default-templates/video/meta.json +17 -0
- package/dist/default-templates/video/template.d.ts +26 -0
- package/dist/default-templates/video/template.d.ts.map +1 -0
- package/dist/default-templates/video/template.js +33 -0
- package/dist/default-templates/video/template.js.map +1 -0
- package/dist/default-templates/video/template.tsx +37 -0
- package/dist/default-templates/video-template/meta.json +17 -0
- package/dist/default-templates/video-template/template.tsx +36 -0
- package/dist/default-templates/website/meta.json +16 -0
- package/dist/default-templates/website/pages/home.tsx +17 -0
- package/dist/default-templates/website/parts/footer.tsx +17 -0
- package/dist/default-templates/website/parts/header.tsx +17 -0
- package/dist/default-templates/website/template.tsx +17 -0
- package/dist/default-templates/website-template/meta.json +16 -0
- package/dist/default-templates/website-template/pages/home.tsx +16 -0
- package/dist/default-templates/website-template/parts/footer.tsx +16 -0
- package/dist/default-templates/website-template/parts/header.tsx +16 -0
- package/dist/default-templates/website-template/template.tsx +16 -0
- package/dist/lib/config.d.ts +34 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +248 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +7 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +12 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/helpers.d.ts +29 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +159 -0
- package/dist/lib/helpers.js.map +1 -0
- package/dist/lib/installer.d.ts +51 -0
- package/dist/lib/installer.d.ts.map +1 -0
- package/dist/lib/installer.js +215 -0
- package/dist/lib/installer.js.map +1 -0
- package/dist/lib/renderer.d.ts +51 -0
- package/dist/lib/renderer.d.ts.map +1 -0
- package/dist/lib/renderer.js +524 -0
- package/dist/lib/renderer.js.map +1 -0
- package/dist/lib/tailwind-config-loader.d.ts +47 -0
- package/dist/lib/tailwind-config-loader.d.ts.map +1 -0
- package/dist/lib/tailwind-config-loader.js +432 -0
- package/dist/lib/tailwind-config-loader.js.map +1 -0
- package/dist/lib/tailwind-detector.d.ts +36 -0
- package/dist/lib/tailwind-detector.d.ts.map +1 -0
- package/dist/lib/tailwind-detector.js +156 -0
- package/dist/lib/tailwind-detector.js.map +1 -0
- package/dist/lib/tailwind.d.ts +8 -0
- package/dist/lib/tailwind.d.ts.map +1 -0
- package/dist/lib/tailwind.js +994 -0
- package/dist/lib/tailwind.js.map +1 -0
- package/dist/lib/template-validator.d.ts +22 -0
- package/dist/lib/template-validator.d.ts.map +1 -0
- package/dist/lib/template-validator.js +174 -0
- package/dist/lib/template-validator.js.map +1 -0
- package/dist/lib/utils.d.ts +44 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +207 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/version-check.d.ts +16 -0
- package/dist/lib/version-check.d.ts.map +1 -0
- package/dist/lib/version-check.js +88 -0
- package/dist/lib/version-check.js.map +1 -0
- package/dist/lib/video-renderer.d.ts +32 -0
- package/dist/lib/video-renderer.d.ts.map +1 -0
- package/dist/lib/video-renderer.js +226 -0
- package/dist/lib/video-renderer.js.map +1 -0
- package/dist/sdk/index.d.ts +58 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +119 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/template.d.ts +40 -0
- package/dist/sdk/template.d.ts.map +1 -0
- package/dist/sdk/template.js +60 -0
- package/dist/sdk/template.js.map +1 -0
- package/dist/types/config.d.ts +62 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +47 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/template.d.ts +79 -0
- package/dist/types/template.d.ts.map +1 -0
- package/dist/types/template.js +2 -0
- package/dist/types/template.js.map +1 -0
- package/examples/nextjs-api/README.md +180 -0
- package/examples/nextjs-api/package.json +21 -0
- package/examples/nextjs-api/pages/api/intro-video.ts +53 -0
- package/examples/nextjs-api/pages/api/og-image.ts +50 -0
- package/netlify.toml +13 -0
- package/package.json +84 -0
- package/patches/satori+0.18.3.patch +13 -0
- package/test-templates/TESTS.md +63 -0
- package/test-templates/_dsgn/templates/absolute-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/absolute-spin/template.tsx +16 -0
- package/test-templates/_dsgn/templates/animated-intro/.tmp/template-1763468771640.mjs +7 -0
- package/test-templates/_dsgn/templates/animated-intro/meta.json +10 -0
- package/test-templates/_dsgn/templates/animated-intro/template.tsx +23 -0
- package/test-templates/_dsgn/templates/centered-spin/.tmp/template-1763468525386.mjs +7 -0
- package/test-templates/_dsgn/templates/centered-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/centered-spin/template.tsx +11 -0
- package/test-templates/_dsgn/templates/composite/.tmp/template-1763468815645.mjs +7 -0
- package/test-templates/_dsgn/templates/composite/meta.json +9 -0
- package/test-templates/_dsgn/templates/composite/template.tsx +23 -0
- package/test-templates/_dsgn/templates/easing-test/.tmp/template-1763468824501.mjs +7 -0
- package/test-templates/_dsgn/templates/easing-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/easing-test/template.tsx +47 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466364336.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466584319.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466667797.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466746504.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466930225.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467004552.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467060334.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467124493.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467174690.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467359134.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467451928.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467758275.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467985201.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468020563.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468090428.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468211036.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468394057.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/minimal-spin/template.tsx +13 -0
- package/test-templates/_dsgn/templates/no-origin-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/no-origin-spin/template.tsx +10 -0
- package/test-templates/_dsgn/templates/opacity-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/opacity-test/template.tsx +9 -0
- package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468758954.mjs +17 -0
- package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468815672.mjs +17 -0
- package/test-templates/_dsgn/templates/qr-code/meta.json +9 -0
- package/test-templates/_dsgn/templates/qr-code/template.tsx +20 -0
- package/test-templates/_dsgn/templates/rotation-abs-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/rotation-abs-test/template.tsx +15 -0
- package/test-templates/_dsgn/templates/rotation-corner/meta.json +7 -0
- package/test-templates/_dsgn/templates/rotation-corner/template.tsx +12 -0
- package/test-templates/_dsgn/templates/rotation-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/rotation-test/template.tsx +12 -0
- package/test-templates/_dsgn/templates/shake-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/shake-test/template.tsx +12 -0
- package/test-templates/_dsgn/templates/static-image/.tmp/template-1763468746271.mjs +7 -0
- package/test-templates/_dsgn/templates/static-image/meta.json +9 -0
- package/test-templates/_dsgn/templates/static-image/template.tsx +19 -0
- package/test-templates/_dsgn/templates/translate-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/translate-test/template.tsx +9 -0
- package/test-templates/_dsgn/templates/video-loops/.tmp/template-1763468793192.mjs +15 -0
- package/test-templates/_dsgn/templates/video-loops/meta.json +9 -0
- package/test-templates/_dsgn/templates/video-loops/template.tsx +39 -0
- package/test-templates/_dsgn/templates/wrapped-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/wrapped-spin/template.tsx +17 -0
- package/test-templates/compare-svgs.mjs +30 -0
- package/test-templates/convert-frames.mjs +15 -0
- package/test-templates/debug-rotation.mjs +25 -0
- package/test-templates/run-tests.sh +39 -0
- package/test-templates/test-sdk.mjs +115 -0
- package/website/.astro/settings.json +5 -0
- package/website/.astro/types.d.ts +1 -0
- package/website/README.md +112 -0
- package/website/astro.config.mjs +18 -0
- package/website/dist/_astro/fonts.DHdiHGBO.css +1 -0
- package/website/dist/fonts/index.html +193 -0
- package/website/dist/helpers/index.html +166 -0
- package/website/dist/images/index.html +314 -0
- package/website/dist/index.html +219 -0
- package/website/dist/llm.txt +2448 -0
- package/website/dist/styling/index.html +365 -0
- package/website/dist/templates/index.html +124 -0
- package/website/dist/video/index.html +636 -0
- package/website/package-lock.json +7606 -0
- package/website/package.json +23 -0
- package/website/public/robots.txt +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
# dsgn
|
|
2
|
+
|
|
3
|
+
> The Shadcn for design and marketing. A template-based CLI tool for generating images and videos using React + Tailwind CSS + Satori.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **shadcn/ui Design System**: Beautiful, semantic colors out of the box (`text-primary`, `bg-card`, etc.)
|
|
8
|
+
- ✨ **Template-based**: Install design templates like you install UI components
|
|
9
|
+
- 🖼️ **Serverless Image Rendering**: Pure JavaScript rendering with Satori - works on Vercel, Netlify, Cloudflare Workers
|
|
10
|
+
- 🎬 **Serverless Video Rendering**: WASM-based MP4 encoding - 12x faster than traditional approaches
|
|
11
|
+
- 🎨 **Tailwind CSS Support**: Style templates with Tailwind utility classes + opacity modifiers (`bg-primary/50`)
|
|
12
|
+
- 📱 **Built-in Helpers**: QR codes, image embedding, video backgrounds, template composition
|
|
13
|
+
- ✅ **Smart Validation**: Automatic prop and template validation with helpful error messages
|
|
14
|
+
- 🚀 **Framework-agnostic**: Works with Node, Bun, Deno, Laravel, Python, and more
|
|
15
|
+
- 🤖 **Agent-friendly**: Machine-readable metadata for LLMs
|
|
16
|
+
- 📦 **Easy installation**: `npx dsgn add template-name`
|
|
17
|
+
- 🌐 **Pure JavaScript/WASM**: No native dependencies, works everywhere
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g dsgn
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or use with npx:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx dsgn --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Install a Template
|
|
34
|
+
|
|
35
|
+
Templates can be installed from multiple sources:
|
|
36
|
+
|
|
37
|
+
#### 1. Official Registry (Coming Soon)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dsgn add banner-hero
|
|
41
|
+
dsgn add og-image --registry https://custom-registry.com/r
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### 2. GitHub Repositories
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# From a GitHub repo (looks for template.json in repo root)
|
|
48
|
+
dsgn add github:username/repo
|
|
49
|
+
|
|
50
|
+
# From a specific path in the repo
|
|
51
|
+
dsgn add github:username/repo/templates/banner-hero
|
|
52
|
+
|
|
53
|
+
# From an organization
|
|
54
|
+
dsgn add github:myorg/design-templates/social-media/og-image
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**How it works:**
|
|
58
|
+
- Converts to raw GitHub URLs
|
|
59
|
+
- Fetches `template.json` from the specified path
|
|
60
|
+
- Downloads all referenced files
|
|
61
|
+
- Uses `main` branch by default
|
|
62
|
+
|
|
63
|
+
#### 3. Direct URLs
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Install from any publicly accessible URL
|
|
67
|
+
dsgn add https://example.com/templates/my-template.json
|
|
68
|
+
dsgn add https://cdn.example.com/templates/awesome-banner.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Requirements:**
|
|
72
|
+
- URL must return JSON in the registry template format
|
|
73
|
+
- Must be publicly accessible (no authentication)
|
|
74
|
+
|
|
75
|
+
#### 4. Local Filesystem
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Relative path
|
|
79
|
+
dsgn add ./my-templates/banner-hero
|
|
80
|
+
dsgn add ../shared-templates/product-card
|
|
81
|
+
|
|
82
|
+
# Absolute path
|
|
83
|
+
dsgn add /Users/you/templates/social-card
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Use cases:**
|
|
87
|
+
- Development and testing
|
|
88
|
+
- Private templates
|
|
89
|
+
- Shared team templates (monorepo)
|
|
90
|
+
- Before publishing to registry
|
|
91
|
+
|
|
92
|
+
Templates are installed to: `_dsgn/templates/<template>/` (customizable in `dsgn.json`)
|
|
93
|
+
|
|
94
|
+
**Benefits:**
|
|
95
|
+
- Templates are local to your project (like npm packages)
|
|
96
|
+
- Different projects can use different template versions
|
|
97
|
+
- Version controlled with your project
|
|
98
|
+
- Easy to share within your team
|
|
99
|
+
- Works offline once installed
|
|
100
|
+
|
|
101
|
+
See `REGISTRY_SETUP.md` to create your own registry.
|
|
102
|
+
|
|
103
|
+
### Render an Image
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
dsgn render banner-hero \
|
|
107
|
+
--props '{"title":"Hello World","subtitle":"Welcome to dsgn"}'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
By default, images are saved to `dsgn/outputs/` in your current project.
|
|
111
|
+
|
|
112
|
+
You can specify a custom output path:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
dsgn render banner-hero \
|
|
116
|
+
--props '{"title":"Hello World","subtitle":"Welcome to dsgn"}' \
|
|
117
|
+
--out custom/path/banner.png
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Or use a props file:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# props.json
|
|
124
|
+
{
|
|
125
|
+
"title": "Hello World",
|
|
126
|
+
"subtitle": "Welcome to dsgn"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
dsgn render banner-hero --props props.json --out banner.png
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Output formats:**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# PNG (default) - best for photos and complex gradients
|
|
136
|
+
dsgn render banner-hero --props props.json --format png -o banner.png
|
|
137
|
+
|
|
138
|
+
# SVG - scalable vector, smaller file size, perfect for template composition
|
|
139
|
+
dsgn render banner-hero --props props.json --format svg -o banner.svg
|
|
140
|
+
|
|
141
|
+
# WebP - modern format, smaller than PNG with similar quality
|
|
142
|
+
dsgn render banner-hero --props props.json --format webp -o banner.webp
|
|
143
|
+
|
|
144
|
+
# JPEG - smallest file size, good for photos
|
|
145
|
+
dsgn render banner-hero --props props.json --format jpg -o banner.jpg
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**SVG benefits with template composition:**
|
|
149
|
+
- Smaller file size (typically 3-4x smaller than PNG)
|
|
150
|
+
- Infinitely scalable without quality loss
|
|
151
|
+
- Embedded templates remain as native SVG elements
|
|
152
|
+
- Can be edited in design tools (Figma, Illustrator)
|
|
153
|
+
|
|
154
|
+
### List Installed Templates
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
dsgn list
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Validate Templates
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
dsgn validate
|
|
164
|
+
dsgn validate banner-hero
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## SDK for Programmatic Use
|
|
168
|
+
|
|
169
|
+
Use **dsgn** programmatically in your Next.js API routes, serverless functions, or Node.js applications!
|
|
170
|
+
|
|
171
|
+
### Installation
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm install dsgn
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Quick Example
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { defineTemplate, renderImage } from 'dsgn/sdk';
|
|
181
|
+
|
|
182
|
+
// Define template programmatically
|
|
183
|
+
const ogTemplate = defineTemplate({
|
|
184
|
+
name: 'og-image',
|
|
185
|
+
size: { width: 1200, height: 630 },
|
|
186
|
+
render: ({ tw, title, description }) => (
|
|
187
|
+
<div style={tw('flex flex-col w-full h-full bg-gradient-to-br from-blue-600 to-purple-700 p-12')}>
|
|
188
|
+
<h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
|
|
189
|
+
<p style={tw('text-2xl text-white/80')}>{description}</p>
|
|
190
|
+
</div>
|
|
191
|
+
),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Render to buffer
|
|
195
|
+
const png = await renderImage(ogTemplate, {
|
|
196
|
+
title: 'Hello World',
|
|
197
|
+
description: 'Generated with dsgn SDK',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Use in Next.js API route
|
|
201
|
+
export default async function handler(req, res) {
|
|
202
|
+
const png = await renderImage(ogTemplate, req.query);
|
|
203
|
+
res.setHeader('Content-Type', 'image/png');
|
|
204
|
+
res.send(png);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Video Rendering
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { defineTemplate, renderVideo } from 'dsgn/sdk';
|
|
212
|
+
|
|
213
|
+
const introTemplate = defineTemplate({
|
|
214
|
+
name: 'intro-video',
|
|
215
|
+
type: 'video',
|
|
216
|
+
size: { width: 1920, height: 1080 },
|
|
217
|
+
video: { fps: 30, duration: 3 },
|
|
218
|
+
render: ({ tw, progress, title }) => {
|
|
219
|
+
const opacity = progress; // 0 to 1 animation
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-black')}>
|
|
223
|
+
<h1 style={{ ...tw('text-8xl font-bold text-white'), opacity }}>
|
|
224
|
+
{title}
|
|
225
|
+
</h1>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const mp4 = await renderVideo(introTemplate, { title: 'Welcome!' });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Key Features
|
|
235
|
+
|
|
236
|
+
- ✅ **No file system dependencies** - templates defined in code
|
|
237
|
+
- ✅ **Returns buffers** - integrate with any framework or storage
|
|
238
|
+
- ✅ **Serverless-first** - works on Vercel, Netlify, Cloudflare Workers
|
|
239
|
+
- ✅ **Full TypeScript support** - type-safe props and templates
|
|
240
|
+
- ✅ **Same Tailwind + shadcn/ui styling** - consistent with CLI
|
|
241
|
+
|
|
242
|
+
### Use Cases
|
|
243
|
+
|
|
244
|
+
- 🖼️ **Dynamic OG images** for blogs and marketing sites
|
|
245
|
+
- 🎬 **Programmatic videos** for social media automation
|
|
246
|
+
- 📧 **Email images** generated on-the-fly
|
|
247
|
+
- 🎨 **User-generated content** like certificates, cards, badges
|
|
248
|
+
- 🔄 **Batch processing** of images or videos
|
|
249
|
+
|
|
250
|
+
### Example: Next.js API Route
|
|
251
|
+
|
|
252
|
+
See the full example in [`examples/nextjs-api/`](examples/nextjs-api/)
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// pages/api/og-image.ts
|
|
256
|
+
import { defineTemplate, renderImage } from 'dsgn/sdk';
|
|
257
|
+
|
|
258
|
+
const template = defineTemplate({
|
|
259
|
+
name: 'og-image',
|
|
260
|
+
size: { width: 1200, height: 630 },
|
|
261
|
+
render: ({ tw, title }) => (
|
|
262
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-white')}>
|
|
263
|
+
<h1 style={tw('text-6xl font-bold')}>{title}</h1>
|
|
264
|
+
</div>
|
|
265
|
+
),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export default async function handler(req, res) {
|
|
269
|
+
const png = await renderImage(template, req.query);
|
|
270
|
+
res.setHeader('Content-Type', 'image/png');
|
|
271
|
+
res.setHeader('Cache-Control', 's-maxage=86400');
|
|
272
|
+
res.send(png);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Usage:**
|
|
277
|
+
```
|
|
278
|
+
https://yoursite.com/api/og-image?title=Hello+World
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## shadcn/ui Design System
|
|
282
|
+
|
|
283
|
+
dsgn uses **shadcn/ui's design system** by default! All templates have access to semantic color tokens:
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
// Use shadcn colors in templates
|
|
287
|
+
<div style={tw('bg-card border rounded-xl p-16')}>
|
|
288
|
+
<h1 style={tw('text-foreground font-bold')}>Title</h1>
|
|
289
|
+
<p style={tw('text-muted-foreground')}>Subtitle</p>
|
|
290
|
+
</div>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Supported shadcn classes:**
|
|
294
|
+
- `text-primary`, `bg-secondary`, `text-muted-foreground`, `bg-card`, `border`, `bg-destructive`
|
|
295
|
+
- **Opacity modifiers**: `bg-primary/50`, `text-muted-foreground/75`, `border/30`
|
|
296
|
+
- **Dark mode ready**: Override colors in `dsgn.json`
|
|
297
|
+
|
|
298
|
+
See `SHADCN_INTEGRATION.md` for complete documentation.
|
|
299
|
+
|
|
300
|
+
## Configuration (dsgn.json)
|
|
301
|
+
|
|
302
|
+
Templates automatically use your project's design tokens defined in `dsgn.json`. This allows generated images to match your brand colors and design system.
|
|
303
|
+
|
|
304
|
+
### Initialize Config
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
dsgn init
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
This creates a `dsgn.json` file in your project:
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"colors": {
|
|
315
|
+
"primary": "#667eea",
|
|
316
|
+
"secondary": "#764ba2",
|
|
317
|
+
"background": "#ffffff",
|
|
318
|
+
"foreground": "#000000",
|
|
319
|
+
"muted": "#f3f4f6",
|
|
320
|
+
"accent": "#3b82f6",
|
|
321
|
+
"destructive": "#ef4444",
|
|
322
|
+
"border": "#e5e7eb"
|
|
323
|
+
},
|
|
324
|
+
"fonts": {
|
|
325
|
+
"sans": ["Inter", "system-ui", "sans-serif"],
|
|
326
|
+
"mono": ["JetBrains Mono", "monospace"]
|
|
327
|
+
},
|
|
328
|
+
"tokens": {
|
|
329
|
+
"borderRadius": {
|
|
330
|
+
"sm": "0.25rem",
|
|
331
|
+
"md": "0.5rem",
|
|
332
|
+
"lg": "1rem",
|
|
333
|
+
"xl": "1.5rem"
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
"defaults": {
|
|
337
|
+
"width": 1200,
|
|
338
|
+
"height": 630
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Using Config in Templates
|
|
344
|
+
|
|
345
|
+
Templates receive the config via props:
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
export default function MyTemplate({ title, config }) {
|
|
349
|
+
const primaryColor = config?.colors?.primary || '#000';
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div style={{ background: primaryColor }}>
|
|
353
|
+
{title}
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
This ensures your generated images use the same colors as your Tailwind/Shadcn setup!
|
|
360
|
+
|
|
361
|
+
## Using Tailwind Classes
|
|
362
|
+
|
|
363
|
+
Templates can use Tailwind utility classes for styling! The `tw()` function is automatically provided:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
export default function MyTemplate({ title, tw }) {
|
|
367
|
+
return (
|
|
368
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-primary text-white')}>
|
|
369
|
+
<h1 style={tw('text-6xl font-bold')}>
|
|
370
|
+
{title}
|
|
371
|
+
</h1>
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Combine with custom styles:
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
<div
|
|
381
|
+
style={{
|
|
382
|
+
...tw('flex flex-col p-20 text-white'),
|
|
383
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
384
|
+
}}
|
|
385
|
+
>
|
|
386
|
+
<h1 style={tw('text-8xl font-bold')}>{title}</h1>
|
|
387
|
+
</div>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Automatic Tailwind Config Support
|
|
391
|
+
|
|
392
|
+
**dsgn automatically detects and uses your `tailwind.config.js`!**
|
|
393
|
+
|
|
394
|
+
If you have a Tailwind project:
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
// tailwind.config.js
|
|
398
|
+
export default {
|
|
399
|
+
theme: {
|
|
400
|
+
extend: {
|
|
401
|
+
colors: {
|
|
402
|
+
primary: '#667eea',
|
|
403
|
+
brand: {
|
|
404
|
+
500: '#0ea5e9',
|
|
405
|
+
900: '#0c4a6e',
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
spacing: {
|
|
409
|
+
'72': '18rem',
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Your templates can use these values:
|
|
417
|
+
|
|
418
|
+
```tsx
|
|
419
|
+
<div style={tw('bg-primary text-white p-72')}>
|
|
420
|
+
<div style={tw('bg-brand-500')}>Custom brand color!</div>
|
|
421
|
+
</div>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Config priority:**
|
|
425
|
+
1. `tailwind.config.js` (if exists)
|
|
426
|
+
2. `dsgn.json` (fallback)
|
|
427
|
+
3. Default Tailwind values
|
|
428
|
+
|
|
429
|
+
**Supported classes:**
|
|
430
|
+
- Layout: `flex`, `flex-col`, `items-center`, `justify-between`, etc.
|
|
431
|
+
- Spacing: `p-4`, `px-8`, `m-6`, `gap-4`, etc.
|
|
432
|
+
- Typography: `text-6xl`, `font-bold`, `text-center`, etc.
|
|
433
|
+
- Colors: `bg-primary`, `text-brand-500`, etc. (from your config!)
|
|
434
|
+
- Effects: `opacity-90`, `rounded-xl`, etc.
|
|
435
|
+
- shadcn/ui colors: `bg-card`, `text-foreground`, `text-muted-foreground`
|
|
436
|
+
- Opacity modifiers: `bg-primary/50`, `text-muted-foreground/75`
|
|
437
|
+
|
|
438
|
+
**Note:** Tailwind v4 uses CSS variables instead of JS config. We're working on CSS parsing support. For now, use `dsgn.json` or `tailwind.config.js` (v3 format).
|
|
439
|
+
|
|
440
|
+
## Fonts
|
|
441
|
+
|
|
442
|
+
Templates can use **default fonts** (from CDN) or **bundle custom fonts** for brand-specific typography.
|
|
443
|
+
|
|
444
|
+
### Default Fonts
|
|
445
|
+
|
|
446
|
+
By default, templates use **Noto Sans** fetched from jsDelivr CDN. No setup required!
|
|
447
|
+
|
|
448
|
+
### Custom Fonts
|
|
449
|
+
|
|
450
|
+
Templates can bundle their own fonts (WOFF, WOFF2, TTF, OTF):
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
my-template/
|
|
454
|
+
├── my-template.tsx
|
|
455
|
+
├── meta.json
|
|
456
|
+
└── fonts/
|
|
457
|
+
├── CustomFont-Regular.woff
|
|
458
|
+
└── CustomFont-Bold.woff
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**meta.json:**
|
|
462
|
+
```json
|
|
463
|
+
{
|
|
464
|
+
"name": "my-template",
|
|
465
|
+
"fonts": [
|
|
466
|
+
{
|
|
467
|
+
"name": "Custom Font",
|
|
468
|
+
"path": "fonts/CustomFont-Bold.woff",
|
|
469
|
+
"weight": 700,
|
|
470
|
+
"style": "normal"
|
|
471
|
+
}
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Template usage:**
|
|
477
|
+
```tsx
|
|
478
|
+
<h1 style={{ fontFamily: 'Custom Font', fontWeight: 700 }}>
|
|
479
|
+
Branded Typography
|
|
480
|
+
</h1>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Use cases:**
|
|
484
|
+
- Brand-specific fonts (company guidelines)
|
|
485
|
+
- Offline rendering (no CDN dependency)
|
|
486
|
+
- Special display typography
|
|
487
|
+
|
|
488
|
+
## Template Helpers
|
|
489
|
+
|
|
490
|
+
Templates have access to built-in helper functions:
|
|
491
|
+
|
|
492
|
+
- **`tw(classes)`** - Convert Tailwind classes to inline styles
|
|
493
|
+
- **`qr(text, options?)`** - Generate QR codes from text/URLs
|
|
494
|
+
- **`image(propKey)`** - Embed images as data URIs
|
|
495
|
+
- **`video(propKey)`** - Embed video frames (for video templates)
|
|
496
|
+
- **`template(name, props)`** - Embed other templates
|
|
497
|
+
- **`config`** - Access dsgn.json configuration
|
|
498
|
+
|
|
499
|
+
### QR Code Helper
|
|
500
|
+
|
|
501
|
+
Generate QR codes from any text or URL using the `qr()` helper:
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
export default function Template({ title, url, qr, tw }) {
|
|
505
|
+
return (
|
|
506
|
+
<div style={tw('flex flex-col items-center p-16')}>
|
|
507
|
+
<h1 style={tw('text-5xl font-bold mb-8')}>{title}</h1>
|
|
508
|
+
|
|
509
|
+
{/* QR code auto-generated from url prop */}
|
|
510
|
+
<img src={qr(url)} width={200} height={200} alt="QR Code" />
|
|
511
|
+
|
|
512
|
+
<p style={tw('text-xl mt-4')}>{url}</p>
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Usage:**
|
|
519
|
+
```bash
|
|
520
|
+
dsgn render qr-card '{
|
|
521
|
+
"title": "Visit Our Website",
|
|
522
|
+
"url": "https://dsgncli.com"
|
|
523
|
+
}'
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Customization:**
|
|
527
|
+
```tsx
|
|
528
|
+
// Custom size and error correction
|
|
529
|
+
<img src={qr(url, {
|
|
530
|
+
width: 300,
|
|
531
|
+
margin: 2,
|
|
532
|
+
errorCorrectionLevel: 'H', // L, M, Q, H (higher = more error correction)
|
|
533
|
+
color: {
|
|
534
|
+
dark: '#000000',
|
|
535
|
+
light: '#FFFFFF'
|
|
536
|
+
}
|
|
537
|
+
})} />
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**How it works:**
|
|
541
|
+
- All string props are automatically pre-generated as QR codes
|
|
542
|
+
- Call `qr()` with any prop value (url, text, etc.)
|
|
543
|
+
- Returns a data URI that can be used directly in `<img src={...} />`
|
|
544
|
+
- Cached per render for performance
|
|
545
|
+
|
|
546
|
+
**Use cases:**
|
|
547
|
+
- Event tickets with QR codes
|
|
548
|
+
- Business cards with contact info
|
|
549
|
+
- Marketing materials with campaign URLs
|
|
550
|
+
- Product labels with tracking codes
|
|
551
|
+
|
|
552
|
+
### Image Helper
|
|
553
|
+
|
|
554
|
+
Embed images (jpg, png, gif, webp, svg) as data URIs using the `image()` helper:
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
export default function Banner({ background, tw, image }) {
|
|
558
|
+
return (
|
|
559
|
+
<div style={tw('relative w-full h-full')}>
|
|
560
|
+
<img src={image('background')} style={tw('absolute inset-0 w-full h-full object-cover')} />
|
|
561
|
+
<h1 style={tw('relative text-6xl font-bold text-white')}>Hello World</h1>
|
|
562
|
+
</div>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Props format:**
|
|
568
|
+
```json
|
|
569
|
+
{
|
|
570
|
+
"background": "./path/to/image.jpg"
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Usage:**
|
|
575
|
+
```bash
|
|
576
|
+
dsgn render banner '{
|
|
577
|
+
"background": "./my-background.jpg"
|
|
578
|
+
}'
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**How it works:**
|
|
582
|
+
- The renderer auto-detects props ending in image extensions (.jpg, .png, .gif, .webp, .svg)
|
|
583
|
+
- Images are pre-loaded and converted to data URIs
|
|
584
|
+
- Call `image('propKey')` to get the data URI for that prop
|
|
585
|
+
- Works with any image format
|
|
586
|
+
|
|
587
|
+
**Use cases:**
|
|
588
|
+
- Background images in banners
|
|
589
|
+
- Logos and brand elements
|
|
590
|
+
- Product photos in cards
|
|
591
|
+
- Avatar images
|
|
592
|
+
|
|
593
|
+
### Video Helper
|
|
594
|
+
|
|
595
|
+
For video templates, embed video frames synchronized to the current animation frame using the `video()` helper:
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
export default function VideoOverlay({ background, title, tw, video, frame, progress }) {
|
|
599
|
+
const opacity = progress < 0.2 ? progress / 0.2 : 1;
|
|
600
|
+
|
|
601
|
+
return (
|
|
602
|
+
<div style={tw('relative w-full h-full')}>
|
|
603
|
+
{/* Background video - auto-syncs to current frame */}
|
|
604
|
+
<img
|
|
605
|
+
src={video('background')}
|
|
606
|
+
style={tw('absolute inset-0 w-full h-full object-cover')}
|
|
607
|
+
/>
|
|
608
|
+
|
|
609
|
+
{/* Animated overlay */}
|
|
610
|
+
<h1
|
|
611
|
+
style={{
|
|
612
|
+
...tw('relative text-8xl font-bold text-white'),
|
|
613
|
+
opacity
|
|
614
|
+
}}
|
|
615
|
+
>
|
|
616
|
+
{title}
|
|
617
|
+
</h1>
|
|
618
|
+
</div>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Props format:**
|
|
624
|
+
```json
|
|
625
|
+
{
|
|
626
|
+
"title": "My Video",
|
|
627
|
+
"background": "./my-video.mp4"
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**Usage:**
|
|
632
|
+
```bash
|
|
633
|
+
dsgn render video-overlay props.json --out output.mp4
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**How it works:**
|
|
637
|
+
1. First render detects video props (auto-detects .mp4, .mov, etc.)
|
|
638
|
+
2. Extracts all frames at template's FPS
|
|
639
|
+
3. Returns frame matching current template frame number
|
|
640
|
+
4. Frames cached in memory for fast access
|
|
641
|
+
|
|
642
|
+
**Use cases:**
|
|
643
|
+
- Video backgrounds with text overlays
|
|
644
|
+
- Adding titles/captions to existing videos
|
|
645
|
+
- Combining multiple video sources
|
|
646
|
+
- Dynamic watermarking
|
|
647
|
+
|
|
648
|
+
### Template Composition Helper
|
|
649
|
+
|
|
650
|
+
Embed one template inside another using the `template()` helper. This is perfect for creating composite designs where smaller templates are reused as building blocks.
|
|
651
|
+
|
|
652
|
+
**Just call template() - no declaration needed:**
|
|
653
|
+
```tsx
|
|
654
|
+
export default function Template({ title, bannerTitle, bannerSubtitle, template, tw }) {
|
|
655
|
+
return (
|
|
656
|
+
<div style={tw('w-full h-full flex flex-col bg-slate-900 p-12')}>
|
|
657
|
+
<div style={tw('bg-white rounded-3xl p-10 flex flex-col')}>
|
|
658
|
+
<h1 style={tw('text-5xl font-bold mb-8')}>{title}</h1>
|
|
659
|
+
|
|
660
|
+
{/* Embed any template - auto-loaded on first render */}
|
|
661
|
+
<div style={tw('rounded-xl overflow-hidden flex')}>
|
|
662
|
+
{template('image', { title: bannerTitle, subtitle: bannerSubtitle })}
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**Usage:**
|
|
671
|
+
```bash
|
|
672
|
+
dsgn render composite-card '{
|
|
673
|
+
"title": "Product Launch",
|
|
674
|
+
"bannerTitle": "New Release",
|
|
675
|
+
"bannerSubtitle": "Coming Soon"
|
|
676
|
+
}'
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**How it works:**
|
|
680
|
+
- **No declaration needed** - just call `template('any-template-name')`
|
|
681
|
+
- First render pass detects which templates are needed
|
|
682
|
+
- All templates load in parallel automatically
|
|
683
|
+
- Second render pass completes with loaded templates
|
|
684
|
+
- Each template uses its own `meta.json` for sizing
|
|
685
|
+
- Returns JSX directly (not a data URI)
|
|
686
|
+
- Satori renders everything as a single SVG tree
|
|
687
|
+
|
|
688
|
+
**Pass props directly:**
|
|
689
|
+
```tsx
|
|
690
|
+
{template('image', { title: 'Custom Title', subtitle: 'Custom Subtitle' })}
|
|
691
|
+
{template('qr-card', { url: 'https://example.com', title: 'Scan Me' })}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**Use cases:**
|
|
695
|
+
- Composite social media graphics with reusable headers/footers
|
|
696
|
+
- Multi-panel infographics
|
|
697
|
+
- Email templates with consistent branding elements
|
|
698
|
+
- Product cards with embedded badges or labels
|
|
699
|
+
- Presentations with reusable slide components
|
|
700
|
+
|
|
701
|
+
## Template Structure
|
|
702
|
+
|
|
703
|
+
Templates are TSX files with metadata. Each template can be either an **image** or a **video** template (not both).
|
|
704
|
+
|
|
705
|
+
### Image Template
|
|
706
|
+
|
|
707
|
+
```tsx
|
|
708
|
+
export const meta = {
|
|
709
|
+
name: "banner-hero",
|
|
710
|
+
description: "Hero banner with shadcn design",
|
|
711
|
+
type: "image", // Optional, defaults to "image"
|
|
712
|
+
size: { width: 1600, height: 900 },
|
|
713
|
+
props: {
|
|
714
|
+
title: "string",
|
|
715
|
+
subtitle: "string?"
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
export default function Template({ title, subtitle, tw, qr, template }) {
|
|
720
|
+
return (
|
|
721
|
+
<div style={tw('w-full h-full flex flex-col justify-center p-20 bg-background')}>
|
|
722
|
+
<div style={tw('bg-card border rounded-xl p-16 flex flex-col')}>
|
|
723
|
+
<h1 style={tw('text-8xl font-bold text-foreground')}>{title}</h1>
|
|
724
|
+
{subtitle && (
|
|
725
|
+
<p style={tw('text-4xl text-muted-foreground/75')}>{subtitle}</p>
|
|
726
|
+
)}
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Video Template
|
|
734
|
+
|
|
735
|
+
```tsx
|
|
736
|
+
export const meta = {
|
|
737
|
+
name: "animated-banner",
|
|
738
|
+
description: "Animated hero banner with shadcn design",
|
|
739
|
+
type: "video",
|
|
740
|
+
size: { width: 1600, height: 900 },
|
|
741
|
+
props: {
|
|
742
|
+
title: "string",
|
|
743
|
+
subtitle: "string?"
|
|
744
|
+
},
|
|
745
|
+
video: {
|
|
746
|
+
fps: 30,
|
|
747
|
+
duration: 3
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
export default function Template({ title, subtitle, frame, progress, tw, qr, template }) {
|
|
752
|
+
const opacity = Math.min(1, progress * 2);
|
|
753
|
+
const translateY = 20 - (progress * 20);
|
|
754
|
+
|
|
755
|
+
return (
|
|
756
|
+
<div style={tw('w-full h-full flex flex-col justify-center items-center bg-background p-20')}>
|
|
757
|
+
<div style={tw('bg-card border rounded-xl p-16 flex flex-col')}>
|
|
758
|
+
<h1
|
|
759
|
+
style={{
|
|
760
|
+
...tw('text-9xl font-bold text-foreground'),
|
|
761
|
+
opacity,
|
|
762
|
+
transform: `translateY(${translateY}px)`
|
|
763
|
+
}}
|
|
764
|
+
>
|
|
765
|
+
{title}
|
|
766
|
+
</h1>
|
|
767
|
+
{subtitle && (
|
|
768
|
+
<p style={tw('text-5xl text-muted-foreground/75 mt-6')}>
|
|
769
|
+
{subtitle}
|
|
770
|
+
</p>
|
|
771
|
+
)}
|
|
772
|
+
</div>
|
|
773
|
+
</div>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
## Commands
|
|
779
|
+
|
|
780
|
+
### `dsgn init`
|
|
781
|
+
|
|
782
|
+
Initialize a `dsgn.json` config file with default design tokens.
|
|
783
|
+
|
|
784
|
+
### `dsgn add <template>`
|
|
785
|
+
|
|
786
|
+
Install a template from the registry.
|
|
787
|
+
|
|
788
|
+
Options:
|
|
789
|
+
- `-r, --registry <url>` - Custom registry URL
|
|
790
|
+
|
|
791
|
+
### `dsgn list`
|
|
792
|
+
|
|
793
|
+
List all installed templates with metadata.
|
|
794
|
+
|
|
795
|
+
### `dsgn render <template>`
|
|
796
|
+
|
|
797
|
+
Render a template to an image.
|
|
798
|
+
|
|
799
|
+
Options:
|
|
800
|
+
- `-p, --props <file>` - Props file (JSON)
|
|
801
|
+
- `-o, --out <file>` - Output file path
|
|
802
|
+
- `--format <format>` - Output format: png, svg, webp (default: png)
|
|
803
|
+
- `--video` - Render as video (coming soon)
|
|
804
|
+
|
|
805
|
+
### `dsgn validate [template]`
|
|
806
|
+
|
|
807
|
+
Validate template metadata. If no template is specified, validates all installed templates.
|
|
808
|
+
|
|
809
|
+
## Validation & Error Messages
|
|
810
|
+
|
|
811
|
+
dsgn automatically validates templates and props before rendering, providing helpful error messages.
|
|
812
|
+
|
|
813
|
+
### Automatic Validation
|
|
814
|
+
|
|
815
|
+
```bash
|
|
816
|
+
# Missing required props
|
|
817
|
+
$ dsgn render banner-hero --props '{}'
|
|
818
|
+
✖ Template validation failed
|
|
819
|
+
✗ props: Missing required prop: "title"
|
|
820
|
+
→ Add "title" to your props: --props '{"title":"value"}'
|
|
821
|
+
|
|
822
|
+
# Wrong prop type
|
|
823
|
+
$ dsgn render banner-hero --props '{"title":123}'
|
|
824
|
+
✖ Template validation failed
|
|
825
|
+
✗ props: Prop "title" should be a string, got number
|
|
826
|
+
→ Change "title" to a string: "title": "value"
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Enhanced Satori Error Messages
|
|
830
|
+
|
|
831
|
+
When rendering fails, dsgn provides clear suggestions:
|
|
832
|
+
|
|
833
|
+
```bash
|
|
834
|
+
$ dsgn render my-template --props props.json
|
|
835
|
+
✖ Failed to render template
|
|
836
|
+
|
|
837
|
+
Error: Satori requires explicit display: flex for containers with multiple children
|
|
838
|
+
|
|
839
|
+
Suggestions:
|
|
840
|
+
• Add "flex" or "flex-col" to your Tailwind classes: tw("flex items-center")
|
|
841
|
+
• Or add display: flex directly: style={{ display: "flex", ...tw("...") }}
|
|
842
|
+
• Every <div> with 2+ children MUST have display: flex or display: none
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
See `VALIDATION.md` for complete validation documentation and troubleshooting.
|
|
846
|
+
|
|
847
|
+
## Creating Custom Templates
|
|
848
|
+
|
|
849
|
+
Templates are stored in your project's `dsgn/templates/` directory. You can create custom templates:
|
|
850
|
+
|
|
851
|
+
1. Create a folder:
|
|
852
|
+
|
|
853
|
+
```bash
|
|
854
|
+
mkdir -p dsgn/templates/my-template
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
2. Create `meta.json`:
|
|
858
|
+
|
|
859
|
+
```json
|
|
860
|
+
{
|
|
861
|
+
"name": "my-template",
|
|
862
|
+
"description": "My custom template",
|
|
863
|
+
"type": "image",
|
|
864
|
+
"size": { "width": 1200, "height": 630 },
|
|
865
|
+
"props": {
|
|
866
|
+
"title": "string"
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
3. Create `my-template.tsx`:
|
|
872
|
+
|
|
873
|
+
```tsx
|
|
874
|
+
export default function MyTemplate({ title }) {
|
|
875
|
+
return (
|
|
876
|
+
<div style={{
|
|
877
|
+
display: 'flex',
|
|
878
|
+
width: '100%',
|
|
879
|
+
height: '100%',
|
|
880
|
+
background: '#000',
|
|
881
|
+
color: '#fff',
|
|
882
|
+
justifyContent: 'center',
|
|
883
|
+
alignItems: 'center',
|
|
884
|
+
fontSize: '48px',
|
|
885
|
+
fontFamily: 'sans-serif',
|
|
886
|
+
}}>
|
|
887
|
+
{title}
|
|
888
|
+
</div>
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
4. Validate and render:
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
dsgn validate my-template
|
|
897
|
+
dsgn render my-template --props '{"title":"Hello"}' --out test.png
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
## Use Cases
|
|
901
|
+
|
|
902
|
+
- 🎯 **OG Images**: Generate Open Graph images for websites
|
|
903
|
+
- 📱 **Social Media**: Create social media graphics
|
|
904
|
+
- 📧 **Email Headers**: Generate email banners
|
|
905
|
+
- 🎨 **Marketing**: Automate marketing asset creation
|
|
906
|
+
- 🤖 **AI Agents**: Let LLMs generate images programmatically
|
|
907
|
+
|
|
908
|
+
## Video Support
|
|
909
|
+
|
|
910
|
+
Video templates use `type: "video"` and support `frame` and `progress` props for animations.
|
|
911
|
+
|
|
912
|
+
### ⚡ No Dependencies Required
|
|
913
|
+
|
|
914
|
+
**dsgn uses pure JavaScript/WASM for video encoding** - works everywhere!
|
|
915
|
+
|
|
916
|
+
✅ Works on **Vercel Edge Functions**
|
|
917
|
+
✅ Works on **Netlify Functions**
|
|
918
|
+
✅ Works on **Cloudflare Workers**
|
|
919
|
+
✅ Works on **AWS Lambda**
|
|
920
|
+
✅ Works **everywhere** (Docker, CI/CD, local)
|
|
921
|
+
|
|
922
|
+
**12x faster than traditional approaches** - renders a 3-second video in ~2 seconds!
|
|
923
|
+
|
|
924
|
+
### Creating Video Templates
|
|
925
|
+
|
|
926
|
+
Video templates are similar to image templates but include `frame` and `progress` props for animations:
|
|
927
|
+
|
|
928
|
+
```tsx
|
|
929
|
+
export const meta = {
|
|
930
|
+
name: "my-video",
|
|
931
|
+
type: "video",
|
|
932
|
+
size: { width: 1200, height: 630 },
|
|
933
|
+
props: { title: "string" },
|
|
934
|
+
video: {
|
|
935
|
+
fps: 30,
|
|
936
|
+
duration: 3
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
export default function Template({ title, frame, progress, tw, qr, template }) {
|
|
941
|
+
return (
|
|
942
|
+
<div
|
|
943
|
+
style={{
|
|
944
|
+
...tw('w-full h-full flex items-center justify-center'),
|
|
945
|
+
opacity: progress,
|
|
946
|
+
transform: `translateY(${20 - progress * 20}px)`
|
|
947
|
+
}}
|
|
948
|
+
>
|
|
949
|
+
<h1 style={tw('text-6xl font-bold')}>{title}</h1>
|
|
950
|
+
</div>
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
### Rendering Videos
|
|
956
|
+
|
|
957
|
+
```bash
|
|
958
|
+
# Render to MP4
|
|
959
|
+
dsgn render my-video '{"title":"Animated Title"}' -o output.mp4
|
|
960
|
+
|
|
961
|
+
# With custom quality settings
|
|
962
|
+
dsgn render my-video props.json --crf 18 # Higher quality (lower CRF = better quality)
|
|
963
|
+
|
|
964
|
+
# Export frames only (for manual encoding)
|
|
965
|
+
dsgn render my-video props.json --frames-only -o frames/
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
**How it works:**
|
|
969
|
+
1. **SVG Generation**: All frames generated in parallel (~0.8s for 90 frames)
|
|
970
|
+
2. **WASM Encoding**: Frames encoded to H.264 MP4 using pure JavaScript (~1.2s for 90 frames)
|
|
971
|
+
3. **Total**: 3-second video renders in ~2 seconds on M1 Mac
|
|
972
|
+
|
|
973
|
+
**Props available in video templates:**
|
|
974
|
+
- `frame` - Current frame number (0 to totalFrames-1)
|
|
975
|
+
- `progress` - Animation progress from 0 to 1
|
|
976
|
+
- All standard helpers: `tw`, `qr`, `template`, `config`
|
|
977
|
+
|
|
978
|
+
**Performance:**
|
|
979
|
+
- **12x faster** than traditional approaches
|
|
980
|
+
- 90 frames (3s @ 30fps) renders in ~2 seconds
|
|
981
|
+
- ~60 frames per second rendering speed
|
|
982
|
+
- Perfect for serverless environments with execution time limits
|
|
983
|
+
|
|
984
|
+
## Documentation Website
|
|
985
|
+
|
|
986
|
+
A full documentation website is available in the `website/` folder, built with Astro.
|
|
987
|
+
|
|
988
|
+
**Local development:**
|
|
989
|
+
|
|
990
|
+
```bash
|
|
991
|
+
cd website
|
|
992
|
+
npm install
|
|
993
|
+
npm run dev
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
Open http://localhost:4321
|
|
997
|
+
|
|
998
|
+
**Deployment:**
|
|
999
|
+
|
|
1000
|
+
The site is ready to deploy on Netlify, Vercel, or Cloudflare Pages. A `netlify.toml` is included for easy Netlify deployment.
|
|
1001
|
+
|
|
1002
|
+
See `website/README.md` for more details.
|
|
1003
|
+
|
|
1004
|
+
## Registry
|
|
1005
|
+
|
|
1006
|
+
Templates are fetched from a registry (similar to shadcn/ui). The default registry is:
|
|
1007
|
+
|
|
1008
|
+
```
|
|
1009
|
+
https://dsgncli.com/r
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
You can host your own registry and use it with:
|
|
1013
|
+
|
|
1014
|
+
```bash
|
|
1015
|
+
dsgn add my-template --registry https://my-registry.com
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
## License
|
|
1019
|
+
|
|
1020
|
+
MIT
|