Skip to content

Create videos and images with React, Tailwind and Shadcn classes.

Notifications You must be signed in to change notification settings

tomtev/loopwind

Repository files navigation

loopwind

The Shadcn for design and marketing. A template-based CLI tool for generating images and videos using React + Tailwind CSS + Satori.

Features

  • Template-based: Install templates and get full control over code.
  • Fast Video Rendering: WASM-based MP4 encoding with optional FFmpeg for 2x speed.
  • Tailwind CSS & shadcn/ui styling: Style templates with Tailwind & shadcn/ui utility classes (text-foreground bg-primary/50)
  • Animate with classes: Tailwind-style animation classes for video templates. (enter-fade-in/0/500, exit-fade-out/2500/500, loop-ping/500)
  • Built-in Helpers: QR codes, image embedding, video backgrounds, template composition
  • Agent-friendly: File-based editing perfect for code agents

Quick Start

Installation

npm install -g loopwind

Or use with npx:

npx loopwind --help

Install a Template

1. Officials

loopwind add image-template
loopwind add video-template

Templates are installed to: .loopwind/<template>/.

Benefits:

  • Templates are local to your project (like npm packages)
  • Different projects can use different template versions
  • Version controlled with your project
  • Easy to share within your team
  • Works offline once installed

Render a Video

loopwind render my-video '{"title":"Hello World"}'

Videos are rendered to MP4 by default and saved to the current directory.

Example video template (.loopwind/my-video/template.tsx):

export const meta = {
  name: 'my-video',
  type: 'video',
  size: { width: 1920, height: 1080 },
  video: { fps: 30, duration: 3 },
  props: { title: 'string' }
};

export default function Template({ tw, title }) {
  return (
    <div style={tw('flex items-center justify-center w-full h-full bg-black')}>
      {/* Fade in from 0ms to 800ms */}
      <h1 style={tw('text-8xl font-bold text-white ease-out enter-fade-in/0/800')}>
        {title}
      </h1>
    </div>
  );
}

Render an Image

loopwind render banner-hero \
  --props '{"title":"Hello World","subtitle":"Welcome to loopwind"}'

By default, images are saved to the current directory.

You can specify a custom output path:

loopwind render banner-hero \
  --props '{"title":"Hello World","subtitle":"Welcome to loopwind"}' \
  --out custom/path/banner.png

Or use a props file:

# props.json
{
  "title": "Hello World",
  "subtitle": "Welcome to loopwind"
}

loopwind render banner-hero --props props.json --out banner.png

Output formats:

# PNG (default) - best for photos and complex gradients
loopwind render banner-hero --props props.json --format png -o banner.png

# SVG - scalable vector, smaller file size, perfect for template composition
loopwind render banner-hero --props props.json --format svg -o banner.svg

# WebP - modern format, smaller than PNG with similar quality
loopwind render banner-hero --props props.json --format webp -o banner.webp

# JPEG - smallest file size, good for photos
loopwind render banner-hero --props props.json --format jpg -o banner.jpg

SVG benefits with template composition:

  • Smaller file size (typically 3-4x smaller than PNG)
  • Infinitely scalable without quality loss
  • Embedded templates remain as native SVG elements
  • Can be edited in design tools (Figma, Illustrator)

List Installed Templates

loopwind list

Validate Templates

loopwind validate
loopwind validate banner-hero

shadcn/ui Design System

loopwind uses shadcn/ui's design system by default! All templates have access to semantic color tokens:

// Use shadcn colors in templates
<div style={tw('bg-card border rounded-xl p-16')}>
  <h1 style={tw('text-foreground font-bold')}>Title</h1>
  <p style={tw('text-muted-foreground')}>Subtitle</p>
</div>

Supported shadcn classes:

  • text-primary, bg-secondary, text-muted-foreground, bg-card, border, bg-destructive
  • Opacity modifiers: bg-primary/50, text-muted-foreground/75, border/30
  • Dark mode ready: Override colors in loopwind.json

See SHADCN_INTEGRATION.md for complete documentation.

Configuration (loopwind.json)

Templates automatically use your project's design tokens defined in .loopwind/loopwind.json. This allows generated images to match your brand colors and design system.

Initialize Config

loopwind init

This creates a .loopwind/loopwind.json file in your project:

{
  "colors": {
    "primary": "#667eea",
    "secondary": "#764ba2",
    "background": "#ffffff",
    "foreground": "#000000",
    "muted": "#f3f4f6",
    "accent": "#3b82f6",
    "destructive": "#ef4444",
    "border": "#e5e7eb"
  },
  "fonts": {
    "sans": ["Inter", "system-ui", "sans-serif"],
    "mono": ["JetBrains Mono", "monospace"]
  },
  "tokens": {
    "borderRadius": {
      "sm": "0.25rem",
      "md": "0.5rem",
      "lg": "1rem",
      "xl": "1.5rem"
    }
  },
  "defaults": {
    "width": 1200,
    "height": 630
  }
}

Using Config in Templates

Templates receive the config via props:

export default function MyTemplate({ title, config }) {
  const primaryColor = config?.colors?.primary || '#000';

  return (
    <div style={{ background: primaryColor }}>
      {title}
    </div>
  );
}

This ensures your generated images use the same colors as your Tailwind/Shadcn setup!

Using Tailwind Classes

Templates can use Tailwind utility classes for styling! The tw() function is automatically provided:

export default function MyTemplate({ title, tw }) {
  return (
    <div style={tw('flex items-center justify-center w-full h-full bg-primary text-white')}>
      <h1 style={tw('text-6xl font-bold')}>
        {title}
      </h1>
    </div>
  );
}

Combine with custom styles:

<div
  style={{
    ...tw('flex flex-col p-20 text-white'),
    background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
  }}
>
  <h1 style={tw('text-8xl font-bold')}>{title}</h1>
</div>

Automatic Tailwind Config Support

loopwind automatically detects and uses your tailwind.config.js!

If you have a Tailwind project:

// tailwind.config.js
export default {
  theme: {
    extend: {
      colors: {
        primary: '#667eea',
        brand: {
          500: '#0ea5e9',
          900: '#0c4a6e',
        },
      },
      spacing: {
        '72': '18rem',
      },
    },
  },
};

Your templates can use these values:

<div style={tw('bg-primary text-white p-72')}>
  <div style={tw('bg-brand-500')}>Custom brand color!</div>
</div>

Config priority:

  1. tailwind.config.js (if exists)
  2. loopwind.json (fallback)
  3. Default Tailwind values

Supported classes:

  • Layout: flex, flex-col, items-center, justify-between, etc.
  • Spacing: p-4, px-8, m-6, gap-4, etc.
  • Typography: text-6xl, font-bold, text-center, etc.
  • Colors: bg-primary, text-brand-500, etc. (from your config!)
  • Effects: opacity-90, rounded-xl, etc.
  • shadcn/ui colors: bg-card, text-foreground, text-muted-foreground
  • Opacity modifiers: bg-primary/50, text-muted-foreground/75

Note: Tailwind v4 uses CSS variables instead of JS config. We're working on CSS parsing support. For now, use loopwind.json or tailwind.config.js (v3 format).

Fonts

loopwind includes Inter (Regular 400, Bold 700) bundled with the CLI. Fonts work out of the box with no configuration required.

Default Fonts

By default, templates use the bundled Inter font. No setup required - works offline!

Custom Fonts

Configure custom fonts in loopwind.json:

{
  "fonts": {
    "sans": {
      "family": ["CustomFont", "sans-serif"],
      "files": [
        { "path": "fonts/CustomFont-Regular.woff", "weight": 400 },
        { "path": "fonts/CustomFont-Bold.woff", "weight": 700 }
      ]
    }
  }
}

Then use font classes in templates:

export default function Template({ title, tw }) {
  return (
    <h1 style={tw('font-sans font-bold text-6xl')}>
      {title}
    </h1>
  );
}

Font paths are relative to your loopwind.json location.

Available classes:

  • font-sans - Uses fonts.sans from loopwind.json
  • font-serif - Uses fonts.serif from loopwind.json
  • font-mono - Uses fonts.mono from loopwind.json

Supported formats: WOFF, WOFF2, TTF, OTF

Template Helpers

Templates have access to built-in helper functions:

  • tw(classes) - Convert Tailwind classes to inline styles
  • qr(text, options?) - Generate QR codes from text/URLs
  • image(propKey) - Embed images as data URIs
  • video(propKey) - Embed video frames (for video templates)
  • template(name, props) - Embed other templates
  • config - Access loopwind.json configuration

QR Code Helper

Generate QR codes from any text or URL using the qr() helper:

export default function Template({ title, url, qr, tw }) {
  return (
    <div style={tw('flex flex-col items-center p-16')}>
      <h1 style={tw('text-5xl font-bold mb-8')}>{title}</h1>

      {/* QR code auto-generated from url prop */}
      <img src={qr(url)} width={200} height={200} alt="QR Code" />

      <p style={tw('text-xl mt-4')}>{url}</p>
    </div>
  );
}

Usage:

loopwind render qr-card '{
  "title": "Visit Our Website",
  "url": "https://loopwind.dev"
}'

Customization:

// Custom size and error correction
<img src={qr(url, {
  width: 300,
  margin: 2,
  errorCorrectionLevel: 'H', // L, M, Q, H (higher = more error correction)
  color: {
    dark: '#000000',
    light: '#FFFFFF'
  }
})} />

How it works:

  • All string props are automatically pre-generated as QR codes
  • Call qr() with any prop value (url, text, etc.)
  • Returns a data URI that can be used directly in <img src={...} />
  • Cached per render for performance

Use cases:

  • Event tickets with QR codes
  • Business cards with contact info
  • Marketing materials with campaign URLs
  • Product labels with tracking codes

Image Helper

Embed images (jpg, png, gif, webp, svg) as data URIs using the image() helper:

export default function Banner({ background, tw, image }) {
  return (
    <div style={tw('relative w-full h-full')}>
      <img src={image('background')} style={tw('absolute inset-0 w-full h-full object-cover')} />
      <h1 style={tw('relative text-6xl font-bold text-white')}>Hello World</h1>
    </div>
  );
}

Props format:

{
  "background": "./path/to/image.jpg"
}

Usage:

loopwind render banner '{
  "background": "./my-background.jpg"
}'

How it works:

  • The renderer auto-detects props ending in image extensions (.jpg, .png, .gif, .webp, .svg)
  • Images are pre-loaded and converted to data URIs
  • Call image('propKey') to get the data URI for that prop
  • Works with any image format

Use cases:

  • Background images in banners
  • Logos and brand elements
  • Product photos in cards
  • Avatar images

Video Helper

For video templates, embed video frames synchronized to the current animation frame using the video() helper:

export default function VideoOverlay({ background, title, tw, video, frame, progress }) {
  const opacity = progress < 0.2 ? progress / 0.2 : 1;

  return (
    <div style={tw('relative w-full h-full')}>
      {/* Background video - auto-syncs to current frame */}
      <img
        src={video('background')}
        style={tw('absolute inset-0 w-full h-full object-cover')}
      />

      {/* Animated overlay */}
      <h1
        style={{
          ...tw('relative text-8xl font-bold text-white'),
          opacity
        }}
      >
        {title}
      </h1>
    </div>
  );
}

Props format:

{
  "title": "My Video",
  "background": "./my-video.mp4"
}

Usage:

loopwind render video-overlay props.json --out output.mp4

How it works:

  1. First render detects video props (auto-detects .mp4, .mov, etc.)
  2. Extracts all frames at template's FPS
  3. Returns frame matching current template frame number
  4. Frames cached in memory for fast access

Use cases:

  • Video backgrounds with text overlays
  • Adding titles/captions to existing videos
  • Combining multiple video sources
  • Dynamic watermarking

Template Composition Helper

Embed one template inside another using the template() helper. This is perfect for creating composite designs where smaller templates are reused as building blocks.

Just call template() - no declaration needed:

export default function Template({ title, bannerTitle, bannerSubtitle, template, tw }) {
  return (
    <div style={tw('w-full h-full flex flex-col bg-slate-900 p-12')}>
      <div style={tw('bg-white rounded-3xl p-10 flex flex-col')}>
        <h1 style={tw('text-5xl font-bold mb-8')}>{title}</h1>

        {/* Embed any template - auto-loaded on first render */}
        <div style={tw('rounded-xl overflow-hidden flex')}>
          {template('image', { title: bannerTitle, subtitle: bannerSubtitle })}
        </div>
      </div>
    </div>
  );
}

Usage:

loopwind render composite-card '{
  "title": "Product Launch",
  "bannerTitle": "New Release",
  "bannerSubtitle": "Coming Soon"
}'

How it works:

  • No declaration needed - just call template('any-template-name')
  • First render pass detects which templates are needed
  • All templates load in parallel automatically
  • Second render pass completes with loaded templates
  • Each template uses its own meta for sizing
  • Returns JSX directly (not a data URI)
  • Satori renders everything as a single SVG tree

Pass props directly:

{template('image', { title: 'Custom Title', subtitle: 'Custom Subtitle' })}
{template('qr-card', { url: 'https://example.com', title: 'Scan Me' })}

Use cases:

  • Composite social media graphics with reusable headers/footers
  • Multi-panel infographics
  • Email templates with consistent branding elements
  • Product cards with embedded badges or labels
  • Presentations with reusable slide components

Template Structure

Templates are TSX files with metadata. Each template can be either an image or a video template (not both).

Image Template

export const meta = {
  name: "banner-hero",
  description: "Hero banner with shadcn design",
  type: "image", // Optional, defaults to "image"
  size: { width: 1600, height: 900 },
  props: {
    title: "string",
    subtitle: "string?"
  }
};

export default function Template({ title, subtitle, tw, qr, template }) {
  return (
    <div style={tw('w-full h-full flex flex-col justify-center p-20 bg-background')}>
      <div style={tw('bg-card border rounded-xl p-16 flex flex-col')}>
        <h1 style={tw('text-8xl font-bold text-foreground')}>{title}</h1>
        {subtitle && (
          <p style={tw('text-4xl text-muted-foreground/75')}>{subtitle}</p>
        )}
      </div>
    </div>
  );
}

Video Template

export const meta = {
  name: "animated-banner",
  description: "Animated hero banner with shadcn design",
  type: "video",
  size: { width: 1600, height: 900 },
  props: {
    title: "string",
    subtitle: "string?"
  },
  video: {
    fps: 30,
    duration: 3
  }
};

export default function Template({ title, subtitle, tw }) {
  return (
    <div style={tw('w-full h-full flex flex-col justify-center items-center bg-background p-20')}>
      <div style={tw('bg-card border rounded-xl p-16 flex flex-col')}>
        {/* Bounce in from below: starts at 0ms, lasts 600ms */}
        <h1 style={tw('text-9xl font-bold text-foreground ease-out enter-bounce-in-up/0/600')}>
          {title}
        </h1>
        {subtitle && (
          <p style={tw('text-5xl text-muted-foreground/75 mt-6 ease-out enter-fade-in-up/300/600')}>
            {subtitle}
          </p>
        )}
      </div>
    </div>
  );
}

Animation Classes (Video Only)

Tailwind-style animation classes for video templates. No manual progress or frame calculations needed!

Format

enter-{animation}/{startMs}/{durationMs}
exit-{animation}/{startMs}/{durationMs}
loop-{animation}/{durationMs}
  • startMs - when animation begins (milliseconds)
  • durationMs - how long it lasts (milliseconds)

Utility-Based Animations

Animate any transform or opacity property directly using Tailwind-like utilities:

// Translate - numeric (4px per unit)
<div style={tw('enter-translate-x-5/0/500')}>Slide in 20px</div>
<div style={tw('loop-translate-y-10/1000')}>Oscillate 40px</div>

// Translate - keywords, fractions, arbitrary values
<div style={tw('enter-translate-y-full/0/800')}>Slide full height (100%)</div>
<div style={tw('enter-translate-x-1/2/0/600')}>Slide halfway (50%)</div>
<div style={tw('enter-translate-y-[20px]/0/500')}>Exact 20px</div>
<div style={tw('enter-translate-x-[5rem]/0/800')}>5rem (80px)</div>

// Opacity (0-100)
<div style={tw('enter-opacity-100/0/500')}>Fade to 100%</div>
<div style={tw('loop-opacity-50/1000')}>Pulse to 50%</div>

// Scale (0-200)
<div style={tw('enter-scale-100/0/500')}>Scale to 1.0x</div>
<div style={tw('enter-scale-150/0/800')}>Scale to 1.5x</div>

// Rotate (degrees)
<div style={tw('enter-rotate-90/0/500')}>Rotate 90°</div>
<div style={tw('loop-rotate-360/2000')}>Spin continuously</div>

// Skew (degrees)
<div style={tw('enter-skew-x-12/0/500')}>Skew horizontally</div>

// Negative values (prefix with -)
<div style={tw('enter--translate-x-5/0/500')}>Slide left</div>

// Combine multiple utilities
<div style={tw('enter-translate-y-10/0/500 enter-rotate-45/0/500')}>
  Slide and rotate together
</div>

Available utilities: translate-x-{n}, translate-y-{n}, opacity-{n}, scale-{n}, rotate-{n}, skew-x-{n}, skew-y-{n}

Enter Animations

// Fade in: starts at 0ms, lasts 500ms
<h1 style={tw('enter-fade-in/0/500')}>Hello</h1>

// Bounce in from below with easing
<h1 style={tw('ease-out enter-bounce-in-up/0/600')}>Title</h1>

// Staggered animations
<h1 style={tw('ease-out enter-fade-in-up/0/400')}>First</h1>
<p style={tw('ease-out enter-fade-in-up/200/400')}>Second</p>

Available: fade-in, fade-in-up, fade-in-down, fade-in-left, fade-in-right, slide-up, slide-down, slide-left, slide-right, bounce-in, bounce-in-up, bounce-in-down, bounce-in-left, bounce-in-right, scale-in, zoom-in, rotate-in, flip-in-x, flip-in-y

Exit Animations

// Fade out: starts at 2500ms, lasts 500ms
<h1 style={tw('exit-fade-out/2500/500')}>Goodbye</h1>

// Combined enter and exit
<h1 style={tw('enter-fade-in/0/500 exit-fade-out/2500/500')}>
  Hello and Goodbye
</h1>

Loop Animations

// Pulse opacity every 500ms
<div style={tw('loop-fade/500')}>Pulsing</div>

// Continuous rotation every 2000ms
<div style={tw('loop-spin/2000')}>Spinning</div>

Available: loop-fade, loop-bounce, loop-spin, loop-ping, loop-wiggle, loop-float, loop-pulse, loop-shake

Easing Functions

Add before animation class:

<h1 style={tw('ease-out-cubic enter-bounce-in/0/500')}>Dramatic</h1>

Available: linear, ease-in, ease-out, ease-in-out, ease-in-cubic, ease-out-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, ease-in-out-quart

Per-Animation-Type Easing

Apply different easing functions to enter, exit, and loop animations:

// Different easing for enter and exit
<h1 style={tw('enter-ease-out-cubic enter-fade-in/0/500 exit-ease-in exit-fade-out/2500/500')}>
  Smooth entrance, sharp exit
</h1>

// Loop with linear easing, enter with bounce
<div style={tw('enter-ease-out enter-bounce-in/0/400 loop-ease-linear loop-fade/1000')}>
  Bouncy entrance, linear loop
</div>

Available: enter-ease-*, exit-ease-*, loop-ease-* (works with all easing functions)

Spring Easing

Natural, physics-based bouncy animations:

// Default spring easing
<h1 style={tw('ease-spring enter-bounce-in/0/500')}>Bouncy spring!</h1>

// Custom spring: ease-spring/mass/stiffness/damping
<h1 style={tw('ease-spring/1/100/10 enter-scale-in/0/800')}>
  Custom spring (mass=1, stiffness=100, damping=10)
</h1>

// Per-animation-type spring
<div style={tw('enter-ease-spring enter-fade-in/0/500 exit-ease-out exit-fade-out/2500/500')}>
  Spring entrance, smooth exit
</div>

Common spring presets:

  • ease-spring/1/100/10 - Gentle bounce (default)
  • ease-spring/1/170/8 - Extra bouncy
  • ease-spring/1/200/15 - Snappy (no bounce)

Bracket Notation

For CSS-like syntax:

<h1 style={tw('enter-slide-up/[0.6s]/[1.5s]')}>Hello</h1>
<h1 style={tw('enter-fade-in/[300ms]/[800ms]')}>World</h1>

See the Animation Documentation for complete reference.

Commands

loopwind init

Initialize a .loopwind/loopwind.json config file with default design tokens.

loopwind add <template>

Install a template from the registry.

Options:

  • -r, --registry <url> - Custom registry URL

loopwind list

List all installed templates with metadata.

loopwind render <template>

Render a template to an image.

Options:

  • -p, --props <file> - Props file (JSON)
  • -o, --out <file> - Output file path
  • --format <format> - Output format: png, svg, webp (default: png)
  • --video - Render as video (coming soon)

loopwind validate [template]

Validate template metadata. If no template is specified, validates all installed templates.

Validation & Error Messages

loopwind automatically validates templates and props before rendering, providing helpful error messages.

Automatic Validation

# Missing required props
$ loopwind render banner-hero --props '{}'
✖ Template validation failed
  ✗ props: Missing required prop: "title"
    → Add "title" to your props: --props '{"title":"value"}'

# Wrong prop type
$ loopwind render banner-hero --props '{"title":123}'
✖ Template validation failed
  ✗ props: Prop "title" should be a string, got number
    → Change "title" to a string: "title": "value"

Enhanced Satori Error Messages

When rendering fails, loopwind provides clear suggestions:

$ loopwind render my-template --props props.json
✖ Failed to render template

Error: Satori requires explicit display: flex for containers with multiple children

Suggestions:
  • Add "flex" or "flex-col" to your Tailwind classes: tw("flex items-center")
  • Or add display: flex directly: style={{ display: "flex", ...tw("...") }}
  • Every <div> with 2+ children MUST have display: flex or display: none

See VALIDATION.md for complete validation documentation and troubleshooting.

Creating Custom Templates

Templates are stored in your project's .loopwind/ directory. Each template has its own folder with a template.tsx file.

  1. Create a folder:
mkdir -p .loopwind/my-template
  1. Create template.tsx with exported meta:
export const meta = {
  name: "my-template",
  description: "My custom template",
  type: "image",
  size: { width: 1200, height: 630 },
  props: { title: "string" }
};

export default function MyTemplate({ title, tw }) {
  return (
    <div style={tw('flex items-center justify-center w-full h-full bg-black text-white text-5xl')}>
      {title}
    </div>
  );
}
  1. Validate and render:
loopwind validate my-template
loopwind render my-template --props '{"title":"Hello"}' --out test.png

Use Cases

  • 🎯 OG Images: Generate Open Graph images for websites
  • 📱 Social Media: Create social media graphics
  • 📧 Email Headers: Generate email banners
  • 🎨 Marketing: Automate marketing asset creation
  • 🤖 AI Agents: Let LLMs generate images programmatically

Video Support

Video templates use type: "video" and support frame and progress props for animations.

Creating Video Templates

Video templates are similar to image templates but include frame and progress props for animations:

export const meta = {
  name: "my-video",
  type: "video",
  size: { width: 1200, height: 630 },
  props: { title: "string" },
  video: {
    fps: 30,
    duration: 3
  }
};

export default function Template({ title, frame, progress, tw, qr, template }) {
  return (
    <div
      style={{
        ...tw('w-full h-full flex items-center justify-center'),
        opacity: progress,
        transform: `translateY(${20 - progress * 20}px)`
      }}
    >
      <h1 style={tw('text-6xl font-bold')}>{title}</h1>
    </div>
  );
}

Rendering Videos

# Render to MP4 (default - WASM encoder, no dependencies)
loopwind render my-video '{"title":"Animated Title"}' -o output.mp4

# Render to GIF (great for emails, GitHub, Slack)
loopwind render my-video '{"title":"Animated Title"}' --format gif -o output.gif

# With custom quality settings
loopwind render my-video props.json --crf 18   # Higher quality (lower CRF = better quality)

# Fast encoding with FFmpeg (2x faster, requires FFmpeg installed)
loopwind render my-video props.json --ffmpeg

# Export frames only (for manual encoding)
loopwind render my-video props.json --frames-only -o frames/

Output formats:

  • MP4 - H.264 codec, smaller files, best quality (default)
  • GIF - Animated, works everywhere (emails, GitHub READMEs, Slack)

How it works:

  1. SVG Generation: All frames generated in parallel (~0.8s for 90 frames)
  2. H.264 Encoding: Frames encoded to MP4 (~1.2s for 90 frames with WASM)
  3. Total: 3-second video renders in ~2 seconds on M1 Mac

Encoding options:

  • Default (WASM): Pure JavaScript encoder, no dependencies needed
  • FFmpeg (--ffmpeg): 2x faster encoding, smaller files, requires FFmpeg installed

Props available in video templates:

  • frame - Current frame number (0 to totalFrames-1)
  • progress - Animation progress from 0 to 1
  • All standard helpers: tw, qr, template, config

Performance:

  • 12x faster than traditional approaches
  • 90 frames (3s @ 30fps) renders in ~2 seconds
  • ~60 frames per second rendering speed
  • Perfect for CI/CD and automated workflows

Documentation Website

A full documentation website is available in the website/ folder, built with Astro.

Local development:

cd website
npm install
npm run dev

Open http://localhost:4321

Deployment:

The site is ready to deploy on Netlify, Vercel, or Cloudflare Pages. A netlify.toml is included for easy Netlify deployment.

See website/README.md for more details.

Registry

Templates are fetched from a registry (similar to shadcn/ui). The default registry is:

https://loopwind.dev/r

You can host your own registry and use it with:

loopwind add my-template --registry https://my-registry.com

License

MIT

About

Create videos and images with React, Tailwind and Shadcn classes.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •