loopwind 0.22.0 → 0.23.0

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 (120) hide show
  1. package/dist/lib/render-core.d.ts +63 -0
  2. package/dist/lib/render-core.d.ts.map +1 -0
  3. package/dist/lib/render-core.js +65 -0
  4. package/dist/lib/render-core.js.map +1 -0
  5. package/dist/lib/renderer.d.ts.map +1 -1
  6. package/dist/lib/renderer.js +10 -7
  7. package/dist/lib/renderer.js.map +1 -1
  8. package/dist/lib/resvg-init.d.ts +15 -0
  9. package/dist/lib/resvg-init.d.ts.map +1 -0
  10. package/dist/lib/resvg-init.js +55 -0
  11. package/dist/lib/resvg-init.js.map +1 -0
  12. package/dist/lib/tailwind/colors.d.ts +8 -0
  13. package/dist/lib/tailwind/colors.d.ts.map +1 -0
  14. package/dist/lib/tailwind/colors.js +102 -0
  15. package/dist/lib/tailwind/colors.js.map +1 -0
  16. package/dist/lib/tailwind/index.d.ts +10 -0
  17. package/dist/lib/tailwind/index.d.ts.map +1 -0
  18. package/dist/lib/tailwind/index.js +9 -0
  19. package/dist/lib/tailwind/index.js.map +1 -0
  20. package/dist/lib/tailwind/resolvers.d.ts +28 -0
  21. package/dist/lib/tailwind/resolvers.d.ts.map +1 -0
  22. package/dist/lib/tailwind/resolvers.js +94 -0
  23. package/dist/lib/tailwind/resolvers.js.map +1 -0
  24. package/dist/lib/tailwind/types.d.ts +29 -0
  25. package/dist/lib/tailwind/types.d.ts.map +1 -0
  26. package/dist/lib/tailwind/types.js +8 -0
  27. package/dist/lib/tailwind/types.js.map +1 -0
  28. package/dist/lib/tailwind-config-loader.d.ts +8 -45
  29. package/dist/lib/tailwind-config-loader.d.ts.map +1 -1
  30. package/dist/lib/tailwind-config-loader.js +6 -429
  31. package/dist/lib/tailwind-config-loader.js.map +1 -1
  32. package/dist/lib/tailwind.d.ts +1 -1
  33. package/dist/lib/tailwind.d.ts.map +1 -1
  34. package/dist/lib/tailwind.js +1 -1
  35. package/dist/lib/tailwind.js.map +1 -1
  36. package/dist/lib/video-renderer.d.ts.map +1 -1
  37. package/dist/lib/video-renderer.js +6 -5
  38. package/dist/lib/video-renderer.js.map +1 -1
  39. package/dist/sdk/edge/index.d.ts +91 -0
  40. package/dist/sdk/edge/index.d.ts.map +1 -0
  41. package/dist/sdk/edge/index.js +187 -0
  42. package/dist/sdk/edge/index.js.map +1 -0
  43. package/dist/sdk/workers/index.d.ts +135 -0
  44. package/dist/sdk/workers/index.d.ts.map +1 -0
  45. package/dist/sdk/workers/index.js +271 -0
  46. package/dist/sdk/workers/index.js.map +1 -0
  47. package/dist/sdk/workers/tailwind-config.d.ts +48 -0
  48. package/dist/sdk/workers/tailwind-config.d.ts.map +1 -0
  49. package/dist/sdk/workers/tailwind-config.js +187 -0
  50. package/dist/sdk/workers/tailwind-config.js.map +1 -0
  51. package/dist/sdk/workers/tailwind.d.ts +9 -0
  52. package/dist/sdk/workers/tailwind.d.ts.map +1 -0
  53. package/dist/sdk/workers/tailwind.js +8 -0
  54. package/dist/sdk/workers/tailwind.js.map +1 -0
  55. package/package.json +6 -2
  56. package/test-cloudflare-worker/README.md +64 -0
  57. package/test-cloudflare-worker/dist/README.md +1 -0
  58. package/test-cloudflare-worker/dist/index.js +23743 -0
  59. package/test-cloudflare-worker/dist/index.js.map +8 -0
  60. package/test-cloudflare-worker/package-lock.json +1773 -0
  61. package/test-cloudflare-worker/package.json +25 -0
  62. package/test-cloudflare-worker/test-sdk.mjs +75 -0
  63. package/test-cloudflare-worker/wrangler.toml +14 -0
  64. package/test-video-720p.mjs +96 -0
  65. package/test-video-breakdown.mjs +98 -0
  66. package/test-video-perf-1080.mjs +67 -0
  67. package/test-video-perf.mjs +56 -0
  68. package/test-worker-1080p.mjs +103 -0
  69. package/test-worker-viability.mjs +140 -0
  70. package/website/astro.config.mjs +4 -9
  71. package/website/dist/_astro/PlaygroundEditor.DzFavsm8.js +26 -0
  72. package/website/dist/_astro/VideoPreviewClient.BrajhYmh.js +1 -0
  73. package/website/dist/_astro/agents.CZXv4DCM.css +1 -0
  74. package/website/dist/_astro/client.BHSq4mdQ.js +33 -0
  75. package/website/dist/_astro/index.CTbGshLK.js +9 -0
  76. package/website/dist/_astro/jsx-runtime.BjG_zV1W.js +9 -0
  77. package/website/dist/_routes.json +1 -0
  78. package/website/dist/_worker.js/_@astrojs-ssr-adapter.mjs +4 -4
  79. package/website/dist/_worker.js/_astro-internal_middleware.mjs +2 -2
  80. package/website/dist/_worker.js/chunks/Logo_Cud5QvBJ.mjs +22 -0
  81. package/website/dist/_worker.js/chunks/_@astro-renderers_-YVK7NHa.mjs +15015 -0
  82. package/website/dist/_worker.js/chunks/astro/{server_Y5_QHO8v.mjs → server_CsUrSZgd.mjs} +113 -2
  83. package/website/dist/_worker.js/chunks/{astro-designed-error-pages_BNTLO-TA.mjs → astro-designed-error-pages_1ELXm5Tt.mjs} +1 -1
  84. package/website/dist/_worker.js/chunks/{index_C1UTDwYg.mjs → index_BDWR1Q-q.mjs} +2 -2
  85. package/website/dist/_worker.js/chunks/{noop-middleware_DlWGj5t5.mjs → noop-middleware_B8fH5jha.mjs} +1 -1
  86. package/website/dist/_worker.js/index.js +38 -30
  87. package/website/dist/_worker.js/manifest_Bk6136-u.mjs +98 -0
  88. package/website/dist/_worker.js/pages/_image.astro.mjs +1 -1
  89. package/website/dist/_worker.js/pages/api/playground/render.astro.mjs +25562 -0
  90. package/website/dist/_worker.js/pages/api/playground/templates.astro.mjs +92 -0
  91. package/website/dist/_worker.js/pages/api/raw-markdown/_---path_.astro.mjs +1 -1
  92. package/website/dist/_worker.js/pages/playground/_example_.astro.mjs +95 -0
  93. package/website/dist/_worker.js/pages/playground.astro.mjs +1 -0
  94. package/website/dist/_worker.js/renderers.mjs +1 -56
  95. package/website/dist/agents/index.html +4 -2
  96. package/website/dist/animation/index.html +629 -3
  97. package/website/dist/config/index.html +4 -2
  98. package/website/dist/fonts/index.html +4 -2
  99. package/website/dist/getting-started/index.html +4 -2
  100. package/website/dist/helpers/index.html +196 -10
  101. package/website/dist/images/index.html +4 -2
  102. package/website/dist/index.html +4 -3
  103. package/website/dist/llm.txt +870 -20
  104. package/website/dist/playground/index.html +6 -0
  105. package/website/dist/preview/index.html +4 -2
  106. package/website/dist/sdk/index.html +639 -127
  107. package/website/dist/sitemap.xml +12 -12
  108. package/website/dist/styling/index.html +4 -2
  109. package/website/dist/templates/index.html +4 -2
  110. package/website/dist/video/index.html +47 -12
  111. package/website/package-lock.json +11 -1
  112. package/website/package.json +3 -1
  113. package/website/wrangler.toml +9 -0
  114. package/_dsgn/templates/dashed-stroke-test/template.tsx +0 -73
  115. package/_dsgn/templates/path-follow-test/template.tsx +0 -176
  116. package/_dsgn/templates/path-simple-test/template.tsx +0 -98
  117. package/_dsgn/templates/stroke-dash-test/meta.json +0 -12
  118. package/_dsgn/templates/stroke-dash-test/template.tsx +0 -53
  119. package/website/dist/_astro/agents.Yx-L_igG.css +0 -1
  120. package/website/dist/_worker.js/manifest_CT_D-YDe.mjs +0 -98
@@ -1,5 +1,7 @@
1
- <!DOCTYPE html><html lang="en" data-astro-cid-mw7aashj> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v4.16.19"><link rel="canonical" href="https://loopwind.dev/sdk/"><!-- Primary Meta Tags --><title></title><meta name="title"><meta name="description" content="CLI-based Image &#38; Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates."><meta name="keywords" content="loopwind, image generation, video generation, React, Tailwind CSS, Satori, CLI tool, templates, shadcn/ui, AI agents, Cursor, automation, serverless, WASM, Open Graph, social media, marketing automation"><meta name="author" content="loopwind"><!-- Open Graph / Facebook --><meta property="og:type" content="website"><meta property="og:url" content="https://loopwind.dev/sdk/"><meta property="og:title"><meta property="og:description" content="CLI-based Image &#38; Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates."><meta property="og:image" content="https://loopwind.dev/api/og/sdk"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:image:alt"><meta property="og:site_name" content="loopwind"><meta property="og:locale" content="en_US"><!-- Twitter --><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://loopwind.dev/sdk/"><meta name="twitter:title"><meta name="twitter:description" content="CLI-based Image &#38; Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates."><meta name="twitter:image" content="https://loopwind.dev/api/og/sdk"><meta name="twitter:image:alt"><meta name="twitter:creator" content="@loopwind"><meta name="twitter:site" content="@loopwind"><!-- Theme Color --><meta name="theme-color" content="#0a0a0a"><meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0a0a0a"><meta name="theme-color" media="(prefers-color-scheme: light)" content="#fafafa"><!-- Additional SEO --><meta name="robots" content="index, follow"><meta name="googlebot" content="index, follow"><meta name="language" content="English"><meta name="revisit-after" content="7 days"><meta name="rating" content="general"><!-- Mobile Web App --><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="loopwind"><!-- Structured Data (JSON-LD) --><script type="application/ld+json">{"@context":"https://schema.org","@type":"SoftwareApplication","name":"loopwind","applicationCategory":"DeveloperApplication","operatingSystem":"Cross-platform","description":"CLI-based Image & Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates.","url":"https://loopwind.dev/","author":{"@type":"Organization","name":"loopwind","url":"https://loopwind.dev/"},"offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"softwareVersion":"0.11.0","releaseNotes":"https://github.com/loopwind/loopwind/releases","screenshot":"https://loopwind.dev/api/og/sdk","featureList":["Image generation from React templates","Video generation with animations","Tailwind CSS support","shadcn/ui design system","CLI-based workflow","Serverless rendering","TypeScript support","AI Agent friendly"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"loopwind","url":"https://loopwind.dev/","logo":"https://loopwind.dev/favicon.svg","sameAs":["https://github.com/loopwind/loopwind","https://www.npmjs.com/package/loopwind"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://loopwind.dev/"},{"@type":"ListItem","position":2,"name":"Sdk","item":"https://loopwind.dev/sdk"}]}</script><link rel="stylesheet" href="/_astro/agents.Yx-L_igG.css"><script type="module">const p=document.getElementById("mobile-menu-button"),l=document.getElementById("mobile-menu"),g=document.querySelectorAll(".mobile-menu-link");p?.addEventListener("click",()=>{l?.classList.toggle("hidden")});l?.addEventListener("click",o=>{o.target===l&&l.classList.add("hidden")});g.forEach(o=>{o.addEventListener("click",()=>{l?.classList.add("hidden")})});function m(){const o=document.getElementById("toc-nav");if(!o){console.log("TOC: No toc-nav element found");return}const n=document.querySelector("article");if(!n){console.log("TOC: No article element found");return}const c=n.querySelectorAll("h2, h3");if(c.length===0){console.log("TOC: No headings found");return}console.log(`TOC: Found ${c.length} headings`);const a=document.createElement("ul");a.className="space-y-0 text-sm border-l border-border/50",c.forEach(e=>{const t=e.tagName==="H2"?2:3,r=e.id||e.textContent?.toLowerCase().replace(/[^\w]+/g,"-");!e.id&&r&&(e.id=r);const s=document.createElement("li"),d=document.createElement("a");d.href=`#${r}`,d.textContent=e.textContent,d.className=`toc-link block py-1 px-3 -ml-px border-l-2 border-transparent transition-all no-underline ${t===3?"pl-6 text-xs text-muted-foreground/70":"text-sm text-muted-foreground"} hover:text-foreground hover:border-muted-foreground/50`,s.appendChild(d),a.appendChild(s)}),o.appendChild(a);const i=new IntersectionObserver(e=>{e.forEach(t=>{const r=t.target.id,s=o.querySelector(`a[href="#${r}"]`);t.isIntersecting&&(document.querySelectorAll(".toc-link").forEach(d=>{d.classList.remove("text-foreground","border-accent","font-medium")}),s?.classList.add("text-foreground","border-accent","font-medium"))})},{rootMargin:"-100px 0px -66%",threshold:0});c.forEach(e=>{i.observe(e)})}document.addEventListener("DOMContentLoaded",m);document.addEventListener("astro:page-load",m);function u(){const o=document.getElementById("copy-to-llm-btn"),n=document.getElementById("copy-btn-text");if(!o||!n){console.log("Copy to LLM: Button not found on this page");return}console.log("Copy to LLM: Initialized"),o.addEventListener("click",async()=>{const c=window.location.pathname,a=c.replace(/^\//,"").replace(/\/$/,""),e=`/api/raw-markdown/${a===""?"index":a}`;console.log("Copy to LLM clicked: pathname=",c,"apiUrl=",e);try{n.textContent="Copying...";const t=await fetch(e);if(console.log("Response status:",t.status),!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const r=await t.text();console.log("Got markdown, length:",r.length),await navigator.clipboard.writeText(r),n.textContent="Copied!",setTimeout(()=>{n.textContent="Copy to LLM"},2e3)}catch(t){console.error("Copy failed:",t),n.textContent="Failed",setTimeout(()=>{n.textContent="Copy to LLM"},2e3)}})}document.addEventListener("DOMContentLoaded",u);document.addEventListener("astro:page-load",u);
2
- </script></head> <body class="antialiased" data-astro-cid-mw7aashj> <div class="min-h-screen" data-astro-cid-mw7aashj> <!-- Mobile Header --> <header class="mobile-header fixed top-0 left-0 right-0 h-16 bg-card border-b border-border z-50 md:hidden" data-astro-cid-mw7aashj> <div class="flex items-center justify-between h-full px-4" data-astro-cid-mw7aashj> <a href="/" class="no-underline"> <h1 class="text-xl flex items-center gap-2 bg-linear-to-r from-brand-from to-brand-to bg-clip-text text-transparent"> <span class="text-2xl">➰</span> <span>loopwind</span> </h1> </a> <button id="mobile-menu-button" class="p-2 text-foreground hover:bg-accent rounded-md transition-colors" aria-label="Toggle menu" data-astro-cid-mw7aashj> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-mw7aashj> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" data-astro-cid-mw7aashj></path> </svg> </button> </div> </header> <!-- Mobile Menu Overlay --> <div id="mobile-menu" class="mobile-menu fixed inset-0 bg-background/80 backdrop-blur-sm z-40 md:hidden hidden" data-astro-cid-mw7aashj> <div class="fixed top-0 left-0 h-screen w-64 bg-card border-r border-border overflow-y-auto" data-astro-cid-mw7aashj> <div class="p-6 pt-20" data-astro-cid-mw7aashj> <nav data-astro-cid-mw7aashj> <ul class="space-y-1" data-astro-cid-mw7aashj> <li data-astro-cid-mw7aashj> <a href="/" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Home </a> </li><li data-astro-cid-mw7aashj> <a href="/getting-started" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Getting Started </a> </li><li data-astro-cid-mw7aashj> <a href="/templates" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Templates </a> </li><li data-astro-cid-mw7aashj> <a href="/images" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Images </a> </li><li data-astro-cid-mw7aashj> <a href="/video" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Video </a> </li><li data-astro-cid-mw7aashj> <a href="/preview" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Preview </a> </li><li data-astro-cid-mw7aashj> <a href="/animation" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Animation </a> </li><li data-astro-cid-mw7aashj> <a href="/helpers" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Helpers </a> </li><li data-astro-cid-mw7aashj> <a href="/styling" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Styling </a> </li><li data-astro-cid-mw7aashj> <a href="/fonts" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Fonts </a> </li><li data-astro-cid-mw7aashj> <a href="/config" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> loopwind.json </a> </li><li data-astro-cid-mw7aashj> <a href="/sdk" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> SDK </a> </li><li data-astro-cid-mw7aashj> <a href="/agents" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Use with AI Agents </a> </li><li data-astro-cid-mw7aashj> <a href="/llm.txt" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> llm.txt ⇱ </a> </li> </ul> </nav> </div> </div> </div> <!-- Desktop Sidebar --> <aside class="desktop-sidebar fixed top-0 left-0 h-screen w-64 border-r border-border bg-card overflow-y-auto z-10" data-astro-cid-mw7aashj> <div class="p-6" data-astro-cid-mw7aashj> <div class="block mb-3" data-astro-cid-mw7aashj> <a href="/" class="no-underline"> <h1 class="text-3xl flex items-center gap-2 bg-linear-to-r from-brand-from to-brand-to bg-clip-text text-transparent"> <span class="text-4xl">➰</span> <span>loopwind</span> </h1> </a> </div> <nav data-astro-cid-mw7aashj> <ul class="space-y-0" data-astro-cid-mw7aashj> <li data-astro-cid-mw7aashj> <a href="/" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Home </a> </li><li data-astro-cid-mw7aashj> <a href="/getting-started" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Getting Started </a> </li><li data-astro-cid-mw7aashj> <a href="/templates" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Templates </a> </li><li data-astro-cid-mw7aashj> <a href="/images" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Images </a> </li><li data-astro-cid-mw7aashj> <a href="/video" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Video </a> </li><li data-astro-cid-mw7aashj> <a href="/preview" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Preview </a> </li><li data-astro-cid-mw7aashj> <a href="/animation" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Animation </a> </li><li data-astro-cid-mw7aashj> <a href="/helpers" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Helpers </a> </li><li data-astro-cid-mw7aashj> <a href="/styling" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Styling </a> </li><li data-astro-cid-mw7aashj> <a href="/fonts" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Fonts </a> </li><li data-astro-cid-mw7aashj> <a href="/config" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> loopwind.json </a> </li><li data-astro-cid-mw7aashj> <a href="/sdk" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> SDK </a> </li><li data-astro-cid-mw7aashj> <a href="/agents" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Use with AI Agents </a> </li><li data-astro-cid-mw7aashj> <a href="/llm.txt" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> llm.txt ⇱ </a> </li> </ul> </nav> </div> </aside> <!-- Main content --> <main class="ml-64 mr-64" data-astro-cid-mw7aashj> <div class="p-12" data-astro-cid-mw7aashj> <button id="copy-to-llm-btn" class="mb-6 px-4 py-2 bg-accent hover:bg-accent/80 text-accent-foreground rounded-md text-sm font-medium transition-colors flex items-center gap-2" aria-label="Copy raw markdown to clipboard" data-astro-cid-mw7aashj> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-mw7aashj> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-mw7aashj></path> </svg> <span id="copy-btn-text" data-astro-cid-mw7aashj>Copy to LLM</span> </button> <article class="prose prose-invert max-w-4xl" data-astro-cid-mw7aashj> <h1 id="sdk">SDK</h1>
1
+ <!DOCTYPE html><html lang="en" data-astro-cid-mw7aashj> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v4.16.19"><link rel="canonical" href="https://loopwind.dev/sdk/"><!-- Primary Meta Tags --><title></title><meta name="title"><meta name="description" content="CLI-based Image &#38; Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates."><meta name="keywords" content="loopwind, image generation, video generation, React, Tailwind CSS, Satori, CLI tool, templates, shadcn/ui, AI agents, Cursor, automation, serverless, WASM, Open Graph, social media, marketing automation"><meta name="author" content="loopwind"><!-- Open Graph / Facebook --><meta property="og:type" content="website"><meta property="og:url" content="https://loopwind.dev/sdk/"><meta property="og:title"><meta property="og:description" content="CLI-based Image &#38; Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates."><meta property="og:image" content="https://loopwind.dev/api/og/sdk"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:image:alt"><meta property="og:site_name" content="loopwind"><meta property="og:locale" content="en_US"><!-- Twitter --><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://loopwind.dev/sdk/"><meta name="twitter:title"><meta name="twitter:description" content="CLI-based Image &#38; Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates."><meta name="twitter:image" content="https://loopwind.dev/api/og/sdk"><meta name="twitter:image:alt"><meta name="twitter:creator" content="@loopwind"><meta name="twitter:site" content="@loopwind"><!-- Theme Color --><meta name="theme-color" content="#0a0a0a"><meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0a0a0a"><meta name="theme-color" media="(prefers-color-scheme: light)" content="#fafafa"><!-- Additional SEO --><meta name="robots" content="index, follow"><meta name="googlebot" content="index, follow"><meta name="language" content="English"><meta name="revisit-after" content="7 days"><meta name="rating" content="general"><!-- Mobile Web App --><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="loopwind"><!-- Structured Data (JSON-LD) --><script type="application/ld+json">{"@context":"https://schema.org","@type":"SoftwareApplication","name":"loopwind","applicationCategory":"DeveloperApplication","operatingSystem":"Cross-platform","description":"CLI-based Image & Video Generator for developers and AI Agents. Generate stunning visuals from React + Tailwind templates.","url":"https://loopwind.dev/","author":{"@type":"Organization","name":"loopwind","url":"https://loopwind.dev/"},"offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"softwareVersion":"0.11.0","releaseNotes":"https://github.com/loopwind/loopwind/releases","screenshot":"https://loopwind.dev/api/og/sdk","featureList":["Image generation from React templates","Video generation with animations","Tailwind CSS support","shadcn/ui design system","CLI-based workflow","Serverless rendering","TypeScript support","AI Agent friendly"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"loopwind","url":"https://loopwind.dev/","logo":"https://loopwind.dev/favicon.svg","sameAs":["https://github.com/loopwind/loopwind","https://www.npmjs.com/package/loopwind"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://loopwind.dev/"},{"@type":"ListItem","position":2,"name":"Sdk","item":"https://loopwind.dev/sdk"}]}</script><link rel="stylesheet" href="/_astro/agents.CZXv4DCM.css">
2
+ <style>.hero[data-astro-cid-bbe6dxrz]{position:relative}html{scroll-behavior:smooth}.mobile-header[data-astro-cid-mw7aashj]{display:none}.toc-sidebar[data-astro-cid-mw7aashj]{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.1) transparent}.toc-sidebar[data-astro-cid-mw7aashj]::-webkit-scrollbar{width:4px}.toc-sidebar[data-astro-cid-mw7aashj]::-webkit-scrollbar-track{background:transparent}.toc-sidebar[data-astro-cid-mw7aashj]::-webkit-scrollbar-thumb{background-color:#ffffff1a;border-radius:2px}.toc-sidebar[data-astro-cid-mw7aashj]::-webkit-scrollbar-thumb:hover{background-color:#fff3}#copy-to-llm-btn[data-astro-cid-mw7aashj]{cursor:pointer}@media (max-width: 768px){.mobile-header[data-astro-cid-mw7aashj]{display:block}.desktop-sidebar[data-astro-cid-mw7aashj]{display:none}main[data-astro-cid-mw7aashj]{margin-left:0;margin-right:0!important;padding:5rem 1.5rem 1.5rem}.toc-sidebar[data-astro-cid-mw7aashj]{display:none}#copy-to-llm-btn[data-astro-cid-mw7aashj]{width:100%;justify-content:center}}@media (max-width: 1280px){.toc-sidebar[data-astro-cid-mw7aashj]{display:none!important}main[data-astro-cid-mw7aashj]{margin-right:0!important}}
3
+ </style><script type="module">const p=document.getElementById("mobile-menu-button"),l=document.getElementById("mobile-menu"),g=document.querySelectorAll(".mobile-menu-link");p?.addEventListener("click",()=>{l?.classList.toggle("hidden")});l?.addEventListener("click",o=>{o.target===l&&l.classList.add("hidden")});g.forEach(o=>{o.addEventListener("click",()=>{l?.classList.add("hidden")})});function m(){const o=document.getElementById("toc-nav");if(!o){console.log("TOC: No toc-nav element found");return}const n=document.querySelector("article");if(!n){console.log("TOC: No article element found");return}const c=n.querySelectorAll("h2, h3");if(c.length===0){console.log("TOC: No headings found");return}console.log(`TOC: Found ${c.length} headings`);const a=document.createElement("ul");a.className="space-y-0 text-sm border-l border-border/50",c.forEach(e=>{const t=e.tagName==="H2"?2:3,r=e.id||e.textContent?.toLowerCase().replace(/[^\w]+/g,"-");!e.id&&r&&(e.id=r);const s=document.createElement("li"),d=document.createElement("a");d.href=`#${r}`,d.textContent=e.textContent,d.className=`toc-link block py-1 px-3 -ml-px border-l-2 border-transparent transition-all no-underline ${t===3?"pl-6 text-xs text-muted-foreground/70":"text-sm text-muted-foreground"} hover:text-foreground hover:border-muted-foreground/50`,s.appendChild(d),a.appendChild(s)}),o.appendChild(a);const i=new IntersectionObserver(e=>{e.forEach(t=>{const r=t.target.id,s=o.querySelector(`a[href="#${r}"]`);t.isIntersecting&&(document.querySelectorAll(".toc-link").forEach(d=>{d.classList.remove("text-foreground","border-accent","font-medium")}),s?.classList.add("text-foreground","border-accent","font-medium"))})},{rootMargin:"-100px 0px -66%",threshold:0});c.forEach(e=>{i.observe(e)})}document.addEventListener("DOMContentLoaded",m);document.addEventListener("astro:page-load",m);function u(){const o=document.getElementById("copy-to-llm-btn"),n=document.getElementById("copy-btn-text");if(!o||!n){console.log("Copy to LLM: Button not found on this page");return}console.log("Copy to LLM: Initialized"),o.addEventListener("click",async()=>{const c=window.location.pathname,a=c.replace(/^\//,"").replace(/\/$/,""),e=`/api/raw-markdown/${a===""?"index":a}`;console.log("Copy to LLM clicked: pathname=",c,"apiUrl=",e);try{n.textContent="Copying...";const t=await fetch(e);if(console.log("Response status:",t.status),!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const r=await t.text();console.log("Got markdown, length:",r.length),await navigator.clipboard.writeText(r),n.textContent="Copied!",setTimeout(()=>{n.textContent="Copy to LLM"},2e3)}catch(t){console.error("Copy failed:",t),n.textContent="Failed",setTimeout(()=>{n.textContent="Copy to LLM"},2e3)}})}document.addEventListener("DOMContentLoaded",u);document.addEventListener("astro:page-load",u);
4
+ </script></head> <body class="antialiased" data-astro-cid-mw7aashj> <div class="min-h-screen" data-astro-cid-mw7aashj> <!-- Mobile Header --> <header class="mobile-header fixed top-0 left-0 right-0 h-16 bg-card border-b border-border z-50 md:hidden" data-astro-cid-mw7aashj> <div class="flex items-center justify-between h-full px-4" data-astro-cid-mw7aashj> <a href="/" class="no-underline"> <h1 class="text-xl flex items-center gap-2 bg-linear-to-r from-brand-from to-brand-to bg-clip-text text-transparent"> <span class="text-2xl">➰</span> <span>loopwind</span> </h1> </a> <button id="mobile-menu-button" class="p-2 text-foreground hover:bg-accent rounded-md transition-colors" aria-label="Toggle menu" data-astro-cid-mw7aashj> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-mw7aashj> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" data-astro-cid-mw7aashj></path> </svg> </button> </div> </header> <!-- Mobile Menu Overlay --> <div id="mobile-menu" class="mobile-menu fixed inset-0 bg-background/80 backdrop-blur-sm z-40 md:hidden hidden" data-astro-cid-mw7aashj> <div class="fixed top-0 left-0 h-screen w-64 bg-card border-r border-border overflow-y-auto" data-astro-cid-mw7aashj> <div class="p-6 pt-20" data-astro-cid-mw7aashj> <nav data-astro-cid-mw7aashj> <ul class="space-y-1" data-astro-cid-mw7aashj> <li data-astro-cid-mw7aashj> <a href="/" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Home </a> </li><li data-astro-cid-mw7aashj> <a href="/getting-started" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Getting Started </a> </li><li data-astro-cid-mw7aashj> <a href="/templates" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Templates </a> </li><li data-astro-cid-mw7aashj> <a href="/images" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Images </a> </li><li data-astro-cid-mw7aashj> <a href="/video" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Video </a> </li><li data-astro-cid-mw7aashj> <a href="/preview" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Preview </a> </li><li data-astro-cid-mw7aashj> <a href="/animation" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Animation </a> </li><li data-astro-cid-mw7aashj> <a href="/helpers" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Helpers </a> </li><li data-astro-cid-mw7aashj> <a href="/styling" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Styling </a> </li><li data-astro-cid-mw7aashj> <a href="/fonts" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Fonts </a> </li><li data-astro-cid-mw7aashj> <a href="/config" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> loopwind.json </a> </li><li data-astro-cid-mw7aashj> <a href="/sdk" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> SDK </a> </li><li data-astro-cid-mw7aashj> <a href="/playground" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Playground </a> </li><li data-astro-cid-mw7aashj> <a href="/agents" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Use with AI Agents </a> </li><li data-astro-cid-mw7aashj> <a href="/llm.txt" class="mobile-menu-link block px-3 py-2 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> llm.txt ⇱ </a> </li> </ul> </nav> </div> </div> </div> <!-- Desktop Sidebar --> <aside class="desktop-sidebar fixed top-0 left-0 h-screen w-64 border-r border-border bg-card overflow-y-auto z-10" data-astro-cid-mw7aashj> <div class="p-6" data-astro-cid-mw7aashj> <div class="block mb-3" data-astro-cid-mw7aashj> <a href="/" class="no-underline"> <h1 class="text-3xl flex items-center gap-2 bg-linear-to-r from-brand-from to-brand-to bg-clip-text text-transparent"> <span class="text-4xl">➰</span> <span>loopwind</span> </h1> </a> </div> <nav data-astro-cid-mw7aashj> <ul class="space-y-0" data-astro-cid-mw7aashj> <li data-astro-cid-mw7aashj> <a href="/" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Home </a> </li><li data-astro-cid-mw7aashj> <a href="/getting-started" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Getting Started </a> </li><li data-astro-cid-mw7aashj> <a href="/templates" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Templates </a> </li><li data-astro-cid-mw7aashj> <a href="/images" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Images </a> </li><li data-astro-cid-mw7aashj> <a href="/video" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Video </a> </li><li data-astro-cid-mw7aashj> <a href="/preview" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Preview </a> </li><li data-astro-cid-mw7aashj> <a href="/animation" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Animation </a> </li><li data-astro-cid-mw7aashj> <a href="/helpers" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Helpers </a> </li><li data-astro-cid-mw7aashj> <a href="/styling" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Styling </a> </li><li data-astro-cid-mw7aashj> <a href="/fonts" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Fonts </a> </li><li data-astro-cid-mw7aashj> <a href="/config" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> loopwind.json </a> </li><li data-astro-cid-mw7aashj> <a href="/sdk" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> SDK </a> </li><li data-astro-cid-mw7aashj> <a href="/playground" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Playground </a> </li><li data-astro-cid-mw7aashj> <a href="/agents" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> Use with AI Agents </a> </li><li data-astro-cid-mw7aashj> <a href="/llm.txt" class="block px-3 py-1.5 rounded-md text-sm transition-colors no-underline text-muted-foreground hover:text-foreground hover:bg-accent/50" data-astro-cid-mw7aashj> llm.txt ⇱ </a> </li> </ul> </nav> </div> </aside> <!-- Main content --> <main class="ml-64 mr-64" data-astro-cid-mw7aashj> <div class="p-12" data-astro-cid-mw7aashj> <button id="copy-to-llm-btn" class="mb-6 px-4 py-2 bg-accent hover:bg-accent/80 text-accent-foreground rounded-md text-sm font-medium transition-colors flex items-center gap-2" aria-label="Copy raw markdown to clipboard" data-astro-cid-mw7aashj> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-mw7aashj> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-mw7aashj></path> </svg> <span id="copy-btn-text" data-astro-cid-mw7aashj>Copy to LLM</span> </button> <article class="prose prose-invert max-w-4xl" data-astro-cid-mw7aashj> <h1 id="sdk">SDK</h1>
3
5
  <p>Use <strong>loopwind</strong> programmatically in your Next.js API routes, serverless functions, or Node.js applications. Perfect for building image/video generation APIs!</p>
4
6
  <h2 id="installation">Installation</h2>
5
7
  <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> loopwind</span></span>
@@ -28,11 +30,11 @@
28
30
  <span class="line"><span style="color:#E1E4E8">}</span></span>
29
31
  <span class="line"></span></code></pre>
30
32
  <p>Use it in your API route:</p>
31
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromFile, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
33
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
32
34
  <span class="line"><span style="color:#F97583">import</span><span style="color:#79B8FF"> *</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> ogTemplate </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;./_loopwind/templates/og-image/template&#39;</span><span style="color:#E1E4E8">;</span></span>
33
35
  <span class="line"></span>
34
36
  <span class="line"><span style="color:#6A737D">// Load template from file</span></span>
35
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromFile</span><span style="color:#E1E4E8">(ogTemplate, {</span></span>
37
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">(ogTemplate, {</span></span>
36
38
  <span class="line"><span style="color:#E1E4E8"> config: {</span></span>
37
39
  <span class="line"><span style="color:#E1E4E8"> colors: {</span></span>
38
40
  <span class="line"><span style="color:#E1E4E8"> primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span><span style="color:#E1E4E8">,</span></span>
@@ -47,49 +49,6 @@
47
49
  <span class="line"><span style="color:#E1E4E8"> description: </span><span style="color:#9ECBFF">&#39;Generated with loopwind SDK&#39;</span><span style="color:#E1E4E8">,</span></span>
48
50
  <span class="line"><span style="color:#E1E4E8">});</span></span>
49
51
  <span class="line"></span></code></pre>
50
- <p><strong>Benefits:</strong></p>
51
- <ul>
52
- <li>✅ Reuse templates between CLI and SDK</li>
53
- <li>✅ Keep templates in separate files (clean organization)</li>
54
- <li>✅ Standard loopwind format</li>
55
- <li>✅ Full JSX support with <code>&lt;div&gt;</code>, <code>&lt;h1&gt;</code>, etc.</li>
56
- <li>✅ Works with any bundler (Next.js, Vite, Remix, etc.)</li>
57
- </ul>
58
- <h2 id="sdk-vs-cli">SDK vs CLI</h2>
59
- <p>The SDK is designed to be <strong>completely stateless</strong> and <strong>self-contained</strong> - perfect for serverless environments like Vercel, Netlify, or AWS Lambda.</p>
60
- <p><strong>Key differences:</strong></p>
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
- <table><thead><tr><th>Feature</th><th>CLI (<code>loopwind render</code>)</th><th>SDK (<code>renderImage()</code>)</th></tr></thead><tbody><tr><td><strong>Configuration</strong></td><td>Uses <code>loopwind.json</code> for colors, fonts, paths</td><td>Programmatic <code>StyleConfig</code> passed directly</td></tr><tr><td><strong>Templates</strong></td><td>File-based in <code>_loopwind/templates/</code></td><td>Defined with <code>defineTemplate()</code></td></tr><tr><td><strong>State</strong></td><td>Stateful (project directory)</td><td>Stateless (no filesystem reads)</td></tr><tr><td><strong>Use case</strong></td><td>Local development, project-based</td><td>APIs, serverless, dynamic generation</td></tr></tbody></table>
92
- <p><strong>Important:</strong> The SDK does <strong>not</strong> read <code>loopwind.json</code> from the filesystem. All configuration (colors, fonts) must be passed programmatically using <code>StyleConfig</code>.</p>
93
52
  <h2 id="style-configuration">Style Configuration</h2>
94
53
  <p>The SDK accepts configuration through the <code>StyleConfig</code> type, which you can pass in two places:</p>
95
54
  <ol>
@@ -116,7 +75,7 @@
116
75
  <span class="line"><span style="color:#E1E4E8"> );</span></span>
117
76
  <span class="line"><span style="color:#E1E4E8">}</span></span>
118
77
  <span class="line"></span></code></pre>
119
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromFile, renderImage, </span><span style="color:#F97583">type</span><span style="color:#E1E4E8"> StyleConfig } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
78
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, renderImage, </span><span style="color:#F97583">type</span><span style="color:#E1E4E8"> StyleConfig } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
120
79
  <span class="line"><span style="color:#F97583">import</span><span style="color:#79B8FF"> *</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> ogTemplate </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;./templates/og-image&#39;</span><span style="color:#E1E4E8">;</span></span>
121
80
  <span class="line"></span>
122
81
  <span class="line"><span style="color:#6A737D">// Define your style config</span></span>
@@ -132,7 +91,7 @@
132
91
  <span class="line"><span style="color:#E1E4E8">};</span></span>
133
92
  <span class="line"></span>
134
93
  <span class="line"><span style="color:#6A737D">// Option 1: Pass config when defining template</span></span>
135
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromFile</span><span style="color:#E1E4E8">(ogTemplate, {</span></span>
94
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">(ogTemplate, {</span></span>
136
95
  <span class="line"><span style="color:#E1E4E8"> config, </span><span style="color:#6A737D">// Fixed theme for this template</span></span>
137
96
  <span class="line"><span style="color:#E1E4E8">});</span></span>
138
97
  <span class="line"></span>
@@ -172,11 +131,11 @@
172
131
  <span class="line"><span style="color:#E1E4E8"> );</span></span>
173
132
  <span class="line"><span style="color:#E1E4E8">}</span></span>
174
133
  <span class="line"></span></code></pre>
175
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromFile, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
134
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
176
135
  <span class="line"><span style="color:#F97583">import</span><span style="color:#79B8FF"> *</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> brandTemplate </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;./templates/branded-og&#39;</span><span style="color:#E1E4E8">;</span></span>
177
136
  <span class="line"></span>
178
137
  <span class="line"><span style="color:#6A737D">// Load template</span></span>
179
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromFile</span><span style="color:#E1E4E8">(brandTemplate);</span></span>
138
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">(brandTemplate);</span></span>
180
139
  <span class="line"></span>
181
140
  <span class="line"><span style="color:#6A737D">// Render with different brand colors for each customer</span></span>
182
141
  <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> customer1</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template,</span></span>
@@ -279,20 +238,26 @@
279
238
  <li>✅ OTF (<code>.otf</code>)</li>
280
239
  <li>❌ WOFF2 (not supported)</li>
281
240
  </ul>
282
- <h3 id="alternative-hardcoded-colors">Alternative: Hardcoded Colors</h3>
283
- <p>If you prefer not to use config, you can still hardcode colors:</p>
284
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
241
+ <h3 id="hardcoded-colors">Hardcoded Colors</h3>
242
+ <p>You can hardcode colors directly in your template using Tailwind arbitrary values:</p>
243
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#6A737D">// templates/og-image.tsx</span></span>
244
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> meta</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
285
245
  <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;og-image&#39;</span><span style="color:#E1E4E8">,</span></span>
246
+ <span class="line"><span style="color:#E1E4E8"> type: </span><span style="color:#9ECBFF">&#39;image&#39;</span><span style="color:#F97583"> as</span><span style="color:#F97583"> const</span><span style="color:#E1E4E8">,</span></span>
286
247
  <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
287
- <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> (</span></span>
288
- <span class="line"><span style="color:#F97583"> &lt;</span><span style="color:#E1E4E8">div style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;bg-[#3B82F6] text-white p-12&#39;</span><span style="color:#E1E4E8">)}</span><span style="color:#F97583">&gt;</span></span>
289
- <span class="line"><span style="color:#F97583"> &lt;</span><span style="color:#E1E4E8">h1 style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-6xl font-bold&#39;</span><span style="color:#E1E4E8">)}</span><span style="color:#F97583">&gt;</span><span style="color:#E1E4E8">{title}</span><span style="color:#F97583">&lt;/</span><span style="color:#E1E4E8">h1</span><span style="color:#F97583">&gt;</span></span>
290
- <span class="line"><span style="color:#F97583"> &lt;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">&gt;</span></span>
291
- <span class="line"><span style="color:#E1E4E8"> ),</span></span>
292
- <span class="line"><span style="color:#E1E4E8">});</span></span>
248
+ <span class="line"><span style="color:#E1E4E8"> props: { title: </span><span style="color:#9ECBFF">&#39;string&#39;</span><span style="color:#E1E4E8"> },</span></span>
249
+ <span class="line"><span style="color:#E1E4E8">};</span></span>
250
+ <span class="line"></span>
251
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> OgImage</span><span style="color:#E1E4E8">({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) {</span></span>
252
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> (</span></span>
253
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#B392F0"> style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;bg-[#3B82F6] text-white p-12&#39;</span><span style="color:#E1E4E8">)}&gt;</span></span>
254
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">h1</span><span style="color:#B392F0"> style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-6xl font-bold&#39;</span><span style="color:#E1E4E8">)}&gt;{title}&lt;/</span><span style="color:#85E89D">h1</span><span style="color:#E1E4E8">&gt;</span></span>
255
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
256
+ <span class="line"><span style="color:#E1E4E8"> );</span></span>
257
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
293
258
  <span class="line"></span></code></pre>
294
259
  <h2 id="core-functions">Core Functions</h2>
295
- <h3 id="definetemplatefromfile-recommended">defineTemplateFromFile() (Recommended)</h3>
260
+ <h3 id="definetemplate">defineTemplate()</h3>
296
261
  <p>Load a template from a separate file (standard loopwind format):</p>
297
262
  <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#6A737D">// _loopwind/templates/my-template/template.tsx</span></span>
298
263
  <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> const</span><span style="color:#79B8FF"> meta</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
@@ -312,10 +277,10 @@
312
277
  <span class="line"><span style="color:#E1E4E8"> );</span></span>
313
278
  <span class="line"><span style="color:#E1E4E8">}</span></span>
314
279
  <span class="line"></span></code></pre>
315
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromFile } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
280
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
316
281
  <span class="line"><span style="color:#F97583">import</span><span style="color:#79B8FF"> *</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> myTemplate </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;./_loopwind/templates/my-template/template&#39;</span><span style="color:#E1E4E8">;</span></span>
317
282
  <span class="line"></span>
318
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromFile</span><span style="color:#E1E4E8">(myTemplate, {</span></span>
283
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">(myTemplate, {</span></span>
319
284
  <span class="line"><span style="color:#E1E4E8"> config: {</span></span>
320
285
  <span class="line"><span style="color:#E1E4E8"> colors: {</span></span>
321
286
  <span class="line"><span style="color:#E1E4E8"> primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span><span style="color:#E1E4E8">,</span></span>
@@ -334,33 +299,6 @@
334
299
  <li>✅ Full JSX support with <code>&lt;div&gt;</code>, <code>&lt;h1&gt;</code>, etc.</li>
335
300
  <li>✅ Standard loopwind format</li>
336
301
  </ul>
337
- <h3 id="definetemplate-alternative">defineTemplate() (Alternative)</h3>
338
- <p>For inline templates without separate files:</p>
339
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
340
- <span class="line"></span>
341
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
342
- <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;my-template&#39;</span><span style="color:#E1E4E8">,</span></span>
343
- <span class="line"><span style="color:#E1E4E8"> type: </span><span style="color:#9ECBFF">&#39;image&#39;</span><span style="color:#E1E4E8">,</span></span>
344
- <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
345
- <span class="line"><span style="color:#E1E4E8"> config: {</span></span>
346
- <span class="line"><span style="color:#E1E4E8"> colors: {</span></span>
347
- <span class="line"><span style="color:#E1E4E8"> primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span><span style="color:#E1E4E8">,</span></span>
348
- <span class="line"><span style="color:#E1E4E8"> background: </span><span style="color:#9ECBFF">&#39;#ffffff&#39;</span><span style="color:#E1E4E8">,</span></span>
349
- <span class="line"><span style="color:#E1E4E8"> },</span></span>
350
- <span class="line"><span style="color:#E1E4E8"> },</span></span>
351
- <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> (</span></span>
352
- <span class="line"><span style="color:#F97583"> &lt;</span><span style="color:#E1E4E8">div style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex items-center justify-center w-full h-full bg-background&#39;</span><span style="color:#E1E4E8">)}</span><span style="color:#F97583">&gt;</span></span>
353
- <span class="line"><span style="color:#F97583"> &lt;</span><span style="color:#E1E4E8">h1 style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-6xl font-bold text-primary&#39;</span><span style="color:#E1E4E8">)}</span><span style="color:#F97583">&gt;</span><span style="color:#E1E4E8">{title}</span><span style="color:#F97583">&lt;/</span><span style="color:#E1E4E8">h1</span><span style="color:#F97583">&gt;</span></span>
354
- <span class="line"><span style="color:#F97583"> &lt;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">&gt;</span></span>
355
- <span class="line"><span style="color:#E1E4E8"> ),</span></span>
356
- <span class="line"><span style="color:#E1E4E8">});</span></span>
357
- <span class="line"></span></code></pre>
358
- <p><strong>Use when:</strong></p>
359
- <ul>
360
- <li>Simple, one-off templates</li>
361
- <li>No need to reuse in CLI</li>
362
- <li>Keeping everything in one file</li>
363
- </ul>
364
302
  <h3 id="renderimage">renderImage()</h3>
365
303
  <p>Render an image template to a Buffer:</p>
366
304
  <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
@@ -451,41 +389,83 @@
451
389
  <span class="line"><span style="color:#6A737D"># Or publish as npm package</span></span>
452
390
  <span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> publish</span><span style="color:#9ECBFF"> loopwind-templates</span></span>
453
391
  <span class="line"></span></code></pre>
454
- <h2 id="user-generated-templates">User-Generated Templates</h2>
455
- <p>Build template editors where users create templates without writing code!</p>
456
- <h3 id="code-editor-definetemplatefromsource">Code Editor (defineTemplateFromSource)</h3>
457
- <p>For code editors where users write template source as strings:</p>
458
- <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromSource, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
459
- <span class="line"></span>
460
- <span class="line"><span style="color:#6A737D">// User writes template code in Monaco Editor, CodeMirror, etc.</span></span>
461
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> userCode</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> `</span></span>
462
- <span class="line"><span style="color:#9ECBFF">export const meta = {</span></span>
463
- <span class="line"><span style="color:#9ECBFF"> name: &#39;user-card&#39;,</span></span>
464
- <span class="line"><span style="color:#9ECBFF"> type: &#39;image&#39;,</span></span>
465
- <span class="line"><span style="color:#9ECBFF"> size: { width: 1200, height: 630 }</span></span>
466
- <span class="line"><span style="color:#9ECBFF">};</span></span>
467
- <span class="line"></span>
468
- <span class="line"><span style="color:#9ECBFF">export default ({ tw, title, description }) =&gt; (</span></span>
469
- <span class="line"><span style="color:#9ECBFF"> &lt;div style={tw(&#39;flex flex-col w-full h-full bg-gradient-to-br from-blue-600 to-purple-700 p-12&#39;)}&gt;</span></span>
470
- <span class="line"><span style="color:#9ECBFF"> &lt;h1 style={tw(&#39;text-6xl font-bold text-white&#39;)}&gt;{title}&lt;/h1&gt;</span></span>
471
- <span class="line"><span style="color:#9ECBFF"> &lt;p style={tw(&#39;text-2xl text-white/80&#39;)}&gt;{description}&lt;/p&gt;</span></span>
472
- <span class="line"><span style="color:#9ECBFF"> &lt;/div&gt;</span></span>
473
- <span class="line"><span style="color:#9ECBFF">);</span></span>
474
- <span class="line"><span style="color:#9ECBFF">`</span><span style="color:#E1E4E8">;</span></span>
475
- <span class="line"></span>
476
- <span class="line"><span style="color:#6A737D">// Create template from source string</span></span>
477
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromSource</span><span style="color:#E1E4E8">(userCode, {</span></span>
478
- <span class="line"><span style="color:#E1E4E8"> config: {</span></span>
479
- <span class="line"><span style="color:#E1E4E8"> colors: { primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span><span style="color:#E1E4E8"> }</span></span>
392
+ <h2 id="advanced-user-generated-templates">Advanced: User-Generated Templates</h2>
393
+ <p>Build template editors where users create templates without writing code.</p>
394
+ <h3 id="code-editor-jsx-pre-compilation">Code Editor (JSX Pre-compilation)</h3>
395
+ <p>For code editors where users write JSX templates, use a two-step workflow:</p>
396
+ <p><strong>Step 1: Admin Panel - Compile JSX</strong></p>
397
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// app/admin/templates/save/route.ts</span></span>
398
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { compileTemplate } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk/compiler&#39;</span><span style="color:#E1E4E8">;</span></span>
399
+ <span class="line"></span>
400
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> POST</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">req</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Request</span><span style="color:#E1E4E8">) {</span></span>
401
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">templateId</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">jsxCode</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> req.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">();</span></span>
402
+ <span class="line"></span>
403
+ <span class="line"><span style="color:#6A737D"> // User wrote JSX in Monaco Editor, CodeMirror, etc.</span></span>
404
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> jsxCode</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> `</span></span>
405
+ <span class="line"><span style="color:#9ECBFF"> export const meta = {</span></span>
406
+ <span class="line"><span style="color:#9ECBFF"> name: &#39;user-card&#39;,</span></span>
407
+ <span class="line"><span style="color:#9ECBFF"> type: &#39;image&#39;,</span></span>
408
+ <span class="line"><span style="color:#9ECBFF"> size: { width: 1200, height: 630 }</span></span>
409
+ <span class="line"><span style="color:#9ECBFF"> };</span></span>
410
+ <span class="line"></span>
411
+ <span class="line"><span style="color:#9ECBFF"> export default ({ tw, title, description }) =&gt; (</span></span>
412
+ <span class="line"><span style="color:#9ECBFF"> &lt;div style={tw(&#39;flex flex-col w-full h-full bg-gradient-to-br from-blue-600 to-purple-700 p-12&#39;)}&gt;</span></span>
413
+ <span class="line"><span style="color:#9ECBFF"> &lt;h1 style={tw(&#39;text-6xl font-bold text-white&#39;)}&gt;{title}&lt;/h1&gt;</span></span>
414
+ <span class="line"><span style="color:#9ECBFF"> &lt;p style={tw(&#39;text-2xl text-white/80&#39;)}&gt;{description}&lt;/p&gt;</span></span>
415
+ <span class="line"><span style="color:#9ECBFF"> &lt;/div&gt;</span></span>
416
+ <span class="line"><span style="color:#9ECBFF"> );</span></span>
417
+ <span class="line"><span style="color:#9ECBFF"> `</span><span style="color:#E1E4E8">;</span></span>
418
+ <span class="line"></span>
419
+ <span class="line"><span style="color:#F97583"> try</span><span style="color:#E1E4E8"> {</span></span>
420
+ <span class="line"><span style="color:#6A737D"> // Compile JSX to JavaScript</span></span>
421
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> compiled</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> compileTemplate</span><span style="color:#E1E4E8">(jsxCode);</span></span>
422
+ <span class="line"></span>
423
+ <span class="line"><span style="color:#6A737D"> // Save compiled version to database</span></span>
424
+ <span class="line"><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> db.templates.</span><span style="color:#B392F0">update</span><span style="color:#E1E4E8">(templateId, {</span></span>
425
+ <span class="line"><span style="color:#E1E4E8"> sourceCode: compiled,</span></span>
426
+ <span class="line"><span style="color:#E1E4E8"> updatedAt: </span><span style="color:#F97583">new</span><span style="color:#B392F0"> Date</span><span style="color:#E1E4E8">()</span></span>
427
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
428
+ <span class="line"></span>
429
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> Response.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">({ success: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> });</span></span>
430
+ <span class="line"><span style="color:#E1E4E8"> } </span><span style="color:#F97583">catch</span><span style="color:#E1E4E8"> (error) {</span></span>
431
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> Response.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">({</span></span>
432
+ <span class="line"><span style="color:#E1E4E8"> error: </span><span style="color:#9ECBFF">&#39;Compilation failed&#39;</span><span style="color:#E1E4E8">,</span></span>
433
+ <span class="line"><span style="color:#E1E4E8"> message: error.message</span></span>
434
+ <span class="line"><span style="color:#E1E4E8"> }, { status: </span><span style="color:#79B8FF">400</span><span style="color:#E1E4E8"> });</span></span>
480
435
  <span class="line"><span style="color:#E1E4E8"> }</span></span>
481
- <span class="line"><span style="color:#E1E4E8">});</span></span>
436
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
437
+ <span class="line"></span></code></pre>
438
+ <p><strong>Step 2: Production API - Render Pre-compiled Templates</strong></p>
439
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// app/api/render/route.ts</span></span>
440
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromSource, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
482
441
  <span class="line"></span>
483
- <span class="line"><span style="color:#6A737D">// Render</span></span>
484
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, {</span></span>
485
- <span class="line"><span style="color:#E1E4E8"> title: </span><span style="color:#9ECBFF">&#39;User Created&#39;</span><span style="color:#E1E4E8">,</span></span>
486
- <span class="line"><span style="color:#E1E4E8"> description: </span><span style="color:#9ECBFF">&#39;From code editor&#39;</span></span>
487
- <span class="line"><span style="color:#E1E4E8">});</span></span>
442
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> POST</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">req</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Request</span><span style="color:#E1E4E8">) {</span></span>
443
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">templateId</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">props</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> req.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">();</span></span>
444
+ <span class="line"></span>
445
+ <span class="line"><span style="color:#6A737D"> // Load pre-compiled template from database</span></span>
446
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> db.templates.</span><span style="color:#B392F0">findById</span><span style="color:#E1E4E8">(templateId);</span></span>
447
+ <span class="line"></span>
448
+ <span class="line"><span style="color:#6A737D"> // No Babel needed! Already compiled</span></span>
449
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> templateDef</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromSource</span><span style="color:#E1E4E8">(template.sourceCode, {</span></span>
450
+ <span class="line"><span style="color:#E1E4E8"> config: { colors: { primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span><span style="color:#E1E4E8"> } }</span></span>
451
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
452
+ <span class="line"></span>
453
+ <span class="line"><span style="color:#6A737D"> // Render</span></span>
454
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(templateDef, props);</span></span>
455
+ <span class="line"></span>
456
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Response</span><span style="color:#E1E4E8">(png, {</span></span>
457
+ <span class="line"><span style="color:#E1E4E8"> headers: { </span><span style="color:#9ECBFF">&#39;Content-Type&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;image/png&#39;</span><span style="color:#E1E4E8"> }</span></span>
458
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
459
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
488
460
  <span class="line"></span></code></pre>
461
+ <p><strong>✨ Benefits:</strong></p>
462
+ <ul>
463
+ <li>Users write natural JSX syntax (<code>&lt;div&gt;</code>, <code>&lt;h1&gt;</code>) in admin panel</li>
464
+ <li>JSX compiled once at save time (not every render)</li>
465
+ <li>Production bundle <strong>36 MB smaller</strong> (no @babel/standalone)</li>
466
+ <li>Faster cold starts in Vercel/Lambda</li>
467
+ <li>Syntax errors caught early (at save, not production)</li>
468
+ </ul>
489
469
  <p><strong>⚠️ Security Warning:</strong></p>
490
470
  <p><code>defineTemplateFromSource()</code> uses <code>new Function()</code> to evaluate code strings. <strong>Only use with trusted sources!</strong> Never pass untrusted user input directly.</p>
491
471
  <p><strong>Safe use cases:</strong></p>
@@ -498,7 +478,7 @@
498
478
  <ul>
499
479
  <li>Must export <code>meta</code> object with <code>name</code>, <code>size</code>, and optional <code>type</code>/<code>video</code></li>
500
480
  <li>Must have <code>export default</code> render function</li>
501
- <li><strong>Supports JSX syntax!</strong> Write normal React components with <code>&lt;div&gt;</code>, <code>&lt;h1&gt;</code>, etc.</li>
481
+ <li>Supports JSX syntax (compile with <code>compileTemplate()</code> first)</li>
502
482
  <li>Props use curly braces: <code>style={tw(&#39;...&#39;)}</code>, <code>{title}</code></li>
503
483
  </ul>
504
484
  <h3 id="visual-builder-definetemplatefromschema">Visual Builder (defineTemplateFromSchema)</h3>
@@ -644,11 +624,11 @@
644
624
  <p>Use it in your API route:</p>
645
625
  <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// pages/api/og-image.ts</span></span>
646
626
  <span class="line"><span style="color:#F97583">import</span><span style="color:#F97583"> type</span><span style="color:#E1E4E8"> { NextApiRequest, NextApiResponse } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;next&#39;</span><span style="color:#E1E4E8">;</span></span>
647
- <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromFile, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
627
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
648
628
  <span class="line"><span style="color:#F97583">import</span><span style="color:#79B8FF"> *</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> ogTemplate </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;../../_loopwind/templates/og-image/template&#39;</span><span style="color:#E1E4E8">;</span></span>
649
629
  <span class="line"></span>
650
630
  <span class="line"><span style="color:#6A737D">// Load template once at module level (cached)</span></span>
651
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromFile</span><span style="color:#E1E4E8">(ogTemplate);</span></span>
631
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">(ogTemplate);</span></span>
652
632
  <span class="line"></span>
653
633
  <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> handler</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">req</span><span style="color:#F97583">:</span><span style="color:#B392F0"> NextApiRequest</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">res</span><span style="color:#F97583">:</span><span style="color:#B392F0"> NextApiResponse</span><span style="color:#E1E4E8">) {</span></span>
654
634
  <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">title</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> &#39;Welcome&#39;</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">description</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> &#39;Generated with loopwind&#39;</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> req.query;</span></span>
@@ -693,11 +673,11 @@
693
673
  <p>Use it in your API route:</p>
694
674
  <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// pages/api/intro-video.ts</span></span>
695
675
  <span class="line"><span style="color:#F97583">import</span><span style="color:#F97583"> type</span><span style="color:#E1E4E8"> { NextApiRequest, NextApiResponse } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;next&#39;</span><span style="color:#E1E4E8">;</span></span>
696
- <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplateFromFile, renderVideo } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
676
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, renderVideo } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
697
677
  <span class="line"><span style="color:#F97583">import</span><span style="color:#79B8FF"> *</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> introTemplate </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;../../_loopwind/templates/intro-video/template&#39;</span><span style="color:#E1E4E8">;</span></span>
698
678
  <span class="line"></span>
699
679
  <span class="line"><span style="color:#6A737D">// Load template once at module level (cached)</span></span>
700
- <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplateFromFile</span><span style="color:#E1E4E8">(introTemplate);</span></span>
680
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">(introTemplate);</span></span>
701
681
  <span class="line"></span>
702
682
  <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> handler</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">req</span><span style="color:#F97583">:</span><span style="color:#B392F0"> NextApiRequest</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">res</span><span style="color:#F97583">:</span><span style="color:#B392F0"> NextApiResponse</span><span style="color:#E1E4E8">) {</span></span>
703
683
  <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">title</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> &#39;Welcome!&#39;</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> req.query;</span></span>
@@ -1062,6 +1042,538 @@
1062
1042
  <li><strong>Batch processing</strong> of images or videos</li>
1063
1043
  <li><strong>Image/video generation APIs</strong> that you can monetize</li>
1064
1044
  </ul>
1045
+ <h2 id="video-preview-component">Video Preview Component</h2>
1046
+ <p>Preview video templates in your React applications before rendering. Perfect for template editors, preview interfaces, and documentation.</p>
1047
+ <h3 id="previewvideo">previewVideo()</h3>
1048
+ <p>Pre-render all video frames on the server or at build time:</p>
1049
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, previewVideo } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
1050
+ <span class="line"></span>
1051
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1052
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;intro&#39;</span><span style="color:#E1E4E8">,</span></span>
1053
+ <span class="line"><span style="color:#E1E4E8"> type: </span><span style="color:#9ECBFF">&#39;video&#39;</span><span style="color:#E1E4E8">,</span></span>
1054
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1920</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">1080</span><span style="color:#E1E4E8"> },</span></span>
1055
+ <span class="line"><span style="color:#E1E4E8"> video: { fps: </span><span style="color:#79B8FF">30</span><span style="color:#E1E4E8">, duration: </span><span style="color:#79B8FF">3</span><span style="color:#E1E4E8"> },</span></span>
1056
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">progress</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> (</span></span>
1057
+ <span class="line"><span style="color:#F97583"> &lt;</span><span style="color:#E1E4E8">div style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex items-center justify-center w-full h-full bg-black&#39;</span><span style="color:#E1E4E8">)}</span><span style="color:#F97583">&gt;</span></span>
1058
+ <span class="line"><span style="color:#F97583"> &lt;</span><span style="color:#E1E4E8">h1 style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{{ </span><span style="color:#F97583">...</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-8xl font-bold text-white&#39;</span><span style="color:#E1E4E8">), </span><span style="color:#B392F0">opacity</span><span style="color:#E1E4E8">: progress }}</span><span style="color:#F97583">&gt;</span></span>
1059
+ <span class="line"><span style="color:#E1E4E8"> {</span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8">}</span></span>
1060
+ <span class="line"><span style="color:#F97583"> &lt;/</span><span style="color:#E1E4E8">h1</span><span style="color:#F97583">&gt;</span></span>
1061
+ <span class="line"><span style="color:#F97583"> &lt;/</span><span style="color:#E1E4E8">div</span><span style="color:#F97583">&gt;</span></span>
1062
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1063
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1064
+ <span class="line"></span>
1065
+ <span class="line"><span style="color:#6A737D">// Pre-render all frames</span></span>
1066
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> frames</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> previewVideo</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello World&#39;</span><span style="color:#E1E4E8"> });</span></span>
1067
+ <span class="line"></span></code></pre>
1068
+ <p><strong>Returns:</strong></p>
1069
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
1070
+ <span class="line"><span style="color:#B392F0"> frames</span><span style="color:#E1E4E8">: string[], </span><span style="color:#6A737D">// Array of SVG strings or data URLs</span></span>
1071
+ <span class="line"><span style="color:#B392F0"> meta</span><span style="color:#E1E4E8">: {</span></span>
1072
+ <span class="line"><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">: string,</span></span>
1073
+ <span class="line"><span style="color:#B392F0"> width</span><span style="color:#E1E4E8">: number,</span></span>
1074
+ <span class="line"><span style="color:#B392F0"> height</span><span style="color:#E1E4E8">: number,</span></span>
1075
+ <span class="line"><span style="color:#B392F0"> fps</span><span style="color:#E1E4E8">: number,</span></span>
1076
+ <span class="line"><span style="color:#B392F0"> duration</span><span style="color:#E1E4E8">: number,</span></span>
1077
+ <span class="line"><span style="color:#B392F0"> totalFrames</span><span style="color:#E1E4E8">: number</span></span>
1078
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1079
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1080
+ <span class="line"></span></code></pre>
1081
+ <p><strong>Options:</strong></p>
1082
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> frames</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> previewVideo</span><span style="color:#E1E4E8">(template, props, {</span></span>
1083
+ <span class="line"><span style="color:#E1E4E8"> format: </span><span style="color:#9ECBFF">&#39;svg&#39;</span><span style="color:#E1E4E8">, </span><span style="color:#6A737D">// &#39;svg&#39; (default) or &#39;data-url&#39;</span></span>
1084
+ <span class="line"><span style="color:#E1E4E8"> config: { </span><span style="color:#6A737D">// Optional: Override template config</span></span>
1085
+ <span class="line"><span style="color:#E1E4E8"> colors: {</span></span>
1086
+ <span class="line"><span style="color:#E1E4E8"> primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span></span>
1087
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1088
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1089
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1090
+ <span class="line"></span></code></pre>
1091
+ <h3 id="videopreview-component">&lt;VideoPreview&gt; Component</h3>
1092
+ <p>Browser-safe React component for displaying pre-rendered frames:</p>
1093
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { VideoPreview, </span><span style="color:#F97583">type</span><span style="color:#E1E4E8"> PreviewFrames } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
1094
+ <span class="line"></span>
1095
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> PreviewPage</span><span style="color:#E1E4E8">({ </span><span style="color:#FFAB70">frames</span><span style="color:#E1E4E8"> }</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> { </span><span style="color:#FFAB70">frames</span><span style="color:#F97583">:</span><span style="color:#B392F0"> PreviewFrames</span><span style="color:#E1E4E8"> }) {</span></span>
1096
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> (</span></span>
1097
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#79B8FF">VideoPreview</span></span>
1098
+ <span class="line"><span style="color:#B392F0"> frames</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{frames}</span></span>
1099
+ <span class="line"><span style="color:#B392F0"> autoPlay</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">}</span></span>
1100
+ <span class="line"><span style="color:#B392F0"> loop</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">}</span></span>
1101
+ <span class="line"><span style="color:#B392F0"> controls</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">}</span></span>
1102
+ <span class="line"><span style="color:#B392F0"> onFrameChange</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{(</span><span style="color:#FFAB70">frame</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">`Frame: ${</span><span style="color:#E1E4E8">frame</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8">)}</span></span>
1103
+ <span class="line"><span style="color:#E1E4E8"> /&gt;</span></span>
1104
+ <span class="line"><span style="color:#E1E4E8"> );</span></span>
1105
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1106
+ <span class="line"></span></code></pre>
1107
+ <p><strong>Props:</strong></p>
1108
+ <ul>
1109
+ <li><code>frames</code> - Pre-rendered frames from <code>previewVideo()</code></li>
1110
+ <li><code>autoPlay</code> - Auto-play on mount (default: <code>true</code>)</li>
1111
+ <li><code>loop</code> - Loop the video (default: <code>true</code>)</li>
1112
+ <li><code>controls</code> - Show playback controls (default: <code>true</code>)</li>
1113
+ <li><code>className</code> - CSS class for styling</li>
1114
+ <li><code>style</code> - Inline styles</li>
1115
+ <li><code>onFrameChange</code> - Callback when frame changes</li>
1116
+ </ul>
1117
+ <h3 id="nextjs-example">Next.js Example</h3>
1118
+ <p><strong>Pre-render at build time:</strong></p>
1119
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#6A737D">// pages/templates/[id].tsx</span></span>
1120
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { previewVideo, VideoPreview, </span><span style="color:#F97583">type</span><span style="color:#E1E4E8"> PreviewFrames } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
1121
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { templates } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;@/lib/templates&#39;</span><span style="color:#E1E4E8">;</span></span>
1122
+ <span class="line"></span>
1123
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> getStaticProps</span><span style="color:#E1E4E8">({ </span><span style="color:#FFAB70">params</span><span style="color:#E1E4E8"> }) {</span></span>
1124
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> templates[params.id];</span></span>
1125
+ <span class="line"></span>
1126
+ <span class="line"><span style="color:#6A737D"> // Pre-render all frames on server</span></span>
1127
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> frames</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> previewVideo</span><span style="color:#E1E4E8">(template, {</span></span>
1128
+ <span class="line"><span style="color:#E1E4E8"> title: </span><span style="color:#9ECBFF">&#39;Preview&#39;</span></span>
1129
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1130
+ <span class="line"></span>
1131
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> {</span></span>
1132
+ <span class="line"><span style="color:#E1E4E8"> props: { frames } </span><span style="color:#6A737D">// Pass serialized frames to client</span></span>
1133
+ <span class="line"><span style="color:#E1E4E8"> };</span></span>
1134
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1135
+ <span class="line"></span>
1136
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> TemplatePage</span><span style="color:#E1E4E8">({ </span><span style="color:#FFAB70">frames</span><span style="color:#E1E4E8"> }</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> { </span><span style="color:#FFAB70">frames</span><span style="color:#F97583">:</span><span style="color:#B392F0"> PreviewFrames</span><span style="color:#E1E4E8"> }) {</span></span>
1137
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> (</span></span>
1138
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#B392F0"> className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;container mx-auto py-8&quot;</span><span style="color:#E1E4E8">&gt;</span></span>
1139
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">h1</span><span style="color:#B392F0"> className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;text-3xl font-bold mb-4&quot;</span><span style="color:#E1E4E8">&gt;{frames.meta.name}&lt;/</span><span style="color:#85E89D">h1</span><span style="color:#E1E4E8">&gt;</span></span>
1140
+ <span class="line"></span>
1141
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#79B8FF">VideoPreview</span></span>
1142
+ <span class="line"><span style="color:#B392F0"> frames</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{frames}</span></span>
1143
+ <span class="line"><span style="color:#B392F0"> autoPlay</span></span>
1144
+ <span class="line"><span style="color:#B392F0"> loop</span></span>
1145
+ <span class="line"><span style="color:#B392F0"> controls</span></span>
1146
+ <span class="line"><span style="color:#E1E4E8"> /&gt;</span></span>
1147
+ <span class="line"></span>
1148
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#B392F0"> className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;mt-4 text-sm text-gray-600&quot;</span><span style="color:#E1E4E8">&gt;</span></span>
1149
+ <span class="line"><span style="color:#E1E4E8"> {frames.meta.width}×{frames.meta.height} • {frames.meta.fps}fps • {frames.meta.duration}s</span></span>
1150
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1151
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1152
+ <span class="line"><span style="color:#E1E4E8"> );</span></span>
1153
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1154
+ <span class="line"></span></code></pre>
1155
+ <p><strong>On-demand with API route:</strong></p>
1156
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#6A737D">// pages/api/preview/[template].ts</span></span>
1157
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { previewVideo } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
1158
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { templates } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;@/lib/templates&#39;</span><span style="color:#E1E4E8">;</span></span>
1159
+ <span class="line"></span>
1160
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> async</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> handler</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">req</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">res</span><span style="color:#E1E4E8">) {</span></span>
1161
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> { </span><span style="color:#FFAB70">template</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">templateId</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> req.query;</span></span>
1162
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> props</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> req.body;</span></span>
1163
+ <span class="line"></span>
1164
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> templates[templateId];</span></span>
1165
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">template) {</span></span>
1166
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> res.</span><span style="color:#B392F0">status</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">404</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">({ error: </span><span style="color:#9ECBFF">&#39;Template not found&#39;</span><span style="color:#E1E4E8"> });</span></span>
1167
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1168
+ <span class="line"></span>
1169
+ <span class="line"><span style="color:#6A737D"> // Generate preview frames on-demand</span></span>
1170
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> frames</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> previewVideo</span><span style="color:#E1E4E8">(template, props, {</span></span>
1171
+ <span class="line"><span style="color:#E1E4E8"> format: </span><span style="color:#9ECBFF">&#39;data-url&#39;</span><span style="color:#6A737D"> // Easier JSON serialization</span></span>
1172
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1173
+ <span class="line"></span>
1174
+ <span class="line"><span style="color:#E1E4E8"> res.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">(frames);</span></span>
1175
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1176
+ <span class="line"></span>
1177
+ <span class="line"><span style="color:#6A737D">// pages/preview.tsx</span></span>
1178
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { useState, useEffect } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;react&#39;</span><span style="color:#E1E4E8">;</span></span>
1179
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { VideoPreview, </span><span style="color:#F97583">type</span><span style="color:#E1E4E8"> PreviewFrames } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
1180
+ <span class="line"></span>
1181
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> PreviewPage</span><span style="color:#E1E4E8">() {</span></span>
1182
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">frames</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setFrames</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">&lt;</span><span style="color:#B392F0">PreviewFrames</span><span style="color:#F97583"> |</span><span style="color:#79B8FF"> null</span><span style="color:#E1E4E8">&gt;(</span><span style="color:#79B8FF">null</span><span style="color:#E1E4E8">);</span></span>
1183
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">loading</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setLoading</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">);</span></span>
1184
+ <span class="line"></span>
1185
+ <span class="line"><span style="color:#B392F0"> useEffect</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> {</span></span>
1186
+ <span class="line"><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;/api/preview/intro&#39;</span><span style="color:#E1E4E8">, {</span></span>
1187
+ <span class="line"><span style="color:#E1E4E8"> method: </span><span style="color:#9ECBFF">&#39;POST&#39;</span><span style="color:#E1E4E8">,</span></span>
1188
+ <span class="line"><span style="color:#E1E4E8"> body: </span><span style="color:#79B8FF">JSON</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">stringify</span><span style="color:#E1E4E8">({ title: </span><span style="color:#9ECBFF">&#39;Dynamic Preview&#39;</span><span style="color:#E1E4E8"> })</span></span>
1189
+ <span class="line"><span style="color:#E1E4E8"> })</span></span>
1190
+ <span class="line"><span style="color:#E1E4E8"> .</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">res</span><span style="color:#F97583"> =&gt;</span><span style="color:#E1E4E8"> res.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">())</span></span>
1191
+ <span class="line"><span style="color:#E1E4E8"> .</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(setFrames)</span></span>
1192
+ <span class="line"><span style="color:#E1E4E8"> .</span><span style="color:#B392F0">finally</span><span style="color:#E1E4E8">(() </span><span style="color:#F97583">=&gt;</span><span style="color:#B392F0"> setLoading</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">false</span><span style="color:#E1E4E8">));</span></span>
1193
+ <span class="line"><span style="color:#E1E4E8"> }, []);</span></span>
1194
+ <span class="line"></span>
1195
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (loading) </span><span style="color:#F97583">return</span><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;Loading preview...&lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;;</span></span>
1196
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">frames) </span><span style="color:#F97583">return</span><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;Failed to load&lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;;</span></span>
1197
+ <span class="line"></span>
1198
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> &lt;</span><span style="color:#79B8FF">VideoPreview</span><span style="color:#B392F0"> frames</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{frames} </span><span style="color:#B392F0">autoPlay</span><span style="color:#B392F0"> loop</span><span style="color:#E1E4E8"> /&gt;;</span></span>
1199
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1200
+ <span class="line"></span></code></pre>
1201
+ <h3 id="template-editor-example">Template Editor Example</h3>
1202
+ <p>Build a visual template editor with live preview:</p>
1203
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { useState } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;react&#39;</span><span style="color:#E1E4E8">;</span></span>
1204
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { defineTemplate, previewVideo, VideoPreview } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk&#39;</span><span style="color:#E1E4E8">;</span></span>
1205
+ <span class="line"></span>
1206
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> TemplateEditor</span><span style="color:#E1E4E8">() {</span></span>
1207
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">frames</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setFrames</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">null</span><span style="color:#E1E4E8">);</span></span>
1208
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#E1E4E8"> [</span><span style="color:#79B8FF">title</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">setTitle</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#B392F0"> useState</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;Hello World&#39;</span><span style="color:#E1E4E8">);</span></span>
1209
+ <span class="line"></span>
1210
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#B392F0"> handlePreview</span><span style="color:#F97583"> =</span><span style="color:#F97583"> async</span><span style="color:#E1E4E8"> () </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> {</span></span>
1211
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1212
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;preview&#39;</span><span style="color:#E1E4E8">,</span></span>
1213
+ <span class="line"><span style="color:#E1E4E8"> type: </span><span style="color:#9ECBFF">&#39;video&#39;</span><span style="color:#E1E4E8">,</span></span>
1214
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1920</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">1080</span><span style="color:#E1E4E8"> },</span></span>
1215
+ <span class="line"><span style="color:#E1E4E8"> video: { fps: </span><span style="color:#79B8FF">30</span><span style="color:#E1E4E8">, duration: </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8"> },</span></span>
1216
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">progress</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> (</span></span>
1217
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#B392F0"> style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex items-center justify-center w-full h-full bg-gradient-to-r from-blue-500 to-purple-600&#39;</span><span style="color:#E1E4E8">)}&gt;</span></span>
1218
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">h1</span><span style="color:#B392F0"> style</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{{ </span><span style="color:#F97583">...</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-8xl font-bold text-white&#39;</span><span style="color:#E1E4E8">), opacity: progress }}&gt;</span></span>
1219
+ <span class="line"><span style="color:#E1E4E8"> {title}</span></span>
1220
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">h1</span><span style="color:#E1E4E8">&gt;</span></span>
1221
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1222
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1223
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1224
+ <span class="line"></span>
1225
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> frames</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> previewVideo</span><span style="color:#E1E4E8">(template, { title });</span></span>
1226
+ <span class="line"><span style="color:#B392F0"> setFrames</span><span style="color:#E1E4E8">(frames);</span></span>
1227
+ <span class="line"><span style="color:#E1E4E8"> };</span></span>
1228
+ <span class="line"></span>
1229
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> (</span></span>
1230
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#B392F0"> className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;grid grid-cols-2 gap-4&quot;</span><span style="color:#E1E4E8">&gt;</span></span>
1231
+ <span class="line"><span style="color:#E1E4E8"> {</span><span style="color:#6A737D">/* Editor */</span><span style="color:#E1E4E8">}</span></span>
1232
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1233
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">input</span></span>
1234
+ <span class="line"><span style="color:#B392F0"> type</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;text&quot;</span></span>
1235
+ <span class="line"><span style="color:#B392F0"> value</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{title}</span></span>
1236
+ <span class="line"><span style="color:#B392F0"> onChange</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{(</span><span style="color:#FFAB70">e</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=&gt;</span><span style="color:#B392F0"> setTitle</span><span style="color:#E1E4E8">(e.target.value)}</span></span>
1237
+ <span class="line"><span style="color:#B392F0"> className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;border p-2 w-full mb-4&quot;</span></span>
1238
+ <span class="line"><span style="color:#E1E4E8"> /&gt;</span></span>
1239
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">button</span><span style="color:#B392F0"> onClick</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{handlePreview} </span><span style="color:#B392F0">className</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">&quot;bg-blue-500 text-white px-4 py-2&quot;</span><span style="color:#E1E4E8">&gt;</span></span>
1240
+ <span class="line"><span style="color:#E1E4E8"> Update Preview</span></span>
1241
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">button</span><span style="color:#E1E4E8">&gt;</span></span>
1242
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1243
+ <span class="line"></span>
1244
+ <span class="line"><span style="color:#E1E4E8"> {</span><span style="color:#6A737D">/* Preview */</span><span style="color:#E1E4E8">}</span></span>
1245
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1246
+ <span class="line"><span style="color:#E1E4E8"> {frames </span><span style="color:#F97583">?</span><span style="color:#E1E4E8"> (</span></span>
1247
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#79B8FF">VideoPreview</span><span style="color:#B392F0"> frames</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{frames} </span><span style="color:#B392F0">autoPlay</span><span style="color:#B392F0"> loop</span><span style="color:#E1E4E8"> /&gt;</span></span>
1248
+ <span class="line"><span style="color:#E1E4E8"> ) </span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> (</span></span>
1249
+ <span class="line"><span style="color:#E1E4E8"> &lt;</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;Click &quot;Update Preview&quot; to see your template&lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1250
+ <span class="line"><span style="color:#E1E4E8"> )}</span></span>
1251
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1252
+ <span class="line"><span style="color:#E1E4E8"> &lt;/</span><span style="color:#85E89D">div</span><span style="color:#E1E4E8">&gt;</span></span>
1253
+ <span class="line"><span style="color:#E1E4E8"> );</span></span>
1254
+ <span class="line"><span style="color:#E1E4E8">}</span></span>
1255
+ <span class="line"></span></code></pre>
1256
+ <h3 id="benefits">Benefits</h3>
1257
+ <ul>
1258
+ <li>✅ <strong>Accurate</strong> - Preview matches final video exactly (same renderer)</li>
1259
+ <li>✅ <strong>Fast</strong> - Pre-rendered frames load instantly</li>
1260
+ <li>✅ <strong>Interactive</strong> - Scrub, play, pause, step through frames</li>
1261
+ <li>✅ <strong>Browser-safe</strong> - No Node.js dependencies in React component</li>
1262
+ <li>✅ <strong>Flexible</strong> - Server-side or build-time rendering</li>
1263
+ <li>✅ <strong>Type-safe</strong> - Full TypeScript support</li>
1264
+ </ul>
1265
+ <h3 id="use-cases-1">Use Cases</h3>
1266
+ <ul>
1267
+ <li><strong>Template editors</strong> - Live preview while editing</li>
1268
+ <li><strong>Documentation</strong> - Show template examples</li>
1269
+ <li><strong>Admin panels</strong> - Preview before rendering final video</li>
1270
+ <li><strong>Marketing sites</strong> - Showcase video templates</li>
1271
+ <li><strong>Template galleries</strong> - Browse available templates</li>
1272
+ </ul>
1273
+ <h2 id="cloudflare-workers">Cloudflare Workers</h2>
1274
+ <p>Use loopwind in Cloudflare Workers and other edge runtimes with the dedicated workers SDK.</p>
1275
+ <h3 id="installation-1">Installation</h3>
1276
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> loopwind</span><span style="color:#9ECBFF"> @resvg/resvg-wasm</span></span>
1277
+ <span class="line"></span></code></pre>
1278
+ <h3 id="quick-start-1">Quick Start</h3>
1279
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { renderImage, renderSVG, defineTemplate, initWasm } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk/workers&#39;</span><span style="color:#E1E4E8">;</span></span>
1280
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> React </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;react&#39;</span><span style="color:#E1E4E8">;</span></span>
1281
+ <span class="line"></span>
1282
+ <span class="line"><span style="color:#6A737D">// Define a template using React.createElement</span></span>
1283
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1284
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;og-image&#39;</span><span style="color:#E1E4E8">,</span></span>
1285
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
1286
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">subtitle</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span></span>
1287
+ <span class="line"><span style="color:#9ECBFF"> &#39;div&#39;</span><span style="color:#E1E4E8">,</span></span>
1288
+ <span class="line"><span style="color:#E1E4E8"> { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-blue-500 to-purple-600 p-12&#39;</span><span style="color:#E1E4E8">) },</span></span>
1289
+ <span class="line"><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;h1&#39;</span><span style="color:#E1E4E8">, { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-7xl font-bold text-white text-center&#39;</span><span style="color:#E1E4E8">) }, title),</span></span>
1290
+ <span class="line"><span style="color:#E1E4E8"> subtitle </span><span style="color:#F97583">&amp;&amp;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;p&#39;</span><span style="color:#E1E4E8">, { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-3xl text-white mt-6&#39;</span><span style="color:#E1E4E8">) }, subtitle)</span></span>
1291
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1292
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1293
+ <span class="line"></span>
1294
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#E1E4E8"> {</span></span>
1295
+ <span class="line"><span style="color:#F97583"> async</span><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">request</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Request</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Promise</span><span style="color:#E1E4E8">&lt;</span><span style="color:#B392F0">Response</span><span style="color:#E1E4E8">&gt; {</span></span>
1296
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> url</span><span style="color:#F97583"> =</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> URL</span><span style="color:#E1E4E8">(request.url);</span></span>
1297
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> title</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> url.searchParams.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;title&#39;</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">||</span><span style="color:#9ECBFF"> &#39;Hello World&#39;</span><span style="color:#E1E4E8">;</span></span>
1298
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> format</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> url.searchParams.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;format&#39;</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">||</span><span style="color:#9ECBFF"> &#39;svg&#39;</span><span style="color:#E1E4E8">;</span></span>
1299
+ <span class="line"></span>
1300
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (format </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> &#39;png&#39;</span><span style="color:#E1E4E8">) {</span></span>
1301
+ <span class="line"><span style="color:#6A737D"> // Initialize WASM once (required for PNG)</span></span>
1302
+ <span class="line"><span style="color:#F97583"> await</span><span style="color:#B392F0"> initWasm</span><span style="color:#E1E4E8">();</span></span>
1303
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, { title }, { format: </span><span style="color:#9ECBFF">&#39;png&#39;</span><span style="color:#E1E4E8"> });</span></span>
1304
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Response</span><span style="color:#E1E4E8">(png, {</span></span>
1305
+ <span class="line"><span style="color:#E1E4E8"> headers: { </span><span style="color:#9ECBFF">&#39;Content-Type&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;image/png&#39;</span><span style="color:#E1E4E8"> }</span></span>
1306
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1307
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1308
+ <span class="line"></span>
1309
+ <span class="line"><span style="color:#6A737D"> // SVG doesn&#39;t require WASM initialization</span></span>
1310
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> svg</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderSVG</span><span style="color:#E1E4E8">(template, { title });</span></span>
1311
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Response</span><span style="color:#E1E4E8">(svg, {</span></span>
1312
+ <span class="line"><span style="color:#E1E4E8"> headers: { </span><span style="color:#9ECBFF">&#39;Content-Type&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;image/svg+xml&#39;</span><span style="color:#E1E4E8"> }</span></span>
1313
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1314
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1315
+ <span class="line"><span style="color:#E1E4E8">};</span></span>
1316
+ <span class="line"></span></code></pre>
1317
+ <h3 id="key-differences-from-main-sdk">Key Differences from Main SDK</h3>
1318
+ <p>The workers SDK (<code>loopwind/sdk/workers</code>) is designed for edge runtimes:</p>
1319
+
1320
+
1321
+
1322
+
1323
+
1324
+
1325
+
1326
+
1327
+
1328
+
1329
+
1330
+
1331
+
1332
+
1333
+
1334
+
1335
+
1336
+
1337
+
1338
+
1339
+
1340
+
1341
+
1342
+
1343
+
1344
+
1345
+
1346
+
1347
+
1348
+
1349
+
1350
+
1351
+
1352
+
1353
+
1354
+
1355
+
1356
+
1357
+
1358
+
1359
+ <table><thead><tr><th>Feature</th><th>Main SDK (<code>loopwind/sdk</code>)</th><th>Workers SDK (<code>loopwind/sdk/workers</code>)</th></tr></thead><tbody><tr><td><strong>Runtime</strong></td><td>Node.js</td><td>Cloudflare Workers, Edge</td></tr><tr><td><strong>File System</strong></td><td>Uses temp files</td><td>All in-memory</td></tr><tr><td><strong>PNG Rendering</strong></td><td>@resvg/resvg-js (native)</td><td>@resvg/resvg-wasm</td></tr><tr><td><strong>Templates</strong></td><td>JSX or React.createElement</td><td>React.createElement only</td></tr><tr><td><strong>Fonts</strong></td><td>Local files or CDN</td><td>ArrayBuffer (from CDN, R2, or bundled)</td></tr><tr><td><strong>Video</strong></td><td>Full support</td><td>Not supported</td></tr></tbody></table>
1360
+ <h3 id="api-reference">API Reference</h3>
1361
+ <h4 id="initwasm">initWasm()</h4>
1362
+ <p>Initialize WASM modules. Required before rendering PNG images.</p>
1363
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// Initialize with default CDN</span></span>
1364
+ <span class="line"><span style="color:#F97583">await</span><span style="color:#B392F0"> initWasm</span><span style="color:#E1E4E8">();</span></span>
1365
+ <span class="line"></span>
1366
+ <span class="line"><span style="color:#6A737D">// Or provide your own WASM binary</span></span>
1367
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> wasmBinary</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;/resvg.wasm&#39;</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">r</span><span style="color:#F97583"> =&gt;</span><span style="color:#E1E4E8"> r.</span><span style="color:#B392F0">arrayBuffer</span><span style="color:#E1E4E8">());</span></span>
1368
+ <span class="line"><span style="color:#F97583">await</span><span style="color:#B392F0"> initWasm</span><span style="color:#E1E4E8">(wasmBinary);</span></span>
1369
+ <span class="line"></span></code></pre>
1370
+ <h4 id="rendersvg">renderSVG()</h4>
1371
+ <p>Render a template to SVG string. No WASM required.</p>
1372
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> svg</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderSVG</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello&#39;</span><span style="color:#E1E4E8"> });</span></span>
1373
+ <span class="line"><span style="color:#6A737D">// Returns: string (SVG markup)</span></span>
1374
+ <span class="line"></span></code></pre>
1375
+ <h4 id="renderimage-1">renderImage()</h4>
1376
+ <p>Render a template to PNG. Requires <code>initWasm()</code> first.</p>
1377
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">await</span><span style="color:#B392F0"> initWasm</span><span style="color:#E1E4E8">();</span></span>
1378
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello&#39;</span><span style="color:#E1E4E8"> }, {</span></span>
1379
+ <span class="line"><span style="color:#E1E4E8"> format: </span><span style="color:#9ECBFF">&#39;png&#39;</span><span style="color:#E1E4E8">,</span></span>
1380
+ <span class="line"><span style="color:#E1E4E8"> scale: </span><span style="color:#79B8FF">2</span><span style="color:#6A737D"> // 2x resolution (default: 2)</span></span>
1381
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1382
+ <span class="line"><span style="color:#6A737D">// Returns: Uint8Array (PNG data)</span></span>
1383
+ <span class="line"></span></code></pre>
1384
+ <h4 id="definetemplate-1">defineTemplate()</h4>
1385
+ <p>Define a template inline using React.createElement.</p>
1386
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1387
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;my-template&#39;</span><span style="color:#E1E4E8">,</span></span>
1388
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
1389
+ <span class="line"><span style="color:#E1E4E8"> config: {</span></span>
1390
+ <span class="line"><span style="color:#E1E4E8"> colors: {</span></span>
1391
+ <span class="line"><span style="color:#E1E4E8"> primary: </span><span style="color:#9ECBFF">&#39;#3b82f6&#39;</span><span style="color:#E1E4E8">,</span></span>
1392
+ <span class="line"><span style="color:#E1E4E8"> background: </span><span style="color:#9ECBFF">&#39;#ffffff&#39;</span></span>
1393
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1394
+ <span class="line"><span style="color:#E1E4E8"> },</span></span>
1395
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span></span>
1396
+ <span class="line"><span style="color:#9ECBFF"> &#39;div&#39;</span><span style="color:#E1E4E8">,</span></span>
1397
+ <span class="line"><span style="color:#E1E4E8"> { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex items-center justify-center w-full h-full bg-background&#39;</span><span style="color:#E1E4E8">) },</span></span>
1398
+ <span class="line"><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;h1&#39;</span><span style="color:#E1E4E8">, { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-6xl font-bold text-primary&#39;</span><span style="color:#E1E4E8">) }, title)</span></span>
1399
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1400
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1401
+ <span class="line"></span></code></pre>
1402
+ <h4 id="loadfont">loadFont()</h4>
1403
+ <p>Load a custom font from a URL. By default, the SDK fetches Inter fonts from CDN, but you can use your own fonts.</p>
1404
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { loadFont, renderImage } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk/workers&#39;</span><span style="color:#E1E4E8">;</span></span>
1405
+ <span class="line"></span>
1406
+ <span class="line"><span style="color:#6A737D">// Load custom fonts</span></span>
1407
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> fonts</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#79B8FF"> Promise</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">all</span><span style="color:#E1E4E8">([</span></span>
1408
+ <span class="line"><span style="color:#B392F0"> loadFont</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;https://your-cdn.com/fonts/custom-400.woff&#39;</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">&#39;CustomFont&#39;</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">400</span><span style="color:#E1E4E8">),</span></span>
1409
+ <span class="line"><span style="color:#B392F0"> loadFont</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;https://your-cdn.com/fonts/custom-700.woff&#39;</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">&#39;CustomFont&#39;</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">700</span><span style="color:#E1E4E8">),</span></span>
1410
+ <span class="line"><span style="color:#E1E4E8">]);</span></span>
1411
+ <span class="line"></span>
1412
+ <span class="line"><span style="color:#6A737D">// Use custom fonts when rendering</span></span>
1413
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello&#39;</span><span style="color:#E1E4E8"> }, { fonts });</span></span>
1414
+ <span class="line"></span></code></pre>
1415
+ <p>You can also load fonts from Cloudflare R2 or KV storage:</p>
1416
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// From R2 bucket</span></span>
1417
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> fontData</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#E1E4E8"> env.</span><span style="color:#79B8FF">FONTS_BUCKET</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;my-font.woff&#39;</span><span style="color:#E1E4E8">);</span></span>
1418
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> fonts</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [{</span></span>
1419
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;MyFont&#39;</span><span style="color:#E1E4E8">,</span></span>
1420
+ <span class="line"><span style="color:#E1E4E8"> data: </span><span style="color:#F97583">await</span><span style="color:#E1E4E8"> fontData.</span><span style="color:#B392F0">arrayBuffer</span><span style="color:#E1E4E8">(),</span></span>
1421
+ <span class="line"><span style="color:#E1E4E8"> weight: </span><span style="color:#79B8FF">400</span><span style="color:#E1E4E8">,</span></span>
1422
+ <span class="line"><span style="color:#E1E4E8"> style: </span><span style="color:#9ECBFF">&#39;normal&#39;</span></span>
1423
+ <span class="line"><span style="color:#E1E4E8">}];</span></span>
1424
+ <span class="line"></span></code></pre>
1425
+ <h4 id="fetchimage">fetchImage()</h4>
1426
+ <p>Fetch an image from URL and convert to data URI for use in templates. Satori requires images as data URIs, so you need to pre-fetch them before rendering.</p>
1427
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { fetchImage, renderImage, defineTemplate } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk/workers&#39;</span><span style="color:#E1E4E8">;</span></span>
1428
+ <span class="line"></span>
1429
+ <span class="line"><span style="color:#6A737D">// Pre-fetch images before rendering</span></span>
1430
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> logo</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> fetchImage</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;https://example.com/logo.png&#39;</span><span style="color:#E1E4E8">);</span></span>
1431
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> background</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> fetchImage</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;https://example.com/bg.jpg&#39;</span><span style="color:#E1E4E8">);</span></span>
1432
+ <span class="line"></span>
1433
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1434
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;og-image&#39;</span><span style="color:#E1E4E8">,</span></span>
1435
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
1436
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">logo</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">background</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span></span>
1437
+ <span class="line"><span style="color:#9ECBFF"> &#39;div&#39;</span><span style="color:#E1E4E8">,</span></span>
1438
+ <span class="line"><span style="color:#E1E4E8"> { style: { </span><span style="color:#F97583">...</span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex items-center justify-center w-full h-full&#39;</span><span style="color:#E1E4E8">), backgroundImage: </span><span style="color:#9ECBFF">`url(${</span><span style="color:#E1E4E8">background</span><span style="color:#9ECBFF">})`</span><span style="color:#E1E4E8"> } },</span></span>
1439
+ <span class="line"><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;img&#39;</span><span style="color:#E1E4E8">, { src: logo, width: </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8"> })</span></span>
1440
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1441
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1442
+ <span class="line"></span>
1443
+ <span class="line"><span style="color:#6A737D">// Pass pre-fetched images as props</span></span>
1444
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, { logo, background });</span></span>
1445
+ <span class="line"></span></code></pre>
1446
+ <h3 id="wrangler-configuration">Wrangler Configuration</h3>
1447
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="toml"><code><span class="line"><span style="color:#6A737D"># wrangler.toml</span></span>
1448
+ <span class="line"><span style="color:#E1E4E8">name = </span><span style="color:#9ECBFF">&quot;my-og-image-worker&quot;</span></span>
1449
+ <span class="line"><span style="color:#E1E4E8">main = </span><span style="color:#9ECBFF">&quot;src/index.ts&quot;</span></span>
1450
+ <span class="line"><span style="color:#E1E4E8">compatibility_date = </span><span style="color:#9ECBFF">&quot;2024-12-01&quot;</span></span>
1451
+ <span class="line"></span>
1452
+ <span class="line"><span style="color:#6A737D"># Required for satori and resvg-wasm dependencies</span></span>
1453
+ <span class="line"><span style="color:#E1E4E8">compatibility_flags = [</span><span style="color:#9ECBFF">&quot;nodejs_compat_v2&quot;</span><span style="color:#E1E4E8">]</span></span>
1454
+ <span class="line"></span></code></pre>
1455
+ <h3 id="local-development-note">Local Development Note</h3>
1456
+ <p>Local development with <code>wrangler dev</code> may have WASM limitations due to the local workerd runtime. For full testing:</p>
1457
+ <ol>
1458
+ <li><strong>Use SVG format locally</strong>: <code>?format=svg</code> works without WASM</li>
1459
+ <li><strong>Test with Node.js</strong>: Create a test script to verify PNG rendering</li>
1460
+ <li><strong>Deploy to production</strong>: Full PNG support works on Cloudflare’s production runtime</li>
1461
+ </ol>
1462
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// test-sdk.mjs - Test locally with Node.js</span></span>
1463
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { renderImage, renderSVG, defineTemplate, initWasm } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk/workers&#39;</span><span style="color:#E1E4E8">;</span></span>
1464
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> React </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;react&#39;</span><span style="color:#E1E4E8">;</span></span>
1465
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> fs </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;fs/promises&#39;</span><span style="color:#E1E4E8">;</span></span>
1466
+ <span class="line"></span>
1467
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> template</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1468
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;test&#39;</span><span style="color:#E1E4E8">,</span></span>
1469
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
1470
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span></span>
1471
+ <span class="line"><span style="color:#9ECBFF"> &#39;div&#39;</span><span style="color:#E1E4E8">,</span></span>
1472
+ <span class="line"><span style="color:#E1E4E8"> { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex items-center justify-center w-full h-full bg-blue-500&#39;</span><span style="color:#E1E4E8">) },</span></span>
1473
+ <span class="line"><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;h1&#39;</span><span style="color:#E1E4E8">, { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-6xl font-bold text-white&#39;</span><span style="color:#E1E4E8">) }, title)</span></span>
1474
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1475
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1476
+ <span class="line"></span>
1477
+ <span class="line"><span style="color:#6A737D">// Test SVG</span></span>
1478
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> svg</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderSVG</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello&#39;</span><span style="color:#E1E4E8"> });</span></span>
1479
+ <span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;SVG length:&#39;</span><span style="color:#E1E4E8">, svg.</span><span style="color:#79B8FF">length</span><span style="color:#E1E4E8">);</span></span>
1480
+ <span class="line"></span>
1481
+ <span class="line"><span style="color:#6A737D">// Test PNG</span></span>
1482
+ <span class="line"><span style="color:#F97583">await</span><span style="color:#B392F0"> initWasm</span><span style="color:#E1E4E8">();</span></span>
1483
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello&#39;</span><span style="color:#E1E4E8"> });</span></span>
1484
+ <span class="line"><span style="color:#F97583">await</span><span style="color:#E1E4E8"> fs.</span><span style="color:#B392F0">writeFile</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;test.png&#39;</span><span style="color:#E1E4E8">, png);</span></span>
1485
+ <span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;PNG saved!&#39;</span><span style="color:#E1E4E8">);</span></span>
1486
+ <span class="line"></span></code></pre>
1487
+ <h3 id="custom-fonts">Custom Fonts</h3>
1488
+ <p>Fonts are fetched from CDN by default (Inter). You can provide custom fonts:</p>
1489
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> customFonts</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [</span></span>
1490
+ <span class="line"><span style="color:#E1E4E8"> {</span></span>
1491
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;Roboto&#39;</span><span style="color:#E1E4E8">,</span></span>
1492
+ <span class="line"><span style="color:#E1E4E8"> data: </span><span style="color:#F97583">await</span><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;https://example.com/Roboto-Regular.woff&#39;</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">r</span><span style="color:#F97583"> =&gt;</span><span style="color:#E1E4E8"> r.</span><span style="color:#B392F0">arrayBuffer</span><span style="color:#E1E4E8">()),</span></span>
1493
+ <span class="line"><span style="color:#E1E4E8"> weight: </span><span style="color:#79B8FF">400</span><span style="color:#E1E4E8">,</span></span>
1494
+ <span class="line"><span style="color:#E1E4E8"> style: </span><span style="color:#9ECBFF">&#39;normal&#39;</span><span style="color:#F97583"> as</span><span style="color:#F97583"> const</span></span>
1495
+ <span class="line"><span style="color:#E1E4E8"> },</span></span>
1496
+ <span class="line"><span style="color:#E1E4E8"> {</span></span>
1497
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;Roboto&#39;</span><span style="color:#E1E4E8">,</span></span>
1498
+ <span class="line"><span style="color:#E1E4E8"> data: </span><span style="color:#F97583">await</span><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;https://example.com/Roboto-Bold.woff&#39;</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">r</span><span style="color:#F97583"> =&gt;</span><span style="color:#E1E4E8"> r.</span><span style="color:#B392F0">arrayBuffer</span><span style="color:#E1E4E8">()),</span></span>
1499
+ <span class="line"><span style="color:#E1E4E8"> weight: </span><span style="color:#79B8FF">700</span><span style="color:#E1E4E8">,</span></span>
1500
+ <span class="line"><span style="color:#E1E4E8"> style: </span><span style="color:#9ECBFF">&#39;normal&#39;</span><span style="color:#F97583"> as</span><span style="color:#F97583"> const</span></span>
1501
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1502
+ <span class="line"><span style="color:#E1E4E8">];</span></span>
1503
+ <span class="line"></span>
1504
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(template, { title: </span><span style="color:#9ECBFF">&#39;Hello&#39;</span><span style="color:#E1E4E8"> }, {</span></span>
1505
+ <span class="line"><span style="color:#E1E4E8"> fonts: customFonts</span></span>
1506
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1507
+ <span class="line"></span></code></pre>
1508
+ <p><strong>Supported font formats:</strong> WOFF, TTF, OTF (not WOFF2)</p>
1509
+ <h3 id="full-cloudflare-worker-example">Full Cloudflare Worker Example</h3>
1510
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D">// src/index.ts</span></span>
1511
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { renderImage, renderSVG, defineTemplate, initWasm } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;loopwind/sdk/workers&#39;</span><span style="color:#E1E4E8">;</span></span>
1512
+ <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> React </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> &#39;react&#39;</span><span style="color:#E1E4E8">;</span></span>
1513
+ <span class="line"></span>
1514
+ <span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> wasmInitialized </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
1515
+ <span class="line"></span>
1516
+ <span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> ogTemplate</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> defineTemplate</span><span style="color:#E1E4E8">({</span></span>
1517
+ <span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">&#39;og-image&#39;</span><span style="color:#E1E4E8">,</span></span>
1518
+ <span class="line"><span style="color:#E1E4E8"> size: { width: </span><span style="color:#79B8FF">1200</span><span style="color:#E1E4E8">, height: </span><span style="color:#79B8FF">630</span><span style="color:#E1E4E8"> },</span></span>
1519
+ <span class="line"><span style="color:#B392F0"> render</span><span style="color:#E1E4E8">: ({ </span><span style="color:#FFAB70">tw</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">title</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">subtitle</span><span style="color:#E1E4E8"> }) </span><span style="color:#F97583">=&gt;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span></span>
1520
+ <span class="line"><span style="color:#9ECBFF"> &#39;div&#39;</span><span style="color:#E1E4E8">,</span></span>
1521
+ <span class="line"><span style="color:#E1E4E8"> { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 p-12&#39;</span><span style="color:#E1E4E8">) },</span></span>
1522
+ <span class="line"><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;h1&#39;</span><span style="color:#E1E4E8">, { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-7xl font-bold text-white text-center mb-4&#39;</span><span style="color:#E1E4E8">) }, title),</span></span>
1523
+ <span class="line"><span style="color:#E1E4E8"> subtitle </span><span style="color:#F97583">&amp;&amp;</span><span style="color:#E1E4E8"> React.</span><span style="color:#B392F0">createElement</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;p&#39;</span><span style="color:#E1E4E8">, { style: </span><span style="color:#B392F0">tw</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;text-3xl text-white/80&#39;</span><span style="color:#E1E4E8">) }, subtitle)</span></span>
1524
+ <span class="line"><span style="color:#E1E4E8"> )</span></span>
1525
+ <span class="line"><span style="color:#E1E4E8">});</span></span>
1526
+ <span class="line"></span>
1527
+ <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#E1E4E8"> {</span></span>
1528
+ <span class="line"><span style="color:#F97583"> async</span><span style="color:#B392F0"> fetch</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">request</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Request</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">env</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> any</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">ctx</span><span style="color:#F97583">:</span><span style="color:#B392F0"> ExecutionContext</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Promise</span><span style="color:#E1E4E8">&lt;</span><span style="color:#B392F0">Response</span><span style="color:#E1E4E8">&gt; {</span></span>
1529
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> url</span><span style="color:#F97583"> =</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> URL</span><span style="color:#E1E4E8">(request.url);</span></span>
1530
+ <span class="line"></span>
1531
+ <span class="line"><span style="color:#6A737D"> // Health check</span></span>
1532
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (url.pathname </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> &#39;/health&#39;</span><span style="color:#E1E4E8">) {</span></span>
1533
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> Response.</span><span style="color:#B392F0">json</span><span style="color:#E1E4E8">({ status: </span><span style="color:#9ECBFF">&#39;ok&#39;</span><span style="color:#E1E4E8">, wasmInitialized });</span></span>
1534
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1535
+ <span class="line"></span>
1536
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> title</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> url.searchParams.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;title&#39;</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">||</span><span style="color:#9ECBFF"> &#39;Hello from Loopwind!&#39;</span><span style="color:#E1E4E8">;</span></span>
1537
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> subtitle</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> url.searchParams.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;subtitle&#39;</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">||</span><span style="color:#9ECBFF"> &#39;&#39;</span><span style="color:#E1E4E8">;</span></span>
1538
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> format</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> url.searchParams.</span><span style="color:#B392F0">get</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">&#39;format&#39;</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">||</span><span style="color:#9ECBFF"> &#39;png&#39;</span><span style="color:#E1E4E8">;</span></span>
1539
+ <span class="line"></span>
1540
+ <span class="line"><span style="color:#F97583"> try</span><span style="color:#E1E4E8"> {</span></span>
1541
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (format </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> &#39;svg&#39;</span><span style="color:#E1E4E8">) {</span></span>
1542
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> svg</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderSVG</span><span style="color:#E1E4E8">(ogTemplate, { title, subtitle });</span></span>
1543
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Response</span><span style="color:#E1E4E8">(svg, {</span></span>
1544
+ <span class="line"><span style="color:#E1E4E8"> headers: {</span></span>
1545
+ <span class="line"><span style="color:#9ECBFF"> &#39;Content-Type&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;image/svg+xml&#39;</span><span style="color:#E1E4E8">,</span></span>
1546
+ <span class="line"><span style="color:#9ECBFF"> &#39;Cache-Control&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;public, max-age=3600&#39;</span></span>
1547
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1548
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1549
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1550
+ <span class="line"></span>
1551
+ <span class="line"><span style="color:#6A737D"> // PNG requires WASM</span></span>
1552
+ <span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">wasmInitialized) {</span></span>
1553
+ <span class="line"><span style="color:#F97583"> await</span><span style="color:#B392F0"> initWasm</span><span style="color:#E1E4E8">();</span></span>
1554
+ <span class="line"><span style="color:#E1E4E8"> wasmInitialized </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
1555
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1556
+ <span class="line"></span>
1557
+ <span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> png</span><span style="color:#F97583"> =</span><span style="color:#F97583"> await</span><span style="color:#B392F0"> renderImage</span><span style="color:#E1E4E8">(ogTemplate, { title, subtitle }, { format: </span><span style="color:#9ECBFF">&#39;png&#39;</span><span style="color:#E1E4E8">, scale: </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8"> });</span></span>
1558
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Response</span><span style="color:#E1E4E8">(png, {</span></span>
1559
+ <span class="line"><span style="color:#E1E4E8"> headers: {</span></span>
1560
+ <span class="line"><span style="color:#9ECBFF"> &#39;Content-Type&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;image/png&#39;</span><span style="color:#E1E4E8">,</span></span>
1561
+ <span class="line"><span style="color:#9ECBFF"> &#39;Cache-Control&#39;</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">&#39;public, max-age=3600&#39;</span></span>
1562
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1563
+ <span class="line"><span style="color:#E1E4E8"> });</span></span>
1564
+ <span class="line"><span style="color:#E1E4E8"> } </span><span style="color:#F97583">catch</span><span style="color:#E1E4E8"> (error) {</span></span>
1565
+ <span class="line"><span style="color:#F97583"> return</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Response</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">`Error: ${</span><span style="color:#E1E4E8">error</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8">, { status: </span><span style="color:#79B8FF">500</span><span style="color:#E1E4E8"> });</span></span>
1566
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1567
+ <span class="line"><span style="color:#E1E4E8"> }</span></span>
1568
+ <span class="line"><span style="color:#E1E4E8">};</span></span>
1569
+ <span class="line"></span></code></pre>
1570
+ <p><strong>Deploy:</strong></p>
1571
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">npx</span><span style="color:#9ECBFF"> wrangler</span><span style="color:#9ECBFF"> deploy</span></span>
1572
+ <span class="line"></span></code></pre>
1573
+ <p><strong>Test:</strong></p>
1574
+ <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>https://your-worker.workers.dev/?title=Hello&amp;format=png</span></span>
1575
+ <span class="line"><span>https://your-worker.workers.dev/?title=Hello&amp;format=svg</span></span>
1576
+ <span class="line"><span></span></span></code></pre>
1065
1577
  <h2 id="example-project">Example Project</h2>
1066
1578
  <p>See the full Next.js example at <a href="https://github.com/tomtev/loopwind/tree/main/examples/nextjs-api">examples/nextjs-api/</a></p>
1067
1579
  <pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> clone</span><span style="color:#9ECBFF"> https://github.com/tomtev/loopwind.git</span></span>