loopwind
tomtev/loopwindUse when working with .loopwind/ templates, loopwind CLI, OG images, social cards, or animated videos. Helps create, edit, and render image/video templates using Tailwind CSS + Satori.
SKILL.md
name: loopwind description: Use when working with .loopwind/ templates, loopwind CLI, OG images, social cards, or animated videos. Helps create, edit, and render image/video templates using Tailwind CSS + Satori. allowed-tools: Read, Write, Edit, Bash, Glob, Grep metadata: version: "0.25.11"
loopwind - Template-based image & video generation
Generate images (PNG, WebP, JPEG, SVG) and videos (MP4, GIF) from JSX templates using Tailwind CSS + Satori.
Installed templates
!npx loopwind list 2>/dev/null || echo "No templates found. Run: loopwind init && loopwind add <template>"
Commands
loopwind init # Create loopwind.json config
loopwind add <template> # Install template from registry
loopwind list # List installed templates
loopwind render <name> '{"prop":"value"}' # Render image
loopwind render <name> '{"prop":"value"}' --format gif # Render as GIF
loopwind validate [template] # Validate template metadata
Project structure
.loopwind/
├── loopwind.json # Config (colors, fonts, tokens)
├── my-template/
│ └── template.tsx # Image template
└── my-video/
└── template.tsx # Video template
Image template
export const meta = {
name: "my-template",
type: "image",
size: { width: 1200, height: 630, scale: 2 },
props: { title: "string", subtitle: "string?" }
};
export default function Template({ title, subtitle, tw }) {
return (
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-primary')}>
<h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
{subtitle && <p style={tw('text-2xl text-white/80 mt-4')}>{subtitle}</p>}
</div>
);
}
Video template
export const meta = {
name: "my-video",
type: "video",
size: { width: 1920, height: 1080 },
video: { fps: 30, duration: 3 },
props: { title: "string", subtitle: "string?" }
};
export default function Template({ title, subtitle, tw }) {
return (
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
<h1 style={tw('text-8xl font-bold text-white ease-out enter-bounce-in-up/0/600')}>
{title}
</h1>
{subtitle && (
<p style={tw('text-2xl text-white/80 mt-4 ease-out enter-fade-in-up/400/600')}>
{subtitle}
</p>
)}
</div>
);
}
Template helpers
All templates receive these props:
| Helper | Description |
|---|---|
tw(classes) |
Convert Tailwind classes to inline styles |
qr(text) |
Generate QR code as data URI |
image(propKey) |
Load image prop as data URI |
video(propKey) |
Load video frame synced to animation (video only) |
template(name, props) |
Embed another template |
config |
Access loopwind.json config |
frame |
Current frame number (video only) |
progress |
Animation progress 0-1 (video only) |
Helper examples
// tw() - Tailwind to inline styles
<div style={tw('flex items-center justify-center bg-primary text-white p-8')}>
// qr() - Generate QR codes
<img src={qr('https://example.com')} width={200} height={200} />
// image() - Embed images (prop value: "./bg.jpg")
<img src={image('background')} style={tw('w-full h-full')} />
// video() - Embed video frames (prop value: "./video.mp4")
<img src={video('background')} style={tw('absolute inset-0')} />
// template() - Compose templates
{template('banner-hero', { title: 'Nested', subtitle: 'Template' })}
Animation classes (video only)
Format: enter-{anim}/{startMs}/{durationMs}, exit-{anim}/{startMs}/{durationMs}, loop-{anim}/{durationMs}
Enter animations
<h1 style={tw('enter-fade-in/0/500')}>Fade in</h1>
<h1 style={tw('ease-out enter-bounce-in-up/0/600')}>Bounce in</h1>
<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 (staggered)</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
<h1 style={tw('exit-fade-out/2500/500')}>Fade out at 2.5s</h1>
<h1 style={tw('enter-fade-in/0/500 exit-fade-out/2500/500')}>Enter and exit</h1>
Same names as enter but with "out" suffix (fade-out, bounce-out-up, etc.)
Loop animations
<div style={tw('loop-fade/500')}>Pulsing</div>
<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
Utility-based animations
Animate any property directly:
<div style={tw('enter-translate-x-5/0/500')}>Slide 20px</div>
<div style={tw('enter-translate-y-full/0/800')}>Slide full height</div>
<div style={tw('enter-translate-y-[20px]/0/500')}>Exact 20px</div>
<div style={tw('enter-opacity-100/0/500')}>Fade in</div>
<div style={tw('enter-scale-150/0/800')}>Scale to 1.5x</div>
<div style={tw('enter-rotate-90/0/500')}>Rotate 90deg</div>
<div style={tw('enter--translate-x-5/0/500')}>Negative (slide left)</div>
Utilities: translate-x-{n}, translate-y-{n}, opacity-{n}, scale-{n}, rotate-{n}, skew-x-{n}, skew-y-{n}
Easing
<h1 style={tw('ease-out-cubic enter-bounce-in/0/500')}>With easing</h1>
<h1 style={tw('ease-spring enter-bounce-in/0/500')}>Spring physics</h1>
<h1 style={tw('ease-spring/1/170/8 enter-scale-in/0/800')}>Custom spring</h1>
Global: 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-type: enter-ease-*, exit-ease-*, loop-ease-*
Spring: ease-spring (default), ease-spring/mass/stiffness/damping
Satori limitations
Not supported: backdrop filters, 3D transforms, calc(), z-index
Workarounds: pre-calculate values instead of calc(), use DOM order instead of z-index
Supported: flexbox, colors, gradients, typography, spacing, borders, opacity, shadows, text-shadow, filters (blur, brightness, contrast, etc.)
Key rules
- Run
loopwind initfirst in new projects - Every
<div>with 2+ children MUST havedisplay: flex(usetw('flex ...')) - Use
tw()for all styling - it uses the project's Tailwind config - Image props auto-detected by file extension (.jpg, .png, etc.)
- Default output: current directory (
my-template.pngormy-video.mp4) - Templates use React/JSX but render to static images/videos via Satori