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.
Files changed (245) hide show
  1. package/FONTS.md +156 -0
  2. package/HELPERS_DEMO.md +134 -0
  3. package/PROJECT_STRUCTURE.md +286 -0
  4. package/PUBLISHING.md +171 -0
  5. package/README.md +1020 -0
  6. package/REGISTRY_SETUP.md +427 -0
  7. package/SHADCN_INTEGRATION.md +269 -0
  8. package/TAILWIND.md +228 -0
  9. package/TEMPLATE_SOURCES.md +363 -0
  10. package/_dsgn/templates/banner-hero/banner-hero.tsx +57 -0
  11. package/_dsgn/templates/banner-hero/meta.json +14 -0
  12. package/_dsgn/templates/composite-card/meta.json +16 -0
  13. package/_dsgn/templates/composite-card/template.tsx +44 -0
  14. package/_dsgn/templates/image/meta.json +13 -0
  15. package/_dsgn/templates/image/template.tsx +28 -0
  16. package/_dsgn/templates/kitchen-sink/meta.json +13 -0
  17. package/_dsgn/templates/kitchen-sink/template.tsx +72 -0
  18. package/_dsgn/templates/qr-card/meta.json +14 -0
  19. package/_dsgn/templates/qr-card/template.tsx +39 -0
  20. package/_dsgn/templates/test-parent/child/meta.json +11 -0
  21. package/_dsgn/templates/test-parent/child/template.tsx +27 -0
  22. package/_dsgn/templates/test-parent/meta.json +12 -0
  23. package/_dsgn/templates/test-parent/template.tsx +30 -0
  24. package/_dsgn/templates/test-sibling/meta.json +11 -0
  25. package/_dsgn/templates/test-sibling/template.tsx +20 -0
  26. package/_dsgn/templates/video/.tmp/template-1763421345296.mjs +43 -0
  27. package/_dsgn/templates/video/.tmp/template-1763421362228.mjs +43 -0
  28. package/_dsgn/templates/video/.tmp/template-1763421377706.mjs +43 -0
  29. package/_dsgn/templates/video/meta.json +17 -0
  30. package/_dsgn/templates/video/template.tsx +48 -0
  31. package/dist/cli.d.ts +3 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +70 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/commands/add.d.ts +6 -0
  36. package/dist/commands/add.d.ts.map +1 -0
  37. package/dist/commands/add.js +86 -0
  38. package/dist/commands/add.js.map +1 -0
  39. package/dist/commands/default.d.ts +2 -0
  40. package/dist/commands/default.d.ts.map +1 -0
  41. package/dist/commands/default.js +69 -0
  42. package/dist/commands/default.js.map +1 -0
  43. package/dist/commands/init.d.ts +2 -0
  44. package/dist/commands/init.d.ts.map +1 -0
  45. package/dist/commands/init.js +75 -0
  46. package/dist/commands/init.js.map +1 -0
  47. package/dist/commands/list.d.ts +2 -0
  48. package/dist/commands/list.d.ts.map +1 -0
  49. package/dist/commands/list.js +83 -0
  50. package/dist/commands/list.js.map +1 -0
  51. package/dist/commands/preview.d.ts +3 -0
  52. package/dist/commands/preview.d.ts.map +1 -0
  53. package/dist/commands/preview.js +296 -0
  54. package/dist/commands/preview.js.map +1 -0
  55. package/dist/commands/render.d.ts +10 -0
  56. package/dist/commands/render.d.ts.map +1 -0
  57. package/dist/commands/render.js +204 -0
  58. package/dist/commands/render.js.map +1 -0
  59. package/dist/commands/validate.d.ts +2 -0
  60. package/dist/commands/validate.d.ts.map +1 -0
  61. package/dist/commands/validate.js +107 -0
  62. package/dist/commands/validate.js.map +1 -0
  63. package/dist/default-templates/AGENTS.md +229 -0
  64. package/dist/default-templates/image/meta.json +13 -0
  65. package/dist/default-templates/image/template.d.ts +20 -0
  66. package/dist/default-templates/image/template.d.ts.map +1 -0
  67. package/dist/default-templates/image/template.js +18 -0
  68. package/dist/default-templates/image/template.js.map +1 -0
  69. package/dist/default-templates/image/template.tsx +20 -0
  70. package/dist/default-templates/image-template/meta.json +13 -0
  71. package/dist/default-templates/image-template/template.tsx +19 -0
  72. package/dist/default-templates/kitchen-sink/meta.json +13 -0
  73. package/dist/default-templates/kitchen-sink/template.tsx +64 -0
  74. package/dist/default-templates/page/meta.json +17 -0
  75. package/dist/default-templates/page/template.tsx +37 -0
  76. package/dist/default-templates/video/meta.json +17 -0
  77. package/dist/default-templates/video/template.d.ts +26 -0
  78. package/dist/default-templates/video/template.d.ts.map +1 -0
  79. package/dist/default-templates/video/template.js +33 -0
  80. package/dist/default-templates/video/template.js.map +1 -0
  81. package/dist/default-templates/video/template.tsx +37 -0
  82. package/dist/default-templates/video-template/meta.json +17 -0
  83. package/dist/default-templates/video-template/template.tsx +36 -0
  84. package/dist/default-templates/website/meta.json +16 -0
  85. package/dist/default-templates/website/pages/home.tsx +17 -0
  86. package/dist/default-templates/website/parts/footer.tsx +17 -0
  87. package/dist/default-templates/website/parts/header.tsx +17 -0
  88. package/dist/default-templates/website/template.tsx +17 -0
  89. package/dist/default-templates/website-template/meta.json +16 -0
  90. package/dist/default-templates/website-template/pages/home.tsx +16 -0
  91. package/dist/default-templates/website-template/parts/footer.tsx +16 -0
  92. package/dist/default-templates/website-template/parts/header.tsx +16 -0
  93. package/dist/default-templates/website-template/template.tsx +16 -0
  94. package/dist/lib/config.d.ts +34 -0
  95. package/dist/lib/config.d.ts.map +1 -0
  96. package/dist/lib/config.js +248 -0
  97. package/dist/lib/config.js.map +1 -0
  98. package/dist/lib/constants.d.ts +7 -0
  99. package/dist/lib/constants.d.ts.map +1 -0
  100. package/dist/lib/constants.js +12 -0
  101. package/dist/lib/constants.js.map +1 -0
  102. package/dist/lib/helpers.d.ts +29 -0
  103. package/dist/lib/helpers.d.ts.map +1 -0
  104. package/dist/lib/helpers.js +159 -0
  105. package/dist/lib/helpers.js.map +1 -0
  106. package/dist/lib/installer.d.ts +51 -0
  107. package/dist/lib/installer.d.ts.map +1 -0
  108. package/dist/lib/installer.js +215 -0
  109. package/dist/lib/installer.js.map +1 -0
  110. package/dist/lib/renderer.d.ts +51 -0
  111. package/dist/lib/renderer.d.ts.map +1 -0
  112. package/dist/lib/renderer.js +524 -0
  113. package/dist/lib/renderer.js.map +1 -0
  114. package/dist/lib/tailwind-config-loader.d.ts +47 -0
  115. package/dist/lib/tailwind-config-loader.d.ts.map +1 -0
  116. package/dist/lib/tailwind-config-loader.js +432 -0
  117. package/dist/lib/tailwind-config-loader.js.map +1 -0
  118. package/dist/lib/tailwind-detector.d.ts +36 -0
  119. package/dist/lib/tailwind-detector.d.ts.map +1 -0
  120. package/dist/lib/tailwind-detector.js +156 -0
  121. package/dist/lib/tailwind-detector.js.map +1 -0
  122. package/dist/lib/tailwind.d.ts +8 -0
  123. package/dist/lib/tailwind.d.ts.map +1 -0
  124. package/dist/lib/tailwind.js +994 -0
  125. package/dist/lib/tailwind.js.map +1 -0
  126. package/dist/lib/template-validator.d.ts +22 -0
  127. package/dist/lib/template-validator.d.ts.map +1 -0
  128. package/dist/lib/template-validator.js +174 -0
  129. package/dist/lib/template-validator.js.map +1 -0
  130. package/dist/lib/utils.d.ts +44 -0
  131. package/dist/lib/utils.d.ts.map +1 -0
  132. package/dist/lib/utils.js +207 -0
  133. package/dist/lib/utils.js.map +1 -0
  134. package/dist/lib/version-check.d.ts +16 -0
  135. package/dist/lib/version-check.d.ts.map +1 -0
  136. package/dist/lib/version-check.js +88 -0
  137. package/dist/lib/version-check.js.map +1 -0
  138. package/dist/lib/video-renderer.d.ts +32 -0
  139. package/dist/lib/video-renderer.d.ts.map +1 -0
  140. package/dist/lib/video-renderer.js +226 -0
  141. package/dist/lib/video-renderer.js.map +1 -0
  142. package/dist/sdk/index.d.ts +58 -0
  143. package/dist/sdk/index.d.ts.map +1 -0
  144. package/dist/sdk/index.js +119 -0
  145. package/dist/sdk/index.js.map +1 -0
  146. package/dist/sdk/template.d.ts +40 -0
  147. package/dist/sdk/template.d.ts.map +1 -0
  148. package/dist/sdk/template.js +60 -0
  149. package/dist/sdk/template.js.map +1 -0
  150. package/dist/types/config.d.ts +62 -0
  151. package/dist/types/config.d.ts.map +1 -0
  152. package/dist/types/config.js +47 -0
  153. package/dist/types/config.js.map +1 -0
  154. package/dist/types/template.d.ts +79 -0
  155. package/dist/types/template.d.ts.map +1 -0
  156. package/dist/types/template.js +2 -0
  157. package/dist/types/template.js.map +1 -0
  158. package/examples/nextjs-api/README.md +180 -0
  159. package/examples/nextjs-api/package.json +21 -0
  160. package/examples/nextjs-api/pages/api/intro-video.ts +53 -0
  161. package/examples/nextjs-api/pages/api/og-image.ts +50 -0
  162. package/netlify.toml +13 -0
  163. package/package.json +84 -0
  164. package/patches/satori+0.18.3.patch +13 -0
  165. package/test-templates/TESTS.md +63 -0
  166. package/test-templates/_dsgn/templates/absolute-spin/meta.json +7 -0
  167. package/test-templates/_dsgn/templates/absolute-spin/template.tsx +16 -0
  168. package/test-templates/_dsgn/templates/animated-intro/.tmp/template-1763468771640.mjs +7 -0
  169. package/test-templates/_dsgn/templates/animated-intro/meta.json +10 -0
  170. package/test-templates/_dsgn/templates/animated-intro/template.tsx +23 -0
  171. package/test-templates/_dsgn/templates/centered-spin/.tmp/template-1763468525386.mjs +7 -0
  172. package/test-templates/_dsgn/templates/centered-spin/meta.json +7 -0
  173. package/test-templates/_dsgn/templates/centered-spin/template.tsx +11 -0
  174. package/test-templates/_dsgn/templates/composite/.tmp/template-1763468815645.mjs +7 -0
  175. package/test-templates/_dsgn/templates/composite/meta.json +9 -0
  176. package/test-templates/_dsgn/templates/composite/template.tsx +23 -0
  177. package/test-templates/_dsgn/templates/easing-test/.tmp/template-1763468824501.mjs +7 -0
  178. package/test-templates/_dsgn/templates/easing-test/meta.json +7 -0
  179. package/test-templates/_dsgn/templates/easing-test/template.tsx +47 -0
  180. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466364336.mjs +10 -0
  181. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466584319.mjs +10 -0
  182. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466667797.mjs +10 -0
  183. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466746504.mjs +10 -0
  184. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466930225.mjs +10 -0
  185. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467004552.mjs +10 -0
  186. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467060334.mjs +10 -0
  187. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467124493.mjs +10 -0
  188. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467174690.mjs +10 -0
  189. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467359134.mjs +10 -0
  190. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467451928.mjs +10 -0
  191. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467758275.mjs +10 -0
  192. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467985201.mjs +10 -0
  193. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468020563.mjs +10 -0
  194. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468090428.mjs +10 -0
  195. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468211036.mjs +10 -0
  196. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468394057.mjs +10 -0
  197. package/test-templates/_dsgn/templates/minimal-spin/meta.json +7 -0
  198. package/test-templates/_dsgn/templates/minimal-spin/template.tsx +13 -0
  199. package/test-templates/_dsgn/templates/no-origin-spin/meta.json +7 -0
  200. package/test-templates/_dsgn/templates/no-origin-spin/template.tsx +10 -0
  201. package/test-templates/_dsgn/templates/opacity-test/meta.json +7 -0
  202. package/test-templates/_dsgn/templates/opacity-test/template.tsx +9 -0
  203. package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468758954.mjs +17 -0
  204. package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468815672.mjs +17 -0
  205. package/test-templates/_dsgn/templates/qr-code/meta.json +9 -0
  206. package/test-templates/_dsgn/templates/qr-code/template.tsx +20 -0
  207. package/test-templates/_dsgn/templates/rotation-abs-test/meta.json +7 -0
  208. package/test-templates/_dsgn/templates/rotation-abs-test/template.tsx +15 -0
  209. package/test-templates/_dsgn/templates/rotation-corner/meta.json +7 -0
  210. package/test-templates/_dsgn/templates/rotation-corner/template.tsx +12 -0
  211. package/test-templates/_dsgn/templates/rotation-test/meta.json +7 -0
  212. package/test-templates/_dsgn/templates/rotation-test/template.tsx +12 -0
  213. package/test-templates/_dsgn/templates/shake-test/meta.json +7 -0
  214. package/test-templates/_dsgn/templates/shake-test/template.tsx +12 -0
  215. package/test-templates/_dsgn/templates/static-image/.tmp/template-1763468746271.mjs +7 -0
  216. package/test-templates/_dsgn/templates/static-image/meta.json +9 -0
  217. package/test-templates/_dsgn/templates/static-image/template.tsx +19 -0
  218. package/test-templates/_dsgn/templates/translate-test/meta.json +7 -0
  219. package/test-templates/_dsgn/templates/translate-test/template.tsx +9 -0
  220. package/test-templates/_dsgn/templates/video-loops/.tmp/template-1763468793192.mjs +15 -0
  221. package/test-templates/_dsgn/templates/video-loops/meta.json +9 -0
  222. package/test-templates/_dsgn/templates/video-loops/template.tsx +39 -0
  223. package/test-templates/_dsgn/templates/wrapped-spin/meta.json +7 -0
  224. package/test-templates/_dsgn/templates/wrapped-spin/template.tsx +17 -0
  225. package/test-templates/compare-svgs.mjs +30 -0
  226. package/test-templates/convert-frames.mjs +15 -0
  227. package/test-templates/debug-rotation.mjs +25 -0
  228. package/test-templates/run-tests.sh +39 -0
  229. package/test-templates/test-sdk.mjs +115 -0
  230. package/website/.astro/settings.json +5 -0
  231. package/website/.astro/types.d.ts +1 -0
  232. package/website/README.md +112 -0
  233. package/website/astro.config.mjs +18 -0
  234. package/website/dist/_astro/fonts.DHdiHGBO.css +1 -0
  235. package/website/dist/fonts/index.html +193 -0
  236. package/website/dist/helpers/index.html +166 -0
  237. package/website/dist/images/index.html +314 -0
  238. package/website/dist/index.html +219 -0
  239. package/website/dist/llm.txt +2448 -0
  240. package/website/dist/styling/index.html +365 -0
  241. package/website/dist/templates/index.html +124 -0
  242. package/website/dist/video/index.html +636 -0
  243. package/website/package-lock.json +7606 -0
  244. package/website/package.json +23 -0
  245. 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
+