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
|
@@ -0,0 +1,2448 @@
|
|
|
1
|
+
# dsgn - Complete Documentation for LLMs
|
|
2
|
+
|
|
3
|
+
This is a comprehensive, single-file documentation for dsgn, optimized for LLM consumption.
|
|
4
|
+
Generated: 2025-11-17T21:03:55.855Z
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
================================================================================
|
|
11
|
+
FILE: index.mdx
|
|
12
|
+
================================================================================
|
|
13
|
+
|
|
14
|
+
## Image Templates
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// Social media card template
|
|
18
|
+
export default function OGImage({ tw, image, title, description, logo }) {
|
|
19
|
+
return (
|
|
20
|
+
<div style={tw('flex w-full h-full bg-white')}>
|
|
21
|
+
<div style={tw('flex-1 flex flex-col justify-between p-12')}>
|
|
22
|
+
<img src={image(logo)} style={tw('h-12 w-auto')} />
|
|
23
|
+
|
|
24
|
+
<div>
|
|
25
|
+
<h1 style={tw('text-5xl font-bold text-gray-900 mb-4')}>
|
|
26
|
+
{title}
|
|
27
|
+
</h1>
|
|
28
|
+
<p style={tw('text-xl text-gray-600')}>
|
|
29
|
+
{description}
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<p style={tw('text-gray-400')}>yoursite.com</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div style={tw('flex-1')}>
|
|
37
|
+
<img
|
|
38
|
+
src={image(featuredImage)}
|
|
39
|
+
style={tw('w-full h-full object-cover')}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Render it:**
|
|
48
|
+
```bash
|
|
49
|
+
dsgn render og-image '{"title":"Hello World","description":"My awesome blog post"}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Output:** `og-image.png` (1200×630px) in your current directory
|
|
53
|
+
|
|
54
|
+
**Perfect for:** Open Graph images, Twitter cards, blog headers, product cards, and quote graphics.
|
|
55
|
+
|
|
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
|
+
## Features
|
|
89
|
+
|
|
90
|
+
- 🎨 **shadcn/ui Design System**: Beautiful, semantic colors out of the box (`text-primary`, `bg-card`, etc.)
|
|
91
|
+
- ✨ **Template-based**: Install design templates like you install UI components
|
|
92
|
+
- 🎨 **Tailwind CSS Support**: Style templates with Tailwind utility classes + opacity modifiers (`bg-primary/50`)
|
|
93
|
+
- 📱 **Built-in Helpers**: QR codes, image embedding, video backgrounds, template composition
|
|
94
|
+
- ✅ **Smart Validation**: Automatic prop and template validation with helpful error messages
|
|
95
|
+
- 🚀 **Framework-agnostic**: Works with Node, Bun, Deno, Laravel, Python, and more
|
|
96
|
+
- 🤖 **Agent-friendly**: Machine-readable metadata for LLMs
|
|
97
|
+
- 📦 **Easy installation**: `npx dsgn add template-name`
|
|
98
|
+
- 🎬 **Video support**: Render animated videos with frame-by-frame control
|
|
99
|
+
- ⚡ **Powered by Satori**: Generate images from React + Tailwind
|
|
100
|
+
|
|
101
|
+
## Quick Start
|
|
102
|
+
|
|
103
|
+
### Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install -g dsgn
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Or use with npx:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx dsgn --help
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Initialize in Your Project
|
|
116
|
+
|
|
117
|
+
Navigate to any project folder and run:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
dsgn init
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This creates a `_dsgn/` folder where templates will be installed.
|
|
124
|
+
|
|
125
|
+
### Install a Template
|
|
126
|
+
|
|
127
|
+
Templates can be installed from multiple sources:
|
|
128
|
+
|
|
129
|
+
#### 1. Official Registry (Coming Soon)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
dsgn add banner-hero
|
|
133
|
+
dsgn add og-image --registry https://custom-registry.com/r
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### 2. GitHub Repositories
|
|
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`)
|
|
179
|
+
|
|
180
|
+
**Benefits:**
|
|
181
|
+
- Templates are local to your project (like npm packages)
|
|
182
|
+
- Different projects can use different template versions
|
|
183
|
+
- Version controlled with your project
|
|
184
|
+
- Easy to share within your team
|
|
185
|
+
- Works offline once installed
|
|
186
|
+
|
|
187
|
+
### Render an Image
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
dsgn render banner-hero '{"title":"Hello World","subtitle":"Built with dsgn"}'
|
|
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
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Commands
|
|
201
|
+
|
|
202
|
+
### `dsgn add <source>`
|
|
203
|
+
|
|
204
|
+
Install a template from various sources:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# From registry
|
|
208
|
+
dsgn add banner-hero
|
|
209
|
+
|
|
210
|
+
# From GitHub
|
|
211
|
+
dsgn add github:username/repo/path/to/template
|
|
212
|
+
|
|
213
|
+
# From URL
|
|
214
|
+
dsgn add https://example.com/my-template.json
|
|
215
|
+
|
|
216
|
+
# From local path
|
|
217
|
+
dsgn add ./my-templates/custom-banner
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### `dsgn list`
|
|
221
|
+
|
|
222
|
+
List all installed templates:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
dsgn list
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### `dsgn render <template> <props> [options]`
|
|
229
|
+
|
|
230
|
+
Render an image or video:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Image with inline props
|
|
234
|
+
dsgn render banner-hero '{"title":"Hello World"}'
|
|
235
|
+
|
|
236
|
+
# Video with inline props
|
|
237
|
+
dsgn render video-intro '{"title":"Welcome"}' --out intro.mp4
|
|
238
|
+
|
|
239
|
+
# Using a props file
|
|
240
|
+
dsgn render banner-hero props.json
|
|
241
|
+
|
|
242
|
+
# Custom output
|
|
243
|
+
dsgn render banner-hero '{"title":"Hello"}' --out custom-name.png
|
|
244
|
+
|
|
245
|
+
# Different format
|
|
246
|
+
dsgn render banner-hero '{"title":"Hello"}' --format jpeg
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Options:
|
|
250
|
+
- `--out, -o` - Output filename (default: `<template>.<ext>`)
|
|
251
|
+
- `--format` - Output format: `png`, `jpeg`, `svg` (images only)
|
|
252
|
+
- `--quality` - JPEG quality 1-100 (default: 92)
|
|
253
|
+
|
|
254
|
+
### `dsgn validate <template>`
|
|
255
|
+
|
|
256
|
+
Validate a template:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
dsgn validate banner-hero
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Checks:
|
|
263
|
+
- Template file exists and is valid React
|
|
264
|
+
- `meta.json` exists and is valid
|
|
265
|
+
- Required props are defined
|
|
266
|
+
- Fonts exist (if specified)
|
|
267
|
+
|
|
268
|
+
### `dsgn init`
|
|
269
|
+
|
|
270
|
+
Initialize dsgn in a project:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
dsgn init
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Creates `dsgn.json` configuration file.
|
|
277
|
+
|
|
278
|
+
## Next Steps
|
|
279
|
+
|
|
280
|
+
- [Learn about Templates](/templates)
|
|
281
|
+
- [Image Rendering](/images)
|
|
282
|
+
- [Video Rendering](/video)
|
|
283
|
+
- [Helpers (QR, Template Composition)](/helpers)
|
|
284
|
+
- [Styling with Tailwind & shadcn/ui](/styling)
|
|
285
|
+
- [Custom Fonts](/fonts)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
================================================================================
|
|
292
|
+
FILE: templates.mdx
|
|
293
|
+
================================================================================
|
|
294
|
+
|
|
295
|
+
# Installing Templates from Different Sources
|
|
296
|
+
|
|
297
|
+
## Overview
|
|
298
|
+
|
|
299
|
+
dsgn supports installing templates from multiple sources:
|
|
300
|
+
|
|
301
|
+
1. **Official Registry** (default)
|
|
302
|
+
2. **Direct URLs**
|
|
303
|
+
3. **GitHub Repositories**
|
|
304
|
+
4. **Local Filesystem**
|
|
305
|
+
|
|
306
|
+
## 1. Official Registry (Default)
|
|
307
|
+
|
|
308
|
+
Install templates from the official dsgn registry at `https://dsgncli.com/r`
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
dsgn add banner-hero
|
|
312
|
+
dsgn add product-card
|
|
313
|
+
dsgn add social-og-image
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**How it works:**
|
|
317
|
+
- Fetches from: `https://dsgncli.com/r/banner-hero`
|
|
318
|
+
- Returns JSON with template files
|
|
319
|
+
- Installs to: `_dsgn/templates/banner-hero/`
|
|
320
|
+
|
|
321
|
+
### Custom Registry
|
|
322
|
+
|
|
323
|
+
Use a different registry:
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
dsgn add banner-hero --registry https://my-registry.com/templates
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## 2. Direct URLs
|
|
330
|
+
|
|
331
|
+
Install a template from any publicly accessible URL:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
dsgn add https://example.com/templates/my-template.json
|
|
335
|
+
dsgn add https://cdn.example.com/templates/awesome-banner.json
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Requirements:**
|
|
339
|
+
- URL must return JSON in the registry template format
|
|
340
|
+
- Must be publicly accessible (no authentication)
|
|
341
|
+
|
|
342
|
+
**Example template JSON:**
|
|
343
|
+
|
|
344
|
+
```json
|
|
345
|
+
{
|
|
346
|
+
"name": "my-template",
|
|
347
|
+
"version": "1.0.0",
|
|
348
|
+
"description": "My custom template",
|
|
349
|
+
"files": [
|
|
350
|
+
{
|
|
351
|
+
"path": "my-template.tsx",
|
|
352
|
+
"content": "import React from 'react';\n\nexport default function..."
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"path": "meta.json",
|
|
356
|
+
"content": "{\"name\":\"my-template\"...}"
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 3. GitHub Repositories
|
|
363
|
+
|
|
364
|
+
Install templates directly from GitHub repos:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# From a GitHub repo (looks for template.json in repo root)
|
|
368
|
+
dsgn add github:username/repo
|
|
369
|
+
|
|
370
|
+
# From a specific path in the repo
|
|
371
|
+
dsgn add github:username/repo/templates/banner-hero
|
|
372
|
+
|
|
373
|
+
# From an organization
|
|
374
|
+
dsgn add github:myorg/design-templates/social-media/og-image
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**How it works:**
|
|
378
|
+
1. Detects `github:` prefix
|
|
379
|
+
2. Converts to raw GitHub URL
|
|
380
|
+
3. Fetches `template.json` (or `meta.json` + template file)
|
|
381
|
+
4. Downloads all referenced files
|
|
382
|
+
5. Installs locally
|
|
383
|
+
|
|
384
|
+
**Template structure on GitHub:**
|
|
385
|
+
|
|
386
|
+
Option A: Single template.json (self-contained)
|
|
387
|
+
```
|
|
388
|
+
username/repo/
|
|
389
|
+
└── template.json # Contains all files inline
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Option B: Separate files
|
|
393
|
+
```
|
|
394
|
+
username/repo/
|
|
395
|
+
├── meta.json
|
|
396
|
+
├── banner-hero.tsx
|
|
397
|
+
└── fonts/
|
|
398
|
+
└── Inter-Bold.woff
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## 4. Local Filesystem
|
|
402
|
+
|
|
403
|
+
Install templates from your local filesystem:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
# Relative path
|
|
407
|
+
dsgn add ./my-templates/banner-hero
|
|
408
|
+
dsgn add ../shared-templates/product-card
|
|
409
|
+
|
|
410
|
+
# Absolute path
|
|
411
|
+
dsgn add /Users/you/templates/social-card
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Use cases:**
|
|
415
|
+
- Development and testing
|
|
416
|
+
- Private templates
|
|
417
|
+
- Shared team templates (monorepo)
|
|
418
|
+
- Before publishing to registry
|
|
419
|
+
|
|
420
|
+
## Template Installation Directory
|
|
421
|
+
|
|
422
|
+
Templates are installed to `_dsgn/templates/<template-name>/` by default.
|
|
423
|
+
|
|
424
|
+
Customize this in `dsgn.json`:
|
|
425
|
+
|
|
426
|
+
```json
|
|
427
|
+
{
|
|
428
|
+
"templatesDir": "my-custom-templates"
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Benefits of Local Installation
|
|
433
|
+
|
|
434
|
+
- **Version Control**: Templates are part of your project
|
|
435
|
+
- **Offline**: Works without internet once installed
|
|
436
|
+
- **Team Sharing**: Everyone on the team has the same templates
|
|
437
|
+
- **Customization**: Fork and modify templates per project
|
|
438
|
+
- **Consistency**: Different projects can use different template versions
|
|
439
|
+
|
|
440
|
+
## Next Steps
|
|
441
|
+
|
|
442
|
+
- [Learn about Helpers](/helpers)
|
|
443
|
+
- [Styling with Tailwind & shadcn/ui](/styling)
|
|
444
|
+
- [Custom Fonts](/fonts)
|
|
445
|
+
- [Image Rendering](/images)
|
|
446
|
+
- [Video Rendering](/video)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
================================================================================
|
|
453
|
+
FILE: images.mdx
|
|
454
|
+
================================================================================
|
|
455
|
+
|
|
456
|
+
# Image Rendering
|
|
457
|
+
|
|
458
|
+
Generate beautiful images from React components using dsgn's powerful image rendering engine powered by Satori.
|
|
459
|
+
|
|
460
|
+
## Quick Start
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# Render an image template with inline props
|
|
464
|
+
dsgn render banner-hero '{"title":"Hello World","subtitle":"Welcome"}'
|
|
465
|
+
|
|
466
|
+
# Custom output name
|
|
467
|
+
dsgn render banner-hero '{"title":"Hello"}' --out custom-name.png
|
|
468
|
+
|
|
469
|
+
# Different format
|
|
470
|
+
dsgn render banner-hero '{"title":"Hello"}' --format jpeg --quality 95
|
|
471
|
+
|
|
472
|
+
# Or use a props file
|
|
473
|
+
dsgn render banner-hero props.json
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Image Template Structure
|
|
477
|
+
|
|
478
|
+
### Basic Template
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
// banner-hero.tsx
|
|
482
|
+
export default function BannerHero({ title, subtitle, tw }) {
|
|
483
|
+
return (
|
|
484
|
+
<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')}>
|
|
485
|
+
<h1 style={tw('text-7xl font-bold text-white mb-4')}>
|
|
486
|
+
{title}
|
|
487
|
+
</h1>
|
|
488
|
+
<p style={tw('text-2xl text-white/80')}>
|
|
489
|
+
{subtitle}
|
|
490
|
+
</p>
|
|
491
|
+
</div>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
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
|
+
### Props File
|
|
515
|
+
|
|
516
|
+
```json
|
|
517
|
+
{
|
|
518
|
+
"title": "Welcome to dsgn",
|
|
519
|
+
"subtitle": "Generate beautiful images from React"
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Output Formats
|
|
524
|
+
|
|
525
|
+
### PNG (Default)
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
dsgn render my-template '{"title":"Hello"}'
|
|
529
|
+
# Output: my-template.png
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Best for:**
|
|
533
|
+
- Designs with transparency
|
|
534
|
+
- High-quality graphics
|
|
535
|
+
- Sharp text and logos
|
|
536
|
+
- Web graphics
|
|
537
|
+
|
|
538
|
+
### JPEG
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
dsgn render my-template '{"title":"Hello"}' --format jpeg --quality 92
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Options:**
|
|
545
|
+
- `--quality` - 1-100 (default: 92)
|
|
546
|
+
|
|
547
|
+
**Best for:**
|
|
548
|
+
- Photographs
|
|
549
|
+
- Gradients
|
|
550
|
+
- Smaller file sizes
|
|
551
|
+
- Social media images
|
|
552
|
+
|
|
553
|
+
### SVG
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
dsgn render my-template '{"title":"Hello"}' --format svg
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Best for:**
|
|
560
|
+
- Vector graphics
|
|
561
|
+
- Scalable designs
|
|
562
|
+
- Print-ready artwork
|
|
563
|
+
- Logos and icons
|
|
564
|
+
|
|
565
|
+
## Embedding Images
|
|
566
|
+
|
|
567
|
+
Use the `image()` helper to embed external images in your templates.
|
|
568
|
+
|
|
569
|
+
### Basic Usage
|
|
570
|
+
|
|
571
|
+
```tsx
|
|
572
|
+
export default function ProductCard({ tw, image, title, background }) {
|
|
573
|
+
return (
|
|
574
|
+
<div style={tw('relative w-full h-full')}>
|
|
575
|
+
{/* Background image */}
|
|
576
|
+
<img
|
|
577
|
+
src={image(background)}
|
|
578
|
+
style={tw('absolute inset-0 w-full h-full object-cover')}
|
|
579
|
+
/>
|
|
580
|
+
|
|
581
|
+
{/* Content overlay */}
|
|
582
|
+
<div style={tw('relative z-10 p-12')}>
|
|
583
|
+
<h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Props:**
|
|
591
|
+
```json
|
|
592
|
+
{
|
|
593
|
+
"title": "New Product",
|
|
594
|
+
"background": "./images/background.jpg"
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Supported Image Formats
|
|
599
|
+
|
|
600
|
+
- ✅ **JPEG** (`.jpg`, `.jpeg`)
|
|
601
|
+
- ✅ **PNG** (`.png`)
|
|
602
|
+
- ✅ **GIF** (`.gif`)
|
|
603
|
+
- ✅ **WebP** (`.webp`)
|
|
604
|
+
- ✅ **SVG** (`.svg`)
|
|
605
|
+
|
|
606
|
+
### Image Positioning
|
|
607
|
+
|
|
608
|
+
```tsx
|
|
609
|
+
export default function ImageGrid({ tw, image, img1, img2, img3 }) {
|
|
610
|
+
return (
|
|
611
|
+
<div style={tw('grid grid-cols-3 gap-4 w-full h-full p-8 bg-gray-100')}>
|
|
612
|
+
{/* Cover - fills entire area */}
|
|
613
|
+
<img
|
|
614
|
+
src={image(img1)}
|
|
615
|
+
style={tw('w-full h-full object-cover rounded-lg')}
|
|
616
|
+
/>
|
|
617
|
+
|
|
618
|
+
{/* Contain - fits within area */}
|
|
619
|
+
<img
|
|
620
|
+
src={image(img2)}
|
|
621
|
+
style={tw('w-full h-full object-contain')}
|
|
622
|
+
/>
|
|
623
|
+
|
|
624
|
+
{/* Fill - stretches to fill */}
|
|
625
|
+
<img
|
|
626
|
+
src={image(img3)}
|
|
627
|
+
style={tw('w-full h-full object-fill')}
|
|
628
|
+
/>
|
|
629
|
+
</div>
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Common Image Templates
|
|
635
|
+
|
|
636
|
+
### Open Graph / Social Cards
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
// og-image.tsx
|
|
640
|
+
export default function OGImage({ tw, title, description, image, logo }) {
|
|
641
|
+
return (
|
|
642
|
+
<div style={tw('flex w-full h-full bg-white')}>
|
|
643
|
+
{/* Left side - Content */}
|
|
644
|
+
<div style={tw('flex-1 flex flex-col justify-between p-12')}>
|
|
645
|
+
<img src={image(logo)} style={tw('h-12 w-auto')} />
|
|
646
|
+
|
|
647
|
+
<div>
|
|
648
|
+
<h1 style={tw('text-5xl font-bold text-gray-900 mb-4')}>
|
|
649
|
+
{title}
|
|
650
|
+
</h1>
|
|
651
|
+
<p style={tw('text-xl text-gray-600')}>
|
|
652
|
+
{description}
|
|
653
|
+
</p>
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
<p style={tw('text-gray-400')}>yoursite.com</p>
|
|
657
|
+
</div>
|
|
658
|
+
|
|
659
|
+
{/* Right side - Image */}
|
|
660
|
+
<div style={tw('flex-1')}>
|
|
661
|
+
<img
|
|
662
|
+
src={image(featuredImage)}
|
|
663
|
+
style={tw('w-full h-full object-cover')}
|
|
664
|
+
/>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Size:** 1200x630 (standard OG image size)
|
|
672
|
+
|
|
673
|
+
### Product Cards
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
export default function ProductCard({ tw, image, product, price, badge }) {
|
|
677
|
+
return (
|
|
678
|
+
<div style={tw('flex flex-col w-full h-full bg-white rounded-2xl overflow-hidden')}>
|
|
679
|
+
{/* Product image */}
|
|
680
|
+
<div style={tw('relative h-2/3')}>
|
|
681
|
+
<img
|
|
682
|
+
src={image(product)}
|
|
683
|
+
style={tw('w-full h-full object-cover')}
|
|
684
|
+
/>
|
|
685
|
+
{badge && (
|
|
686
|
+
<div style={tw('absolute top-4 right-4 bg-red-500 text-white px-4 py-2 rounded-full text-sm font-bold')}>
|
|
687
|
+
{badge}
|
|
688
|
+
</div>
|
|
689
|
+
)}
|
|
690
|
+
</div>
|
|
691
|
+
|
|
692
|
+
{/* Product info */}
|
|
693
|
+
<div style={tw('flex-1 p-6 flex flex-col justify-between')}>
|
|
694
|
+
<h2 style={tw('text-2xl font-bold text-gray-900')}>
|
|
695
|
+
{title}
|
|
696
|
+
</h2>
|
|
697
|
+
<div style={tw('flex justify-between items-center')}>
|
|
698
|
+
<span style={tw('text-3xl font-bold text-gray-900')}>
|
|
699
|
+
${price}
|
|
700
|
+
</span>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Quote Cards
|
|
709
|
+
|
|
710
|
+
```tsx
|
|
711
|
+
export default function QuoteCard({ tw, image, quote, author, avatar }) {
|
|
712
|
+
return (
|
|
713
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-indigo-500 to-purple-600 p-16')}>
|
|
714
|
+
{avatar && (
|
|
715
|
+
<img
|
|
716
|
+
src={image(avatar)}
|
|
717
|
+
style={tw('w-24 h-24 rounded-full border-4 border-white mb-8')}
|
|
718
|
+
/>
|
|
719
|
+
)}
|
|
720
|
+
|
|
721
|
+
<blockquote style={tw('text-3xl text-white text-center font-light mb-8 max-w-3xl')}>
|
|
722
|
+
"{quote}"
|
|
723
|
+
</blockquote>
|
|
724
|
+
|
|
725
|
+
<p style={tw('text-xl text-white/80')}>
|
|
726
|
+
— {author}
|
|
727
|
+
</p>
|
|
728
|
+
</div>
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
## Performance Tips
|
|
734
|
+
|
|
735
|
+
### 1. Optimize Image Sizes
|
|
736
|
+
|
|
737
|
+
Use appropriately sized images before embedding:
|
|
738
|
+
|
|
739
|
+
```bash
|
|
740
|
+
# Resize large images before using them
|
|
741
|
+
convert large-image.jpg -resize 1600x900 optimized.jpg
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### 2. Use Web-Optimized Formats
|
|
745
|
+
|
|
746
|
+
- **WebP** for modern browsers (smaller file sizes)
|
|
747
|
+
- **JPEG** for photos with controlled quality
|
|
748
|
+
- **PNG** only when transparency is needed
|
|
749
|
+
|
|
750
|
+
### 3. Batch Rendering
|
|
751
|
+
|
|
752
|
+
Render multiple images at once:
|
|
753
|
+
|
|
754
|
+
```bash
|
|
755
|
+
# Using a script with inline props
|
|
756
|
+
dsgn render my-template '{"title":"Image 1"}'
|
|
757
|
+
dsgn render my-template '{"title":"Image 2"}'
|
|
758
|
+
dsgn render my-template '{"title":"Image 3"}'
|
|
759
|
+
|
|
760
|
+
# Or loop through props files
|
|
761
|
+
for props in props/*.json; do
|
|
762
|
+
dsgn render my-template "$props"
|
|
763
|
+
done
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### 4. Cache Templates
|
|
767
|
+
|
|
768
|
+
Templates are cached after first load for faster subsequent renders.
|
|
769
|
+
|
|
770
|
+
## Common Sizes
|
|
771
|
+
|
|
772
|
+
### Social Media
|
|
773
|
+
|
|
774
|
+
- **Twitter/X Card**: 1200x675
|
|
775
|
+
- **Facebook Post**: 1200x630
|
|
776
|
+
- **Instagram Post**: 1080x1080
|
|
777
|
+
- **LinkedIn Post**: 1200x627
|
|
778
|
+
- **Pinterest Pin**: 1000x1500
|
|
779
|
+
|
|
780
|
+
### Web Graphics
|
|
781
|
+
|
|
782
|
+
- **Hero Banner**: 1920x1080
|
|
783
|
+
- **Blog Header**: 1600x900
|
|
784
|
+
- **Thumbnail**: 640x360
|
|
785
|
+
- **Avatar**: 400x400
|
|
786
|
+
|
|
787
|
+
### Print
|
|
788
|
+
|
|
789
|
+
- **Flyer (A4)**: 2480x3508 (300 DPI)
|
|
790
|
+
- **Business Card**: 1050x600 (300 DPI)
|
|
791
|
+
- **Poster (A3)**: 3508x4961 (300 DPI)
|
|
792
|
+
|
|
793
|
+
## Troubleshooting
|
|
794
|
+
|
|
795
|
+
### Fonts Not Rendering
|
|
796
|
+
|
|
797
|
+
Make sure fonts are properly loaded in `meta.json`:
|
|
798
|
+
|
|
799
|
+
```json
|
|
800
|
+
{
|
|
801
|
+
"fonts": [
|
|
802
|
+
{
|
|
803
|
+
"name": "Inter",
|
|
804
|
+
"path": "fonts/Inter-Bold.woff",
|
|
805
|
+
"weight": 700
|
|
806
|
+
}
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Images Not Loading
|
|
812
|
+
|
|
813
|
+
Check file paths are relative to the props file:
|
|
814
|
+
|
|
815
|
+
```json
|
|
816
|
+
{
|
|
817
|
+
"background": "./images/bg.jpg" // ✅ Relative path
|
|
818
|
+
"background": "/images/bg.jpg" // ❌ Absolute path won't work
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### Blurry Text
|
|
823
|
+
|
|
824
|
+
Ensure you're using appropriate dimensions. Satori renders at exact pixel dimensions.
|
|
825
|
+
|
|
826
|
+
## Next Steps
|
|
827
|
+
|
|
828
|
+
- [Learn about Video Rendering](/video)
|
|
829
|
+
- [QR Codes and Template Composition](/helpers)
|
|
830
|
+
- [Styling with Tailwind & shadcn/ui](/styling)
|
|
831
|
+
- [Using Custom Fonts](/fonts)
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
================================================================================
|
|
838
|
+
FILE: video.mdx
|
|
839
|
+
================================================================================
|
|
840
|
+
|
|
841
|
+
# Video Rendering
|
|
842
|
+
|
|
843
|
+
Create animated videos programmatically using React components. Perfect for automated video generation, social media content, and dynamic animations.
|
|
844
|
+
|
|
845
|
+
## Quick Start
|
|
846
|
+
|
|
847
|
+
```bash
|
|
848
|
+
# Render a video template with inline props
|
|
849
|
+
dsgn render video-intro '{"title":"Welcome!"}' --out intro.mp4
|
|
850
|
+
|
|
851
|
+
# With custom encoding
|
|
852
|
+
dsgn render video-intro '{"title":"Welcome!"}' --preset ultrafast
|
|
853
|
+
|
|
854
|
+
# Or use a props file
|
|
855
|
+
dsgn render video-intro props.json --out intro.mp4
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
## Video Template Structure
|
|
859
|
+
|
|
860
|
+
### Basic Template
|
|
861
|
+
|
|
862
|
+
```tsx
|
|
863
|
+
// video-intro.tsx
|
|
864
|
+
export default function VideoIntro({ tw, title, frame, progress }) {
|
|
865
|
+
// Animate opacity based on progress
|
|
866
|
+
const titleOpacity = progress < 0.3 ? progress / 0.3 : 1;
|
|
867
|
+
|
|
868
|
+
return (
|
|
869
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
|
|
870
|
+
<h1
|
|
871
|
+
style={{
|
|
872
|
+
...tw('text-8xl font-bold text-white'),
|
|
873
|
+
opacity: titleOpacity
|
|
874
|
+
}}
|
|
875
|
+
>
|
|
876
|
+
{title}
|
|
877
|
+
</h1>
|
|
878
|
+
|
|
879
|
+
{/* Show frame counter */}
|
|
880
|
+
<div style={tw('absolute bottom-10 right-10 text-white text-sm')}>
|
|
881
|
+
Frame: {frame}
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
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
|
+
### Props File
|
|
910
|
+
|
|
911
|
+
```json
|
|
912
|
+
{
|
|
913
|
+
"title": "Welcome!"
|
|
914
|
+
}
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
## Video-Specific Props
|
|
918
|
+
|
|
919
|
+
Every video template receives these additional props:
|
|
920
|
+
|
|
921
|
+
### `frame`
|
|
922
|
+
Current frame number (0 to totalFrames - 1)
|
|
923
|
+
|
|
924
|
+
```tsx
|
|
925
|
+
export default function MyVideo({ frame }) {
|
|
926
|
+
// Frame 0, 1, 2, 3, ... 89 (for 3s @ 30fps)
|
|
927
|
+
return <div>Frame: {frame}</div>;
|
|
928
|
+
}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### `progress`
|
|
932
|
+
Animation progress from 0 to 1
|
|
933
|
+
|
|
934
|
+
```tsx
|
|
935
|
+
export default function MyVideo({ progress }) {
|
|
936
|
+
// 0.0 at start, 0.5 at middle, 1.0 at end
|
|
937
|
+
const x = progress * 100; // Move from 0 to 100
|
|
938
|
+
|
|
939
|
+
return (
|
|
940
|
+
<div style={{ transform: `translateX(${x}px)` }}>
|
|
941
|
+
Moving element
|
|
942
|
+
</div>
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
## Animation Patterns
|
|
948
|
+
|
|
949
|
+
### Fade In/Out
|
|
950
|
+
|
|
951
|
+
```tsx
|
|
952
|
+
export default function FadeVideo({ progress, title, tw }) {
|
|
953
|
+
// Fade in first 20%, stay visible, fade out last 20%
|
|
954
|
+
let opacity = 1;
|
|
955
|
+
if (progress < 0.2) {
|
|
956
|
+
opacity = progress / 0.2;
|
|
957
|
+
} else if (progress > 0.8) {
|
|
958
|
+
opacity = (1 - progress) / 0.2;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return (
|
|
962
|
+
<h1 style={{ ...tw('text-6xl font-bold'), opacity }}>
|
|
963
|
+
{title}
|
|
964
|
+
</h1>
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
### Slide In
|
|
970
|
+
|
|
971
|
+
```tsx
|
|
972
|
+
export default function SlideVideo({ progress, tw }) {
|
|
973
|
+
// Slide in from left
|
|
974
|
+
const x = -100 + (progress * 100); // -100 to 0
|
|
975
|
+
|
|
976
|
+
return (
|
|
977
|
+
<div style={{
|
|
978
|
+
...tw('text-4xl font-bold'),
|
|
979
|
+
transform: `translateX(${x}%)`
|
|
980
|
+
}}>
|
|
981
|
+
Sliding Text
|
|
982
|
+
</div>
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Scale Animation
|
|
988
|
+
|
|
989
|
+
```tsx
|
|
990
|
+
export default function ScaleVideo({ progress, tw }) {
|
|
991
|
+
// Scale from 0 to 1
|
|
992
|
+
const scale = progress;
|
|
993
|
+
|
|
994
|
+
return (
|
|
995
|
+
<div style={{
|
|
996
|
+
...tw('text-6xl font-bold'),
|
|
997
|
+
transform: `scale(${scale})`
|
|
998
|
+
}}>
|
|
999
|
+
Growing Text
|
|
1000
|
+
</div>
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### Rotation
|
|
1006
|
+
|
|
1007
|
+
```tsx
|
|
1008
|
+
export default function RotateVideo({ progress, tw }) {
|
|
1009
|
+
// Rotate 360 degrees
|
|
1010
|
+
const rotation = progress * 360;
|
|
1011
|
+
|
|
1012
|
+
return (
|
|
1013
|
+
<div style={{
|
|
1014
|
+
...tw('w-32 h-32 bg-blue-500 rounded-lg'),
|
|
1015
|
+
transform: `rotate(${rotation}deg)`
|
|
1016
|
+
}}>
|
|
1017
|
+
</div>
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
### Easing Functions
|
|
1023
|
+
|
|
1024
|
+
Create smooth animations with easing:
|
|
1025
|
+
|
|
1026
|
+
```tsx
|
|
1027
|
+
function easeOutCubic(t) {
|
|
1028
|
+
return 1 - Math.pow(1 - t, 3);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export default function EasedVideo({ progress, tw }) {
|
|
1032
|
+
const easedProgress = easeOutCubic(progress);
|
|
1033
|
+
const x = easedProgress * 100;
|
|
1034
|
+
|
|
1035
|
+
return (
|
|
1036
|
+
<div style={{ transform: `translateX(${x}px)` }}>
|
|
1037
|
+
Smooth animation
|
|
1038
|
+
</div>
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
## Embedding Videos
|
|
1044
|
+
|
|
1045
|
+
Use the `video()` helper to embed video backgrounds that auto-sync with your animation.
|
|
1046
|
+
|
|
1047
|
+
### Basic Usage
|
|
1048
|
+
|
|
1049
|
+
```tsx
|
|
1050
|
+
export default function VideoOverlay({ tw, video, title, background }) {
|
|
1051
|
+
return (
|
|
1052
|
+
<div style={tw('relative w-full h-full')}>
|
|
1053
|
+
{/* Background video - automatically synced to current frame */}
|
|
1054
|
+
<img
|
|
1055
|
+
src={video(background)}
|
|
1056
|
+
style={tw('absolute inset-0 w-full h-full object-cover')}
|
|
1057
|
+
/>
|
|
1058
|
+
|
|
1059
|
+
{/* Text overlay */}
|
|
1060
|
+
<div style={tw('relative z-10 flex items-center justify-center w-full h-full')}>
|
|
1061
|
+
<h1 style={tw('text-7xl font-bold text-white drop-shadow-lg')}>
|
|
1062
|
+
{title}
|
|
1063
|
+
</h1>
|
|
1064
|
+
</div>
|
|
1065
|
+
</div>
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
**Props:**
|
|
1071
|
+
```json
|
|
1072
|
+
{
|
|
1073
|
+
"title": "Amazing Video",
|
|
1074
|
+
"background": "./footage/background.mp4"
|
|
1075
|
+
}
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
### How Video Sync Works
|
|
1079
|
+
|
|
1080
|
+
1. **First pass**: Template calls `video()` to register needed videos
|
|
1081
|
+
2. **Pre-processing**: dsgn extracts all frames at template's FPS
|
|
1082
|
+
3. **Rendering**: Each frame uses the corresponding video frame
|
|
1083
|
+
4. **Caching**: Frames are cached in memory for fast access
|
|
1084
|
+
|
|
1085
|
+
### Animated Overlay Example
|
|
1086
|
+
|
|
1087
|
+
```tsx
|
|
1088
|
+
export default function VideoWithText({ tw, video, frame, progress, title, clip }) {
|
|
1089
|
+
// Fade in title
|
|
1090
|
+
const titleOpacity = progress < 0.2 ? progress / 0.2 : 1;
|
|
1091
|
+
|
|
1092
|
+
return (
|
|
1093
|
+
<div style={tw('relative w-full h-full')}>
|
|
1094
|
+
{/* Synced video background */}
|
|
1095
|
+
<img
|
|
1096
|
+
src={video(clip)}
|
|
1097
|
+
style={tw('absolute inset-0 w-full h-full object-cover')}
|
|
1098
|
+
/>
|
|
1099
|
+
|
|
1100
|
+
{/* Dark overlay */}
|
|
1101
|
+
<div style={tw('absolute inset-0 bg-black/40')} />
|
|
1102
|
+
|
|
1103
|
+
{/* Animated title */}
|
|
1104
|
+
<div style={tw('relative z-10 flex items-center justify-center w-full h-full')}>
|
|
1105
|
+
<h1
|
|
1106
|
+
style={{
|
|
1107
|
+
...tw('text-8xl font-bold text-white text-center'),
|
|
1108
|
+
opacity: titleOpacity
|
|
1109
|
+
}}
|
|
1110
|
+
>
|
|
1111
|
+
{title}
|
|
1112
|
+
</h1>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
{/* Frame counter */}
|
|
1116
|
+
<div style={tw('absolute bottom-4 right-4 text-white text-sm bg-black/50 px-3 py-1 rounded')}>
|
|
1117
|
+
{frame} / {Math.floor(3 * 30)}
|
|
1118
|
+
</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
## Combining Videos and Images
|
|
1125
|
+
|
|
1126
|
+
Use both `video()` and `image()` helpers together to create rich, branded video content.
|
|
1127
|
+
|
|
1128
|
+
### Video Background with Logo
|
|
1129
|
+
|
|
1130
|
+
```tsx
|
|
1131
|
+
export default function BrandedVideo({ tw, video, image, progress, background, logo, title }) {
|
|
1132
|
+
// Logo slides in from top
|
|
1133
|
+
const logoY = progress < 0.3 ? -100 + (progress / 0.3) * 100 : 0;
|
|
1134
|
+
// Title fades in after logo
|
|
1135
|
+
const titleOpacity = progress > 0.3 ? Math.min((progress - 0.3) / 0.3, 1) : 0;
|
|
1136
|
+
|
|
1137
|
+
return (
|
|
1138
|
+
<div style={tw('relative w-full h-full')}>
|
|
1139
|
+
{/* Video background */}
|
|
1140
|
+
<img
|
|
1141
|
+
src={video(background)}
|
|
1142
|
+
style={tw('absolute inset-0 w-full h-full object-cover')}
|
|
1143
|
+
/>
|
|
1144
|
+
|
|
1145
|
+
{/* Gradient overlay */}
|
|
1146
|
+
<div style={tw('absolute inset-0 bg-gradient-to-t from-black/80 to-transparent')} />
|
|
1147
|
+
|
|
1148
|
+
{/* Animated logo */}
|
|
1149
|
+
<div
|
|
1150
|
+
style={{
|
|
1151
|
+
...tw('absolute top-12 left-12'),
|
|
1152
|
+
transform: `translateY(${logoY}px)`
|
|
1153
|
+
}}
|
|
1154
|
+
>
|
|
1155
|
+
<img
|
|
1156
|
+
src={image(logo)}
|
|
1157
|
+
style={tw('h-16 w-auto')}
|
|
1158
|
+
/>
|
|
1159
|
+
</div>
|
|
1160
|
+
|
|
1161
|
+
{/* Animated title */}
|
|
1162
|
+
<div style={tw('absolute bottom-20 left-12 right-12')}>
|
|
1163
|
+
<h1
|
|
1164
|
+
style={{
|
|
1165
|
+
...tw('text-7xl font-bold text-white'),
|
|
1166
|
+
opacity: titleOpacity
|
|
1167
|
+
}}
|
|
1168
|
+
>
|
|
1169
|
+
{title}
|
|
1170
|
+
</h1>
|
|
1171
|
+
</div>
|
|
1172
|
+
</div>
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Props:**
|
|
1178
|
+
```json
|
|
1179
|
+
{
|
|
1180
|
+
"title": "Introducing Our New Product",
|
|
1181
|
+
"background": "./footage/product-demo.mp4",
|
|
1182
|
+
"logo": "./images/company-logo.png"
|
|
1183
|
+
}
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
### Multi-Element Video Composition
|
|
1187
|
+
|
|
1188
|
+
```tsx
|
|
1189
|
+
export default function ProductShowcase({
|
|
1190
|
+
tw,
|
|
1191
|
+
video,
|
|
1192
|
+
image,
|
|
1193
|
+
progress,
|
|
1194
|
+
backgroundVideo,
|
|
1195
|
+
productImage,
|
|
1196
|
+
badge,
|
|
1197
|
+
title,
|
|
1198
|
+
price
|
|
1199
|
+
}) {
|
|
1200
|
+
// Product image zooms in
|
|
1201
|
+
const scale = 0.8 + (Math.min(progress / 0.5, 1) * 0.2); // 0.8 to 1.0
|
|
1202
|
+
|
|
1203
|
+
// Badge rotates in
|
|
1204
|
+
const badgeRotation = progress < 0.4 ? (progress / 0.4) * 360 : 360;
|
|
1205
|
+
const badgeOpacity = Math.min(progress / 0.4, 1);
|
|
1206
|
+
|
|
1207
|
+
// Price slides up
|
|
1208
|
+
const priceY = progress > 0.6 ? (1 - Math.min((progress - 0.6) / 0.4, 1)) * 50 : 50;
|
|
1209
|
+
|
|
1210
|
+
return (
|
|
1211
|
+
<div style={tw('relative w-full h-full bg-white')}>
|
|
1212
|
+
{/* Video background (blurred) */}
|
|
1213
|
+
<div style={tw('absolute inset-0 overflow-hidden')}>
|
|
1214
|
+
<img
|
|
1215
|
+
src={video(backgroundVideo)}
|
|
1216
|
+
style={{
|
|
1217
|
+
...tw('w-full h-full object-cover'),
|
|
1218
|
+
filter: 'blur(20px)',
|
|
1219
|
+
transform: 'scale(1.1)' // Prevent blur edge artifacts
|
|
1220
|
+
}}
|
|
1221
|
+
/>
|
|
1222
|
+
<div style={tw('absolute inset-0 bg-white/50')} />
|
|
1223
|
+
</div>
|
|
1224
|
+
|
|
1225
|
+
{/* Main product image */}
|
|
1226
|
+
<div style={tw('relative z-10 flex items-center justify-center w-full h-full p-20')}>
|
|
1227
|
+
<div style={{ transform: `scale(${scale})` }}>
|
|
1228
|
+
<img
|
|
1229
|
+
src={image(productImage)}
|
|
1230
|
+
style={tw('w-96 h-96 object-contain drop-shadow-2xl')}
|
|
1231
|
+
/>
|
|
1232
|
+
</div>
|
|
1233
|
+
</div>
|
|
1234
|
+
|
|
1235
|
+
{/* Animated badge */}
|
|
1236
|
+
<div
|
|
1237
|
+
style={{
|
|
1238
|
+
...tw('absolute top-20 right-20'),
|
|
1239
|
+
opacity: badgeOpacity,
|
|
1240
|
+
transform: `rotate(${badgeRotation}deg)`
|
|
1241
|
+
}}
|
|
1242
|
+
>
|
|
1243
|
+
<img
|
|
1244
|
+
src={image(badge)}
|
|
1245
|
+
style={tw('w-32 h-32')}
|
|
1246
|
+
/>
|
|
1247
|
+
</div>
|
|
1248
|
+
|
|
1249
|
+
{/* Title */}
|
|
1250
|
+
<div style={tw('absolute top-20 left-20')}>
|
|
1251
|
+
<h2 style={tw('text-5xl font-bold text-gray-900')}>
|
|
1252
|
+
{title}
|
|
1253
|
+
</h2>
|
|
1254
|
+
</div>
|
|
1255
|
+
|
|
1256
|
+
{/* Animated price */}
|
|
1257
|
+
<div
|
|
1258
|
+
style={{
|
|
1259
|
+
...tw('absolute bottom-20 left-20'),
|
|
1260
|
+
transform: `translateY(${priceY}px)`
|
|
1261
|
+
}}
|
|
1262
|
+
>
|
|
1263
|
+
<div style={tw('text-6xl font-black text-blue-600')}>
|
|
1264
|
+
${price}
|
|
1265
|
+
</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
</div>
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
**Props:**
|
|
1273
|
+
```json
|
|
1274
|
+
{
|
|
1275
|
+
"title": "Premium Headphones",
|
|
1276
|
+
"price": "299",
|
|
1277
|
+
"backgroundVideo": "./footage/studio-background.mp4",
|
|
1278
|
+
"productImage": "./images/headphones.png",
|
|
1279
|
+
"badge": "./images/new-badge.png"
|
|
1280
|
+
}
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
### Video with Multiple Image Overlays
|
|
1284
|
+
|
|
1285
|
+
```tsx
|
|
1286
|
+
export default function TutorialVideo({
|
|
1287
|
+
tw,
|
|
1288
|
+
video,
|
|
1289
|
+
image,
|
|
1290
|
+
progress,
|
|
1291
|
+
frame,
|
|
1292
|
+
screencast,
|
|
1293
|
+
avatar,
|
|
1294
|
+
logo,
|
|
1295
|
+
instructor,
|
|
1296
|
+
lesson
|
|
1297
|
+
}) {
|
|
1298
|
+
// Avatar pulses
|
|
1299
|
+
const avatarScale = 1 + Math.sin(frame * 0.2) * 0.05;
|
|
1300
|
+
|
|
1301
|
+
// Logo stays visible
|
|
1302
|
+
const logoOpacity = 1;
|
|
1303
|
+
|
|
1304
|
+
return (
|
|
1305
|
+
<div style={tw('relative w-full h-full bg-gray-900')}>
|
|
1306
|
+
{/* Main screencast video */}
|
|
1307
|
+
<img
|
|
1308
|
+
src={video(screencast)}
|
|
1309
|
+
style={tw('absolute inset-0 w-full h-full object-contain')}
|
|
1310
|
+
/>
|
|
1311
|
+
|
|
1312
|
+
{/* Company logo (top left) */}
|
|
1313
|
+
<div style={{
|
|
1314
|
+
...tw('absolute top-8 left-8'),
|
|
1315
|
+
opacity: logoOpacity
|
|
1316
|
+
}}>
|
|
1317
|
+
<img
|
|
1318
|
+
src={image(logo)}
|
|
1319
|
+
style={tw('h-12 w-auto')}
|
|
1320
|
+
/>
|
|
1321
|
+
</div>
|
|
1322
|
+
|
|
1323
|
+
{/* Instructor avatar (bottom right) */}
|
|
1324
|
+
<div style={tw('absolute bottom-8 right-8')}>
|
|
1325
|
+
<div
|
|
1326
|
+
style={{
|
|
1327
|
+
...tw('relative'),
|
|
1328
|
+
transform: `scale(${avatarScale})`
|
|
1329
|
+
}}
|
|
1330
|
+
>
|
|
1331
|
+
<img
|
|
1332
|
+
src={image(avatar)}
|
|
1333
|
+
style={tw('w-24 h-24 rounded-full border-4 border-blue-500')}
|
|
1334
|
+
/>
|
|
1335
|
+
{/* Red recording dot */}
|
|
1336
|
+
<div style={tw('absolute -top-1 -right-1 w-6 h-6 bg-red-500 rounded-full animate-pulse')} />
|
|
1337
|
+
</div>
|
|
1338
|
+
</div>
|
|
1339
|
+
|
|
1340
|
+
{/* Lesson info overlay */}
|
|
1341
|
+
<div style={tw('absolute bottom-8 left-8 bg-black/80 backdrop-blur px-6 py-4 rounded-lg')}>
|
|
1342
|
+
<p style={tw('text-sm text-gray-400')}>Instructor</p>
|
|
1343
|
+
<p style={tw('text-xl font-bold text-white mb-2')}>{instructor}</p>
|
|
1344
|
+
<p style={tw('text-sm text-blue-400')}>{lesson}</p>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
**Props:**
|
|
1352
|
+
```json
|
|
1353
|
+
{
|
|
1354
|
+
"instructor": "Sarah Johnson",
|
|
1355
|
+
"lesson": "Lesson 3: Advanced Techniques",
|
|
1356
|
+
"screencast": "./footage/screen-recording.mp4",
|
|
1357
|
+
"avatar": "./images/instructor.jpg",
|
|
1358
|
+
"logo": "./images/course-logo.png"
|
|
1359
|
+
}
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
### Supported Formats
|
|
1363
|
+
|
|
1364
|
+
**Video formats** (via `video()` helper):
|
|
1365
|
+
- ✅ MP4 (`.mp4`)
|
|
1366
|
+
- ✅ MOV (`.mov`)
|
|
1367
|
+
- ✅ WebM (`.webm`)
|
|
1368
|
+
- ✅ AVI (`.avi`)
|
|
1369
|
+
|
|
1370
|
+
**Image formats** (via `image()` helper):
|
|
1371
|
+
- ✅ JPEG (`.jpg`, `.jpeg`)
|
|
1372
|
+
- ✅ PNG (`.png`) - with transparency
|
|
1373
|
+
- ✅ GIF (`.gif`)
|
|
1374
|
+
- ✅ WebP (`.webp`)
|
|
1375
|
+
- ✅ SVG (`.svg`)
|
|
1376
|
+
|
|
1377
|
+
### Best Practices
|
|
1378
|
+
|
|
1379
|
+
1. **Pre-optimize assets**: Resize images and videos to match your output resolution
|
|
1380
|
+
2. **Use PNG for logos**: Transparency works perfectly with video backgrounds
|
|
1381
|
+
3. **Match frame rates**: If possible, use videos with the same FPS as your template
|
|
1382
|
+
4. **Consider file sizes**: Large videos take longer to process
|
|
1383
|
+
5. **Test opacity**: Ensure overlays are visible against video backgrounds
|
|
1384
|
+
|
|
1385
|
+
## FPS and Duration
|
|
1386
|
+
|
|
1387
|
+
### Common Frame Rates
|
|
1388
|
+
|
|
1389
|
+
```json
|
|
1390
|
+
{
|
|
1391
|
+
"video": {
|
|
1392
|
+
"fps": 24, // Film standard
|
|
1393
|
+
"duration": 5
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
```json
|
|
1399
|
+
{
|
|
1400
|
+
"video": {
|
|
1401
|
+
"fps": 30, // YouTube/web standard
|
|
1402
|
+
"duration": 3
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
```json
|
|
1408
|
+
{
|
|
1409
|
+
"video": {
|
|
1410
|
+
"fps": 60, // Smooth animations
|
|
1411
|
+
"duration": 2
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1416
|
+
**Total frames = fps × duration**
|
|
1417
|
+
- 24fps × 5s = 120 frames
|
|
1418
|
+
- 30fps × 3s = 90 frames
|
|
1419
|
+
- 60fps × 2s = 120 frames
|
|
1420
|
+
|
|
1421
|
+
### Choosing FPS
|
|
1422
|
+
|
|
1423
|
+
- **24fps**: Cinematic look, smaller files
|
|
1424
|
+
- **30fps**: Standard web video, good balance
|
|
1425
|
+
- **60fps**: Smooth animations, larger files
|
|
1426
|
+
|
|
1427
|
+
## Encoding Options
|
|
1428
|
+
|
|
1429
|
+
### Default (Balanced)
|
|
1430
|
+
|
|
1431
|
+
```bash
|
|
1432
|
+
dsgn render video-intro '{"title":"Welcome"}'
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
Uses H.264 codec with good quality/size balance.
|
|
1436
|
+
|
|
1437
|
+
### Fast Encoding
|
|
1438
|
+
|
|
1439
|
+
```bash
|
|
1440
|
+
dsgn render video-intro '{"title":"Welcome"}' --preset ultrafast
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
Faster encoding, slightly larger files. Good for previews.
|
|
1444
|
+
|
|
1445
|
+
### High Quality
|
|
1446
|
+
|
|
1447
|
+
```bash
|
|
1448
|
+
dsgn render video-intro '{"title":"Welcome"}' --preset slow
|
|
1449
|
+
```
|
|
1450
|
+
|
|
1451
|
+
Better compression, slower encoding. Good for final output.
|
|
1452
|
+
|
|
1453
|
+
## Performance
|
|
1454
|
+
|
|
1455
|
+
### Rendering Speed
|
|
1456
|
+
|
|
1457
|
+
On an M1 Mac:
|
|
1458
|
+
- **30fps, 3s video** (90 frames): ~15-20 seconds
|
|
1459
|
+
- **60fps, 3s video** (180 frames): ~30-40 seconds
|
|
1460
|
+
|
|
1461
|
+
Speed depends on:
|
|
1462
|
+
- Template complexity
|
|
1463
|
+
- Image/video embedding
|
|
1464
|
+
- CPU performance
|
|
1465
|
+
- Frame count
|
|
1466
|
+
|
|
1467
|
+
### Optimization Tips
|
|
1468
|
+
|
|
1469
|
+
1. **Lower FPS for previews**: Test at 15fps, render final at 30fps
|
|
1470
|
+
2. **Shorter duration**: Preview with 1s, expand for final
|
|
1471
|
+
3. **Simplify templates**: Complex layouts take longer
|
|
1472
|
+
4. **Pre-optimize media**: Resize images/videos before embedding
|
|
1473
|
+
|
|
1474
|
+
### Memory Usage
|
|
1475
|
+
|
|
1476
|
+
Video frames are cached in memory during rendering:
|
|
1477
|
+
- **1920x1080 @ 30fps for 3s**: ~500MB RAM
|
|
1478
|
+
- **1920x1080 @ 60fps for 5s**: ~1.7GB RAM
|
|
1479
|
+
|
|
1480
|
+
For long videos, consider rendering in segments.
|
|
1481
|
+
|
|
1482
|
+
## Common Video Templates
|
|
1483
|
+
|
|
1484
|
+
### Social Media Intro
|
|
1485
|
+
|
|
1486
|
+
```tsx
|
|
1487
|
+
export default function SocialIntro({ tw, progress, brand, tagline }) {
|
|
1488
|
+
const logoScale = progress < 0.4 ? progress / 0.4 : 1;
|
|
1489
|
+
const taglineOpacity = progress > 0.5 ? (progress - 0.5) / 0.5 : 0;
|
|
1490
|
+
|
|
1491
|
+
return (
|
|
1492
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-pink-500 to-purple-600')}>
|
|
1493
|
+
<h1
|
|
1494
|
+
style={{
|
|
1495
|
+
...tw('text-9xl font-black text-white mb-8'),
|
|
1496
|
+
transform: `scale(${logoScale})`
|
|
1497
|
+
}}
|
|
1498
|
+
>
|
|
1499
|
+
{brand}
|
|
1500
|
+
</h1>
|
|
1501
|
+
|
|
1502
|
+
<p
|
|
1503
|
+
style={{
|
|
1504
|
+
...tw('text-3xl text-white'),
|
|
1505
|
+
opacity: taglineOpacity
|
|
1506
|
+
}}
|
|
1507
|
+
>
|
|
1508
|
+
{tagline}
|
|
1509
|
+
</p>
|
|
1510
|
+
</div>
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
### Progress Bar Animation
|
|
1516
|
+
|
|
1517
|
+
```tsx
|
|
1518
|
+
export default function ProgressVideo({ tw, progress, title, subtitle }) {
|
|
1519
|
+
return (
|
|
1520
|
+
<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')}>
|
|
1522
|
+
{title}
|
|
1523
|
+
</h2>
|
|
1524
|
+
|
|
1525
|
+
{/* Progress bar */}
|
|
1526
|
+
<div style={tw('w-full max-w-2xl h-4 bg-gray-700 rounded-full overflow-hidden')}>
|
|
1527
|
+
<div
|
|
1528
|
+
style={{
|
|
1529
|
+
...tw('h-full bg-gradient-to-r from-blue-500 to-green-500'),
|
|
1530
|
+
width: `${progress * 100}%`
|
|
1531
|
+
}}
|
|
1532
|
+
/>
|
|
1533
|
+
</div>
|
|
1534
|
+
|
|
1535
|
+
<p style={tw('text-2xl text-gray-400 mt-8')}>
|
|
1536
|
+
{subtitle}
|
|
1537
|
+
</p>
|
|
1538
|
+
</div>
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
```
|
|
1542
|
+
|
|
1543
|
+
### Countdown Timer
|
|
1544
|
+
|
|
1545
|
+
```tsx
|
|
1546
|
+
export default function CountdownVideo({ tw, frame, message }) {
|
|
1547
|
+
const fps = 30;
|
|
1548
|
+
const secondsRemaining = Math.ceil((90 - frame) / fps); // 3s countdown
|
|
1549
|
+
|
|
1550
|
+
return (
|
|
1551
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
|
|
1552
|
+
<div style={tw('text-[200px] font-black text-white')}>
|
|
1553
|
+
{secondsRemaining}
|
|
1554
|
+
</div>
|
|
1555
|
+
<p style={tw('text-3xl text-gray-400 mt-8')}>
|
|
1556
|
+
{message}
|
|
1557
|
+
</p>
|
|
1558
|
+
</div>
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
## Output Format
|
|
1564
|
+
|
|
1565
|
+
Videos are always rendered as MP4 (H.264 codec) for maximum compatibility.
|
|
1566
|
+
|
|
1567
|
+
## Next Steps
|
|
1568
|
+
|
|
1569
|
+
- [Learn about Image Rendering](/images)
|
|
1570
|
+
- [QR Codes and Template Composition](/helpers)
|
|
1571
|
+
- [Styling with Tailwind & shadcn/ui](/styling)
|
|
1572
|
+
- [Custom Fonts in Videos](/fonts)
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
================================================================================
|
|
1579
|
+
FILE: helpers.mdx
|
|
1580
|
+
================================================================================
|
|
1581
|
+
|
|
1582
|
+
# Template Helpers
|
|
1583
|
+
|
|
1584
|
+
Additional helpers for creating powerful, composable templates.
|
|
1585
|
+
|
|
1586
|
+
## Overview
|
|
1587
|
+
|
|
1588
|
+
Beyond the basics, dsgn provides:
|
|
1589
|
+
- `template()` - Compose templates together
|
|
1590
|
+
- `qr()` - Generate QR codes on the fly
|
|
1591
|
+
- `config` - Access user configuration
|
|
1592
|
+
|
|
1593
|
+
For image and video embedding, see the [Images](/images) and [Video](/video) pages.
|
|
1594
|
+
|
|
1595
|
+
## Template Composition
|
|
1596
|
+
|
|
1597
|
+
Compose multiple templates together to create complex designs.
|
|
1598
|
+
|
|
1599
|
+
### Usage
|
|
1600
|
+
|
|
1601
|
+
```tsx
|
|
1602
|
+
export default function CompositeCard({ tw, template, title, author, avatar }) {
|
|
1603
|
+
return (
|
|
1604
|
+
<div style={tw('w-full h-full bg-gradient-to-br from-purple-600 to-blue-500 p-12')}>
|
|
1605
|
+
<div style={tw('bg-white rounded-2xl p-8 shadow-xl')}>
|
|
1606
|
+
<h1 style={tw('text-4xl font-bold text-gray-900 mb-6')}>{title}</h1>
|
|
1607
|
+
|
|
1608
|
+
{/* Embed another template */}
|
|
1609
|
+
<div style={tw('mb-6')}>
|
|
1610
|
+
{template('user-badge', {
|
|
1611
|
+
name: author,
|
|
1612
|
+
avatar: avatar
|
|
1613
|
+
})}
|
|
1614
|
+
</div>
|
|
1615
|
+
|
|
1616
|
+
<p style={tw('text-gray-600')}>Published by {author}</p>
|
|
1617
|
+
</div>
|
|
1618
|
+
</div>
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
**How it works:**
|
|
1624
|
+
1. `template(name, props)` renders another installed template
|
|
1625
|
+
2. The embedded template is rendered at its specified size
|
|
1626
|
+
3. You can embed multiple templates in one design
|
|
1627
|
+
4. Templates can be nested (template within a template)
|
|
1628
|
+
|
|
1629
|
+
### Use Cases
|
|
1630
|
+
|
|
1631
|
+
**1. Reusable components:**
|
|
1632
|
+
```tsx
|
|
1633
|
+
// Create a logo template once, use it everywhere
|
|
1634
|
+
<div>{template('company-logo', { variant: 'dark' })}</div>
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
**2. Complex layouts:**
|
|
1638
|
+
```tsx
|
|
1639
|
+
// Combine multiple templates into one design
|
|
1640
|
+
<div style={tw('grid grid-cols-2 gap-4')}>
|
|
1641
|
+
{template('product-card', { product: product1 })}
|
|
1642
|
+
{template('product-card', { product: product2 })}
|
|
1643
|
+
</div>
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
**3. Dynamic content:**
|
|
1647
|
+
```tsx
|
|
1648
|
+
// Render templates based on data
|
|
1649
|
+
{users.map(user =>
|
|
1650
|
+
template('user-avatar', { name: user.name, image: user.avatar })
|
|
1651
|
+
)}
|
|
1652
|
+
```
|
|
1653
|
+
|
|
1654
|
+
### Best Practices
|
|
1655
|
+
|
|
1656
|
+
1. **Keep templates focused** - Each template should do one thing well
|
|
1657
|
+
2. **Pass minimal props** - Only pass what the embedded template needs
|
|
1658
|
+
3. **Document dependencies** - Note which templates are required in your README
|
|
1659
|
+
4. **Avoid deep nesting** - Too many nested templates can be hard to debug
|
|
1660
|
+
|
|
1661
|
+
## QR Codes
|
|
1662
|
+
|
|
1663
|
+
Generate QR codes dynamically in your templates.
|
|
1664
|
+
|
|
1665
|
+
### Usage
|
|
1666
|
+
|
|
1667
|
+
```tsx
|
|
1668
|
+
export default function QRCard({ tw, qr, title, url }) {
|
|
1669
|
+
return (
|
|
1670
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-white p-10')}>
|
|
1671
|
+
<h1 style={tw('text-4xl font-bold text-black mb-8')}>{title}</h1>
|
|
1672
|
+
|
|
1673
|
+
{/* Generate QR code for the URL */}
|
|
1674
|
+
<img src={qr(url)} style={tw('w-64 h-64')} />
|
|
1675
|
+
|
|
1676
|
+
<p style={tw('text-gray-600 mt-4')}>{url}</p>
|
|
1677
|
+
</div>
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
**Props format:**
|
|
1683
|
+
```json
|
|
1684
|
+
{
|
|
1685
|
+
"title": "Scan Me",
|
|
1686
|
+
"url": "https://example.com"
|
|
1687
|
+
}
|
|
1688
|
+
```
|
|
1689
|
+
|
|
1690
|
+
### QR Options
|
|
1691
|
+
|
|
1692
|
+
You can customize QR code appearance:
|
|
1693
|
+
|
|
1694
|
+
```tsx
|
|
1695
|
+
// Basic QR code
|
|
1696
|
+
<img src={qr('https://example.com')} />
|
|
1697
|
+
|
|
1698
|
+
// With error correction level
|
|
1699
|
+
<img src={qr('https://example.com', { errorCorrectionLevel: 'H' })} />
|
|
1700
|
+
|
|
1701
|
+
// With custom size
|
|
1702
|
+
<img src={qr('https://example.com', { width: 512 })} />
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1705
|
+
**Error correction levels:**
|
|
1706
|
+
- `L` - Low (~7% correction)
|
|
1707
|
+
- `M` - Medium (~15% correction) - default
|
|
1708
|
+
- `Q` - Quartile (~25% correction)
|
|
1709
|
+
- `H` - High (~30% correction)
|
|
1710
|
+
|
|
1711
|
+
## User Configuration
|
|
1712
|
+
|
|
1713
|
+
Access user settings from `dsgn.json` using the `config` prop:
|
|
1714
|
+
|
|
1715
|
+
```tsx
|
|
1716
|
+
export default function BrandedTemplate({ tw, config, title }) {
|
|
1717
|
+
// Access custom colors from dsgn.json
|
|
1718
|
+
const primaryColor = config?.colors?.brand || '#6366f1';
|
|
1719
|
+
|
|
1720
|
+
return (
|
|
1721
|
+
<div style={tw('w-full h-full p-12')}>
|
|
1722
|
+
<h1 style={{
|
|
1723
|
+
...tw('text-6xl font-bold'),
|
|
1724
|
+
color: primaryColor
|
|
1725
|
+
}}>
|
|
1726
|
+
{title}
|
|
1727
|
+
</h1>
|
|
1728
|
+
</div>
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
**User's dsgn.json:**
|
|
1734
|
+
```json
|
|
1735
|
+
{
|
|
1736
|
+
"colors": {
|
|
1737
|
+
"brand": "#ff6b6b"
|
|
1738
|
+
},
|
|
1739
|
+
"fonts": {
|
|
1740
|
+
"sans": ["Inter", "system-ui", "sans-serif"]
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
```
|
|
1744
|
+
|
|
1745
|
+
This allows templates to adapt to user preferences and brand guidelines.
|
|
1746
|
+
|
|
1747
|
+
## All Props Reference
|
|
1748
|
+
|
|
1749
|
+
Every template receives these props:
|
|
1750
|
+
|
|
1751
|
+
```tsx
|
|
1752
|
+
export default function MyTemplate({
|
|
1753
|
+
// Core helpers
|
|
1754
|
+
tw, // Tailwind class converter
|
|
1755
|
+
qr, // QR code generator (this page)
|
|
1756
|
+
template, // Template composer (this page)
|
|
1757
|
+
config, // User config from dsgn.json (this page)
|
|
1758
|
+
|
|
1759
|
+
// Media helpers (see dedicated pages)
|
|
1760
|
+
image, // Image embedder → see /images
|
|
1761
|
+
video, // Video frame getter → see /video
|
|
1762
|
+
|
|
1763
|
+
// Video-specific (only in video templates)
|
|
1764
|
+
frame, // Current frame number → see /video
|
|
1765
|
+
progress, // Animation progress 0-1 → see /video
|
|
1766
|
+
|
|
1767
|
+
// Your custom props
|
|
1768
|
+
...props // Any props from your props.json
|
|
1769
|
+
}) {
|
|
1770
|
+
// Your template code
|
|
1771
|
+
}
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
## Next Steps
|
|
1775
|
+
|
|
1776
|
+
- [Image Rendering and Embedding](/images)
|
|
1777
|
+
- [Video Rendering and Animation](/video)
|
|
1778
|
+
- [Styling with Tailwind & shadcn/ui](/styling)
|
|
1779
|
+
- [Custom Fonts](/fonts)
|
|
1780
|
+
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
================================================================================
|
|
1786
|
+
FILE: styling.mdx
|
|
1787
|
+
================================================================================
|
|
1788
|
+
|
|
1789
|
+
# Styling Templates
|
|
1790
|
+
|
|
1791
|
+
Style your templates with Tailwind utility classes and shadcn/ui's beautiful design system.
|
|
1792
|
+
|
|
1793
|
+
## Quick Start
|
|
1794
|
+
|
|
1795
|
+
```tsx
|
|
1796
|
+
export default function MyTemplate({ title, tw }) {
|
|
1797
|
+
return (
|
|
1798
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
|
|
1799
|
+
<h1 style={tw('text-7xl font-bold text-white')}>
|
|
1800
|
+
{title}
|
|
1801
|
+
</h1>
|
|
1802
|
+
</div>
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
```
|
|
1806
|
+
|
|
1807
|
+
## The `tw()` Function
|
|
1808
|
+
|
|
1809
|
+
Every template receives a `tw()` function that converts Tailwind classes to inline styles compatible with Satori:
|
|
1810
|
+
|
|
1811
|
+
```tsx
|
|
1812
|
+
// Tailwind classes
|
|
1813
|
+
tw('flex items-center justify-center p-8 bg-blue-500')
|
|
1814
|
+
|
|
1815
|
+
// Converts to inline styles:
|
|
1816
|
+
{
|
|
1817
|
+
display: 'flex',
|
|
1818
|
+
alignItems: 'center',
|
|
1819
|
+
justifyContent: 'center',
|
|
1820
|
+
padding: '2rem',
|
|
1821
|
+
backgroundColor: '#3b82f6'
|
|
1822
|
+
}
|
|
1823
|
+
```
|
|
1824
|
+
|
|
1825
|
+
### Basic Usage
|
|
1826
|
+
|
|
1827
|
+
```tsx
|
|
1828
|
+
export default function Banner({ title, subtitle, tw }) {
|
|
1829
|
+
return (
|
|
1830
|
+
<div style={tw('w-full h-full p-12 bg-gray-50')}>
|
|
1831
|
+
<h1 style={tw('text-6xl font-bold text-gray-900 mb-4')}>
|
|
1832
|
+
{title}
|
|
1833
|
+
</h1>
|
|
1834
|
+
<p style={tw('text-2xl text-gray-600')}>
|
|
1835
|
+
{subtitle}
|
|
1836
|
+
</p>
|
|
1837
|
+
</div>
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
```
|
|
1841
|
+
|
|
1842
|
+
### Combining with Custom Styles
|
|
1843
|
+
|
|
1844
|
+
Mix Tailwind classes with custom styles using the spread operator:
|
|
1845
|
+
|
|
1846
|
+
```tsx
|
|
1847
|
+
export default function CustomGradient({ title, tw }) {
|
|
1848
|
+
return (
|
|
1849
|
+
<div
|
|
1850
|
+
style={{
|
|
1851
|
+
...tw('flex flex-col items-center justify-center w-full h-full p-20'),
|
|
1852
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
1853
|
+
}}
|
|
1854
|
+
>
|
|
1855
|
+
<h1 style={tw('text-8xl font-bold text-white')}>{title}</h1>
|
|
1856
|
+
</div>
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
```
|
|
1860
|
+
|
|
1861
|
+
## shadcn/ui Design System
|
|
1862
|
+
|
|
1863
|
+
dsgn uses **shadcn/ui's design system** by default, providing semantic color tokens for beautiful, consistent designs.
|
|
1864
|
+
|
|
1865
|
+
### Default Color Palette
|
|
1866
|
+
|
|
1867
|
+
All templates automatically have access to these semantic colors:
|
|
1868
|
+
|
|
1869
|
+
```typescript
|
|
1870
|
+
colors: {
|
|
1871
|
+
// Primary colors
|
|
1872
|
+
primary: '#18181b', // Main brand color
|
|
1873
|
+
'primary-foreground': '#fafafa',
|
|
1874
|
+
|
|
1875
|
+
// Secondary colors
|
|
1876
|
+
secondary: '#f4f4f5', // Subtle accents
|
|
1877
|
+
'secondary-foreground': '#18181b',
|
|
1878
|
+
|
|
1879
|
+
// Background
|
|
1880
|
+
background: '#ffffff', // Page background
|
|
1881
|
+
foreground: '#09090b', // Main text color
|
|
1882
|
+
|
|
1883
|
+
// Muted
|
|
1884
|
+
muted: '#f4f4f5', // Subtle backgrounds
|
|
1885
|
+
'muted-foreground': '#71717a', // Muted text
|
|
1886
|
+
|
|
1887
|
+
// Accent
|
|
1888
|
+
accent: '#f4f4f5', // Highlight color
|
|
1889
|
+
'accent-foreground': '#18181b',
|
|
1890
|
+
|
|
1891
|
+
// Destructive
|
|
1892
|
+
destructive: '#ef4444', // Error/danger states
|
|
1893
|
+
'destructive-foreground': '#fafafa',
|
|
1894
|
+
|
|
1895
|
+
// UI Elements
|
|
1896
|
+
border: '#e4e4e7', // Border color
|
|
1897
|
+
input: '#e4e4e7', // Input borders
|
|
1898
|
+
ring: '#18181b', // Focus rings
|
|
1899
|
+
card: '#ffffff', // Card background
|
|
1900
|
+
'card-foreground': '#09090b',
|
|
1901
|
+
}
|
|
1902
|
+
```
|
|
1903
|
+
|
|
1904
|
+
### Using Semantic Colors
|
|
1905
|
+
|
|
1906
|
+
```tsx
|
|
1907
|
+
export default function SemanticCard({ title, description, price, tw }) {
|
|
1908
|
+
return (
|
|
1909
|
+
<div style={tw('bg-card border border-border rounded-lg p-6')}>
|
|
1910
|
+
<h2 style={tw('text-card-foreground text-2xl font-bold mb-2')}>
|
|
1911
|
+
{title}
|
|
1912
|
+
</h2>
|
|
1913
|
+
<p style={tw('text-muted-foreground mb-4')}>
|
|
1914
|
+
{description}
|
|
1915
|
+
</p>
|
|
1916
|
+
<div style={tw('text-primary text-3xl font-bold')}>
|
|
1917
|
+
${price}
|
|
1918
|
+
</div>
|
|
1919
|
+
</div>
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
```
|
|
1923
|
+
|
|
1924
|
+
### Opacity Modifiers
|
|
1925
|
+
|
|
1926
|
+
Use Tailwind's slash syntax for opacity with any color:
|
|
1927
|
+
|
|
1928
|
+
```tsx
|
|
1929
|
+
export default function OpacityExample({ tw }) {
|
|
1930
|
+
return (
|
|
1931
|
+
<div style={tw('bg-primary/50')}> {/* 50% opacity */}
|
|
1932
|
+
<p style={tw('text-muted-foreground/75')}> {/* 75% opacity */}
|
|
1933
|
+
Subtle text
|
|
1934
|
+
</p>
|
|
1935
|
+
<div style={tw('border border-border/30')}> {/* 30% opacity */}
|
|
1936
|
+
Faint border
|
|
1937
|
+
</div>
|
|
1938
|
+
</div>
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
```
|
|
1942
|
+
|
|
1943
|
+
**Supported syntax:**
|
|
1944
|
+
- `bg-{color}/{opacity}` - Background with opacity
|
|
1945
|
+
- `text-{color}/{opacity}` - Text with opacity
|
|
1946
|
+
- `border-{color}/{opacity}` - Border with opacity
|
|
1947
|
+
|
|
1948
|
+
### Text Hierarchy
|
|
1949
|
+
|
|
1950
|
+
```tsx
|
|
1951
|
+
// Primary text
|
|
1952
|
+
tw('text-foreground')
|
|
1953
|
+
|
|
1954
|
+
// Secondary/muted text
|
|
1955
|
+
tw('text-muted-foreground')
|
|
1956
|
+
|
|
1957
|
+
// Accent/brand text
|
|
1958
|
+
tw('text-primary')
|
|
1959
|
+
|
|
1960
|
+
// Destructive/error text
|
|
1961
|
+
tw('text-destructive')
|
|
1962
|
+
```
|
|
1963
|
+
|
|
1964
|
+
### Backgrounds
|
|
1965
|
+
|
|
1966
|
+
```tsx
|
|
1967
|
+
// Page background
|
|
1968
|
+
tw('bg-background')
|
|
1969
|
+
|
|
1970
|
+
// Card/elevated surfaces
|
|
1971
|
+
tw('bg-card')
|
|
1972
|
+
|
|
1973
|
+
// Subtle backgrounds
|
|
1974
|
+
tw('bg-muted')
|
|
1975
|
+
|
|
1976
|
+
// Accent backgrounds
|
|
1977
|
+
tw('bg-accent')
|
|
1978
|
+
```
|
|
1979
|
+
|
|
1980
|
+
## Supported Tailwind Classes
|
|
1981
|
+
|
|
1982
|
+
### Layout
|
|
1983
|
+
|
|
1984
|
+
- **Display**: `flex`, `inline-flex`, `block`, `inline-block`, `hidden`
|
|
1985
|
+
- **Flex Direction**: `flex-row`, `flex-col`, `flex-row-reverse`, `flex-col-reverse`
|
|
1986
|
+
- **Justify**: `justify-start`, `justify-end`, `justify-center`, `justify-between`, `justify-around`
|
|
1987
|
+
- **Align**: `items-start`, `items-end`, `items-center`, `items-baseline`, `items-stretch`
|
|
1988
|
+
|
|
1989
|
+
### Spacing
|
|
1990
|
+
|
|
1991
|
+
- **Padding**: `p-{n}`, `px-{n}`, `py-{n}`, `pt-{n}`, `pb-{n}`, `pl-{n}`, `pr-{n}`
|
|
1992
|
+
- **Margin**: `m-{n}`, `mx-{n}`, `my-{n}`, `mt-{n}`, `mb-{n}`, `ml-{n}`, `mr-{n}`
|
|
1993
|
+
- **Gap**: `gap-{n}`, `gap-x-{n}`, `gap-y-{n}`
|
|
1994
|
+
- **Sizes**: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64
|
|
1995
|
+
|
|
1996
|
+
Examples:
|
|
1997
|
+
```tsx
|
|
1998
|
+
tw('p-4') // padding: 1rem
|
|
1999
|
+
tw('px-8') // paddingLeft: 2rem, paddingRight: 2rem
|
|
2000
|
+
tw('m-6') // margin: 1.5rem
|
|
2001
|
+
tw('gap-4') // gap: 1rem
|
|
2002
|
+
```
|
|
2003
|
+
|
|
2004
|
+
### Sizing
|
|
2005
|
+
|
|
2006
|
+
- **Width**: `w-{n}`, `w-full`, `w-screen`, `w-1/2`, `w-1/3`, `w-2/3`
|
|
2007
|
+
- **Height**: `h-{n}`, `h-full`, `h-screen`
|
|
2008
|
+
|
|
2009
|
+
Examples:
|
|
2010
|
+
```tsx
|
|
2011
|
+
tw('w-full') // width: 100%
|
|
2012
|
+
tw('h-64') // height: 16rem
|
|
2013
|
+
tw('w-1/2') // width: 50%
|
|
2014
|
+
```
|
|
2015
|
+
|
|
2016
|
+
### Typography
|
|
2017
|
+
|
|
2018
|
+
- **Font Size**: `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, `text-3xl`, `text-4xl`, `text-5xl`, `text-6xl`, `text-7xl`, `text-8xl`, `text-9xl`
|
|
2019
|
+
- **Font Weight**: `font-thin`, `font-light`, `font-normal`, `font-medium`, `font-semibold`, `font-bold`, `font-extrabold`, `font-black`
|
|
2020
|
+
- **Text Align**: `text-left`, `text-center`, `text-right`
|
|
2021
|
+
- **Line Height**: `leading-none`, `leading-tight`, `leading-normal`, `leading-relaxed`, `leading-loose`
|
|
2022
|
+
|
|
2023
|
+
### Colors
|
|
2024
|
+
|
|
2025
|
+
All standard Tailwind colors plus shadcn semantic colors:
|
|
2026
|
+
|
|
2027
|
+
**Standard colors:**
|
|
2028
|
+
- `text-{color}-{shade}`, `bg-{color}-{shade}`, `border-{color}-{shade}`
|
|
2029
|
+
- Colors: `red`, `blue`, `green`, `yellow`, `purple`, `pink`, `gray`, `indigo`, `teal`, `orange`
|
|
2030
|
+
- Shades: `50`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`
|
|
2031
|
+
|
|
2032
|
+
**shadcn semantic colors:**
|
|
2033
|
+
- `text-foreground`, `text-primary`, `text-muted-foreground`, `text-destructive`
|
|
2034
|
+
- `bg-background`, `bg-card`, `bg-muted`, `bg-accent`, `bg-primary`
|
|
2035
|
+
- `border-border`, `border-input`
|
|
2036
|
+
|
|
2037
|
+
```tsx
|
|
2038
|
+
tw('text-blue-500') // Standard Tailwind color
|
|
2039
|
+
tw('bg-purple-600') // Standard Tailwind color
|
|
2040
|
+
tw('text-primary') // shadcn semantic color
|
|
2041
|
+
tw('bg-card') // shadcn semantic color
|
|
2042
|
+
```
|
|
2043
|
+
|
|
2044
|
+
### Position & Layout
|
|
2045
|
+
|
|
2046
|
+
- **Position**: `relative`, `absolute`, `fixed`, `sticky`
|
|
2047
|
+
- **Inset**: `inset-0`, `top-0`, `bottom-0`, `left-0`, `right-0`
|
|
2048
|
+
- **Z-Index**: `z-0`, `z-10`, `z-20`, `z-30`, `z-40`, `z-50`
|
|
2049
|
+
|
|
2050
|
+
### Borders
|
|
2051
|
+
|
|
2052
|
+
- **Border Width**: `border`, `border-{n}`, `border-t`, `border-b`, `border-l`, `border-r`
|
|
2053
|
+
- **Border Radius**: `rounded`, `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-full`
|
|
2054
|
+
- **Border Color**: `border-{color}-{shade}`, `border-border`, `border-input`
|
|
2055
|
+
|
|
2056
|
+
### Effects
|
|
2057
|
+
|
|
2058
|
+
- **Shadow**: `shadow-sm`, `shadow`, `shadow-md`, `shadow-lg`, `shadow-xl`, `shadow-2xl`
|
|
2059
|
+
- **Opacity**: `opacity-0`, `opacity-25`, `opacity-50`, `opacity-75`, `opacity-100`
|
|
2060
|
+
|
|
2061
|
+
### Filters
|
|
2062
|
+
|
|
2063
|
+
- **Blur**: `blur-none`, `blur-sm`, `blur`, `blur-md`, `blur-lg`, `blur-xl`
|
|
2064
|
+
- **Brightness**: `brightness-0`, `brightness-50`, `brightness-100`, `brightness-150`, `brightness-200`
|
|
2065
|
+
- **Contrast**: `contrast-0`, `contrast-50`, `contrast-100`, `contrast-150`, `contrast-200`
|
|
2066
|
+
|
|
2067
|
+
## Gradients
|
|
2068
|
+
|
|
2069
|
+
### Linear Gradients
|
|
2070
|
+
|
|
2071
|
+
```tsx
|
|
2072
|
+
// Gradient direction
|
|
2073
|
+
tw('bg-gradient-to-r') // left to right
|
|
2074
|
+
tw('bg-gradient-to-br') // top-left to bottom-right
|
|
2075
|
+
tw('bg-gradient-to-t') // bottom to top
|
|
2076
|
+
|
|
2077
|
+
// Gradient colors
|
|
2078
|
+
tw('from-blue-500') // Start color
|
|
2079
|
+
tw('via-purple-500') // Middle color
|
|
2080
|
+
tw('to-pink-500') // End color
|
|
2081
|
+
|
|
2082
|
+
// Complete gradient
|
|
2083
|
+
tw('bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500')
|
|
2084
|
+
```
|
|
2085
|
+
|
|
2086
|
+
### Gradient Examples
|
|
2087
|
+
|
|
2088
|
+
```tsx
|
|
2089
|
+
export default function GradientCard({ title, tw }) {
|
|
2090
|
+
return (
|
|
2091
|
+
<div style={tw('w-full h-full bg-gradient-to-br from-cyan-500 to-blue-600 p-12')}>
|
|
2092
|
+
<h1 style={tw('text-white text-6xl font-bold')}>
|
|
2093
|
+
{title}
|
|
2094
|
+
</h1>
|
|
2095
|
+
</div>
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
```
|
|
2099
|
+
|
|
2100
|
+
## Custom Theme Colors
|
|
2101
|
+
|
|
2102
|
+
Override default colors in your `dsgn.json`:
|
|
2103
|
+
|
|
2104
|
+
```json
|
|
2105
|
+
{
|
|
2106
|
+
"theme": {
|
|
2107
|
+
"colors": {
|
|
2108
|
+
"primary": "#3b82f6",
|
|
2109
|
+
"primary-foreground": "#ffffff",
|
|
2110
|
+
"accent": "#10b981",
|
|
2111
|
+
"brand": "#ff6b6b"
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
```
|
|
2116
|
+
|
|
2117
|
+
Then use in templates:
|
|
2118
|
+
|
|
2119
|
+
```tsx
|
|
2120
|
+
tw('text-brand') // Uses your custom brand color
|
|
2121
|
+
tw('bg-primary') // Uses your custom primary color
|
|
2122
|
+
```
|
|
2123
|
+
|
|
2124
|
+
## Auto-Detection from tailwind.config.js
|
|
2125
|
+
|
|
2126
|
+
dsgn automatically detects and loads your project's Tailwind configuration:
|
|
2127
|
+
|
|
2128
|
+
```
|
|
2129
|
+
your-project/
|
|
2130
|
+
├── tailwind.config.js ← Automatically detected
|
|
2131
|
+
├── dsgn.json
|
|
2132
|
+
└── _dsgn/templates/
|
|
2133
|
+
```
|
|
2134
|
+
|
|
2135
|
+
This includes:
|
|
2136
|
+
- Custom colors
|
|
2137
|
+
- Custom spacing values
|
|
2138
|
+
- Custom fonts
|
|
2139
|
+
- Theme extensions
|
|
2140
|
+
- Custom utilities
|
|
2141
|
+
|
|
2142
|
+
## Complete Example
|
|
2143
|
+
|
|
2144
|
+
```tsx
|
|
2145
|
+
export default function ModernCard({
|
|
2146
|
+
tw,
|
|
2147
|
+
image,
|
|
2148
|
+
title,
|
|
2149
|
+
description,
|
|
2150
|
+
category,
|
|
2151
|
+
author,
|
|
2152
|
+
avatar
|
|
2153
|
+
}) {
|
|
2154
|
+
return (
|
|
2155
|
+
<div style={tw('w-full h-full bg-card')}>
|
|
2156
|
+
{/* Hero image */}
|
|
2157
|
+
<div style={tw('relative h-2/3')}>
|
|
2158
|
+
<img
|
|
2159
|
+
src={image(hero)}
|
|
2160
|
+
style={tw('w-full h-full object-cover')}
|
|
2161
|
+
/>
|
|
2162
|
+
{/* Category badge */}
|
|
2163
|
+
<div style={tw('absolute top-4 left-4 bg-primary/90 backdrop-blur px-4 py-2 rounded-full')}>
|
|
2164
|
+
<span style={tw('text-sm font-semibold text-primary-foreground')}>
|
|
2165
|
+
{category}
|
|
2166
|
+
</span>
|
|
2167
|
+
</div>
|
|
2168
|
+
</div>
|
|
2169
|
+
|
|
2170
|
+
{/* Content */}
|
|
2171
|
+
<div style={tw('h-1/3 p-8 flex flex-col justify-between')}>
|
|
2172
|
+
<div>
|
|
2173
|
+
<h2 style={tw('text-3xl font-bold text-foreground mb-2')}>
|
|
2174
|
+
{title}
|
|
2175
|
+
</h2>
|
|
2176
|
+
<p style={tw('text-muted-foreground line-clamp-2')}>
|
|
2177
|
+
{description}
|
|
2178
|
+
</p>
|
|
2179
|
+
</div>
|
|
2180
|
+
|
|
2181
|
+
{/* Author */}
|
|
2182
|
+
<div style={tw('flex items-center gap-3')}>
|
|
2183
|
+
<img
|
|
2184
|
+
src={image(avatar)}
|
|
2185
|
+
style={tw('w-10 h-10 rounded-full border-2 border-border')}
|
|
2186
|
+
/>
|
|
2187
|
+
<span style={tw('text-sm text-muted-foreground')}>
|
|
2188
|
+
{author}
|
|
2189
|
+
</span>
|
|
2190
|
+
</div>
|
|
2191
|
+
</div>
|
|
2192
|
+
</div>
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
```
|
|
2196
|
+
|
|
2197
|
+
## Why This Approach?
|
|
2198
|
+
|
|
2199
|
+
- **Semantic naming**: `text-primary` instead of `text-blue-600`
|
|
2200
|
+
- **Consistency**: All templates use the same design language
|
|
2201
|
+
- **Flexibility**: Easy to customize entire theme
|
|
2202
|
+
- **Accessibility**: Pre-tested color contrasts
|
|
2203
|
+
- **Modern**: Same system as shadcn/ui components
|
|
2204
|
+
- **Familiar**: Standard Tailwind syntax
|
|
2205
|
+
|
|
2206
|
+
## Next Steps
|
|
2207
|
+
|
|
2208
|
+
- [Custom Fonts](/fonts)
|
|
2209
|
+
- [Built-in Helpers](/helpers)
|
|
2210
|
+
- [Image Rendering](/images)
|
|
2211
|
+
- [Video Rendering](/video)
|
|
2212
|
+
|
|
2213
|
+
|
|
2214
|
+
|
|
2215
|
+
|
|
2216
|
+
|
|
2217
|
+
================================================================================
|
|
2218
|
+
FILE: fonts.mdx
|
|
2219
|
+
================================================================================
|
|
2220
|
+
|
|
2221
|
+
# Font Handling in dsgn
|
|
2222
|
+
|
|
2223
|
+
The recommended way to use fonts is through `dsgn.json` - configure fonts once, use everywhere.
|
|
2224
|
+
|
|
2225
|
+
## Using Fonts from dsgn.json (Recommended)
|
|
2226
|
+
|
|
2227
|
+
Configure fonts in your `dsgn.json` and use Tailwind classes in templates.
|
|
2228
|
+
|
|
2229
|
+
### Simple Setup (CSS Only)
|
|
2230
|
+
|
|
2231
|
+
Define font families without loading custom fonts (uses system fonts):
|
|
2232
|
+
|
|
2233
|
+
```json
|
|
2234
|
+
{
|
|
2235
|
+
"fonts": {
|
|
2236
|
+
"sans": ["Inter", "system-ui", "-apple-system", "sans-serif"],
|
|
2237
|
+
"serif": ["Georgia", "serif"],
|
|
2238
|
+
"mono": ["Courier New", "monospace"]
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
```
|
|
2242
|
+
|
|
2243
|
+
**Template usage:**
|
|
2244
|
+
```tsx
|
|
2245
|
+
export default function({ title, tw }) {
|
|
2246
|
+
return (
|
|
2247
|
+
<div style={tw('w-full h-full')}>
|
|
2248
|
+
{/* Uses fonts.sans from dsgn.json */}
|
|
2249
|
+
<h1 style={tw('font-sans text-6xl font-bold')}>
|
|
2250
|
+
{title}
|
|
2251
|
+
</h1>
|
|
2252
|
+
|
|
2253
|
+
{/* Uses fonts.mono from dsgn.json */}
|
|
2254
|
+
<code style={tw('font-mono text-sm')}>
|
|
2255
|
+
{code}
|
|
2256
|
+
</code>
|
|
2257
|
+
</div>
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
```
|
|
2261
|
+
|
|
2262
|
+
**Result:** Uses system fonts, falls back to Noto Sans for rendering.
|
|
2263
|
+
|
|
2264
|
+
### Complete Setup (With Font Files)
|
|
2265
|
+
|
|
2266
|
+
Load custom font files for brand-specific typography:
|
|
2267
|
+
|
|
2268
|
+
```json
|
|
2269
|
+
{
|
|
2270
|
+
"fonts": {
|
|
2271
|
+
"sans": {
|
|
2272
|
+
"family": ["Inter", "system-ui", "sans-serif"],
|
|
2273
|
+
"files": [
|
|
2274
|
+
{ "path": "./fonts/Inter-Regular.woff", "weight": 400 },
|
|
2275
|
+
{ "path": "./fonts/Inter-Bold.woff", "weight": 700 }
|
|
2276
|
+
]
|
|
2277
|
+
},
|
|
2278
|
+
"mono": {
|
|
2279
|
+
"family": ["JetBrains Mono", "monospace"],
|
|
2280
|
+
"files": [
|
|
2281
|
+
{ "path": "./fonts/JetBrainsMono-Regular.woff", "weight": 400 }
|
|
2282
|
+
]
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
```
|
|
2287
|
+
|
|
2288
|
+
**Project structure:**
|
|
2289
|
+
```
|
|
2290
|
+
your-project/
|
|
2291
|
+
├── dsgn.json
|
|
2292
|
+
├── fonts/
|
|
2293
|
+
│ ├── Inter-Regular.woff
|
|
2294
|
+
│ ├── Inter-Bold.woff
|
|
2295
|
+
│ └── JetBrainsMono-Regular.woff
|
|
2296
|
+
└── _dsgn/
|
|
2297
|
+
└── templates/
|
|
2298
|
+
```
|
|
2299
|
+
|
|
2300
|
+
**Template usage (same as before):**
|
|
2301
|
+
```tsx
|
|
2302
|
+
<h1 style={tw('font-sans font-bold')}>
|
|
2303
|
+
{/* Uses Inter Bold from dsgn.json */}
|
|
2304
|
+
{title}
|
|
2305
|
+
</h1>
|
|
2306
|
+
```
|
|
2307
|
+
|
|
2308
|
+
**Available classes:**
|
|
2309
|
+
- `font-sans` - Uses `fonts.sans` from dsgn.json
|
|
2310
|
+
- `font-serif` - Uses `fonts.serif` from dsgn.json
|
|
2311
|
+
- `font-mono` - Uses `fonts.mono` from dsgn.json
|
|
2312
|
+
|
|
2313
|
+
**Supported formats:**
|
|
2314
|
+
- ✅ **WOFF** (`.woff`) - Recommended
|
|
2315
|
+
- ✅ **WOFF2** (`.woff2`) - Best compression
|
|
2316
|
+
- ✅ **TTF** (`.ttf`) - Also supported
|
|
2317
|
+
- ✅ **OTF** (`.otf`) - Also supported
|
|
2318
|
+
|
|
2319
|
+
## Template-Specific Fonts (Advanced)
|
|
2320
|
+
|
|
2321
|
+
For templates that need unique fonts not shared across the project:
|
|
2322
|
+
|
|
2323
|
+
**Template structure:**
|
|
2324
|
+
```
|
|
2325
|
+
_dsgn/templates/my-template/
|
|
2326
|
+
├── my-template.tsx
|
|
2327
|
+
├── meta.json
|
|
2328
|
+
└── fonts/
|
|
2329
|
+
└── SpecialFont.woff
|
|
2330
|
+
```
|
|
2331
|
+
|
|
2332
|
+
**meta.json:**
|
|
2333
|
+
```json
|
|
2334
|
+
{
|
|
2335
|
+
"name": "my-template",
|
|
2336
|
+
"type": "image",
|
|
2337
|
+
"size": { "width": 1200, "height": 630 },
|
|
2338
|
+
"fonts": [
|
|
2339
|
+
{
|
|
2340
|
+
"name": "Special Font",
|
|
2341
|
+
"path": "fonts/SpecialFont.woff",
|
|
2342
|
+
"weight": 400,
|
|
2343
|
+
"style": "normal"
|
|
2344
|
+
}
|
|
2345
|
+
]
|
|
2346
|
+
}
|
|
2347
|
+
```
|
|
2348
|
+
|
|
2349
|
+
**Template usage:**
|
|
2350
|
+
```tsx
|
|
2351
|
+
<h1 style={{ fontFamily: 'Special Font', fontWeight: 400 }}>
|
|
2352
|
+
{title}
|
|
2353
|
+
</h1>
|
|
2354
|
+
```
|
|
2355
|
+
|
|
2356
|
+
## Font Loading Priority
|
|
2357
|
+
|
|
2358
|
+
dsgn loads fonts in this order:
|
|
2359
|
+
|
|
2360
|
+
1. **dsgn.json fonts** (if configured with `files`)
|
|
2361
|
+
2. **Template meta.json fonts** (if specified)
|
|
2362
|
+
3. **Default Noto Sans** (from CDN)
|
|
2363
|
+
|
|
2364
|
+
This means dsgn.json fonts override template fonts, ensuring consistency.
|
|
2365
|
+
|
|
2366
|
+
## Default Fonts
|
|
2367
|
+
|
|
2368
|
+
If no fonts are configured, dsgn automatically fetches **Noto Sans** from jsDelivr CDN.
|
|
2369
|
+
|
|
2370
|
+
## Best Practices
|
|
2371
|
+
|
|
2372
|
+
1. ✅ **Use dsgn.json for project-wide fonts** - Configure once, use everywhere
|
|
2373
|
+
2. ✅ **Use font classes** - `tw('font-sans')` instead of `fontFamily: 'Inter'`
|
|
2374
|
+
3. ✅ **Include fallbacks** - Always add system fonts: `["Inter", "system-ui", "sans-serif"]`
|
|
2375
|
+
4. ✅ **Match names** - First font in `family` array is used as the loaded font name
|
|
2376
|
+
5. ✅ **Relative paths** - Font paths are relative to `dsgn.json` location
|
|
2377
|
+
6. ⚠️ **Template fonts for special cases** - Only use meta.json fonts for template-specific typography
|
|
2378
|
+
|
|
2379
|
+
## Examples
|
|
2380
|
+
|
|
2381
|
+
### Minimal Setup (System Fonts)
|
|
2382
|
+
```json
|
|
2383
|
+
{
|
|
2384
|
+
"fonts": {
|
|
2385
|
+
"sans": ["Inter", "-apple-system", "sans-serif"]
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
```
|
|
2389
|
+
Uses system Inter if available, falls back to Noto Sans for rendering.
|
|
2390
|
+
|
|
2391
|
+
### Brand Fonts Setup
|
|
2392
|
+
```json
|
|
2393
|
+
{
|
|
2394
|
+
"fonts": {
|
|
2395
|
+
"sans": {
|
|
2396
|
+
"family": ["Montserrat", "sans-serif"],
|
|
2397
|
+
"files": [
|
|
2398
|
+
{ "path": "./fonts/Montserrat-Regular.woff", "weight": 400 },
|
|
2399
|
+
{ "path": "./fonts/Montserrat-Bold.woff", "weight": 700 }
|
|
2400
|
+
]
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
```
|
|
2405
|
+
Loads and uses Montserrat for all templates.
|
|
2406
|
+
|
|
2407
|
+
### Multi-Font Setup
|
|
2408
|
+
```json
|
|
2409
|
+
{
|
|
2410
|
+
"fonts": {
|
|
2411
|
+
"sans": {
|
|
2412
|
+
"family": ["Inter", "sans-serif"],
|
|
2413
|
+
"files": [
|
|
2414
|
+
{ "path": "./fonts/Inter-Regular.woff", "weight": 400 },
|
|
2415
|
+
{ "path": "./fonts/Inter-Bold.woff", "weight": 700 }
|
|
2416
|
+
]
|
|
2417
|
+
},
|
|
2418
|
+
"serif": {
|
|
2419
|
+
"family": ["Playfair Display", "serif"],
|
|
2420
|
+
"files": [
|
|
2421
|
+
{ "path": "./fonts/Playfair-Regular.woff", "weight": 400 }
|
|
2422
|
+
]
|
|
2423
|
+
},
|
|
2424
|
+
"mono": {
|
|
2425
|
+
"family": ["Fira Code", "monospace"],
|
|
2426
|
+
"files": [
|
|
2427
|
+
{ "path": "./fonts/FiraCode-Regular.woff", "weight": 400 }
|
|
2428
|
+
]
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
```
|
|
2433
|
+
Loads different fonts for each style class.
|
|
2434
|
+
|
|
2435
|
+
## Performance
|
|
2436
|
+
|
|
2437
|
+
- ✅ **Font caching** - Fonts load once and are cached for all renders
|
|
2438
|
+
- ✅ **Video optimization** - 90-frame video loads fonts once, not 90 times
|
|
2439
|
+
- ✅ **No CDN delays** - Local fonts load instantly
|
|
2440
|
+
|
|
2441
|
+
## Next Steps
|
|
2442
|
+
|
|
2443
|
+
- [Styling with Tailwind & shadcn/ui](/styling)
|
|
2444
|
+
- [Built-in Helpers](/helpers)
|
|
2445
|
+
- [Image Rendering](/images)
|
|
2446
|
+
- [Video Rendering](/video)
|
|
2447
|
+
|
|
2448
|
+
|