Loading...
Loading...
SuperImg programmatic video generation framework. Create HTML/CSS video templates with defineTemplate(), animate with ctx.std (tween, math, color), and render to MP4. Use when working with superimg templates or video rendering.
npx skill4agent add anaptfox/superimg superimgRenderContextsceneProgresssceneTimeSecondsctx.stdctx.datasuperimg renderrenderVideoloadTemplateimport { defineTemplate } from "superimg";
export default defineTemplate({
defaults: {
title: "Hello, SuperImg!",
subtitle: "Create stunning videos from code",
accentColor: "#667eea",
},
config: {
width: 1920,
height: 1080,
fps: 30,
durationSeconds: 4,
fonts: ["Space+Grotesk:wght@400;700"],
},
render(ctx) {
const { std, sceneTimeSeconds: time, width, height, isPortrait, data } = ctx;
const { title, subtitle, accentColor } = data;
// Responsive sizing
const titleSize = isPortrait ? 64 : 88;
const subtitleSize = isPortrait ? 22 : 28;
// Phase timing: Enter 0-1.5s | Hold 1.5-3s | Exit 3-4s
const enterProgress = std.math.clamp(time / 1.5, 0, 1);
const exitProgress = std.math.clamp((time - 3.0) / 1.0, 0, 1);
// Animate title
const titleOpacity = std.tween(0, 1, enterProgress, "easeOutCubic") * (1 - exitProgress);
const titleY = std.tween(40, 0, enterProgress, "easeOutCubic");
// Staggered subtitle (+0.3s delay)
const subtitleEnter = std.math.clamp((time - 0.3) / 1.5, 0, 1);
const subtitleOpacity = std.tween(0, 0.8, subtitleEnter, "easeOutCubic") * (1 - exitProgress);
const subtitleY = std.tween(30, 0, subtitleEnter, "easeOutCubic");
// Accent line
const lineEnter = std.math.clamp((time - 0.5) / 1.0, 0, 1);
const lineWidth = std.tween(0, 100, lineEnter, "easeOutCubic") * (1 - exitProgress);
const lineColor = std.color.alpha(accentColor, 0.8 * (1 - exitProgress));
return `
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: ${width}px;
height: ${height}px;
display: flex;
align-items: center;
justify-content: center;
background: #0f0f23;
font-family: system-ui, sans-serif;
overflow: hidden;
}
.title {
font-size: ${titleSize}px;
font-weight: 700;
color: ${accentColor};
opacity: ${titleOpacity};
transform: translateY(${titleY}px);
margin: 24px 0 12px;
}
.subtitle {
font-size: ${subtitleSize}px;
color: white;
opacity: ${subtitleOpacity};
transform: translateY(${subtitleY}px);
margin-bottom: 24px;
}
.accent-line {
width: ${lineWidth}%;
max-width: 500px;
height: 2px;
background: ${lineColor};
margin: 0 auto;
}
</style>
<div style="text-align: center">
<div class="accent-line"></div>
<h1 class="title">${title}</h1>
<p class="subtitle">${subtitle}</p>
<div class="accent-line"></div>
</div>
`;
},
});interface RenderContext<TData> {
std: Stdlib; // Tween, math, color utilities
// Scene timing (use these for animation)
sceneProgress: number; // 0-1 progress through current scene
sceneTimeSeconds: number; // Elapsed seconds in current scene
sceneDurationSeconds: number; // Total scene duration
// Canvas
width: number; // Canvas width in pixels
height: number; // Canvas height in pixels
isPortrait: boolean; // height > width
isLandscape: boolean; // width > height
// Data
data: TData; // Scene-specific data (merged with defaults)
// Global timing (rarely needed in templates)
globalProgress: number; // 0-1 progress through entire video
globalTimeSeconds: number;
fps: number;
}std.tween(from, to, progress) // Linear interpolation
std.tween(from, to, progress, "easeOutCubic") // Eased interpolation
std.tween(from, to, progress, { start, end, easing }) // Windowed animationstd.math.clamp(value, min, max) // Restrict to range
std.math.map(val, inMin, inMax, outMin, outMax) // Remap rangestd.color.alpha(color, opacity) // Add transparency: alpha('#F00', 0.5)
std.color.mix(color1, color2, t) // Blend two colors
std.color.lighten(color, amount) // Lighten by percentage
std.color.darken(color, amount) // Darken by percentage
std.color.hsl(h, s, l) // Create HSL stringsceneTimeSecondsclamprender(ctx) {
const { std, sceneTimeSeconds: time } = ctx;
// Enter: 0-1s | Hold: 1-3s | Exit: 3-4s
const enterProgress = std.math.clamp(time / 1.0, 0, 1);
const exitProgress = std.math.clamp((time - 3.0) / 1.0, 0, 1);
const eased = std.tween(0, 1, enterProgress, "easeOutCubic");
const opacity = eased * (1 - exitProgress);
const y = std.tween(50, 0, enterProgress, "easeOutCubic");
// ...
}isPortraitisLandscapeconst fontSize = ctx.isPortrait ? 48 : 72;
const layout = ctx.isPortrait ? "column" : "row";# Scaffold a new project (detects package manager automatically)
superimg init my-project
superimg init my-project --pm pnpm # override package manager
superimg init --add # Add videos/ to existing project
# Dev server with live preview (bare name resolves to videos/intro.ts)
superimg dev intro
# Render to video
superimg render videos/intro.ts -o output.mp4
# With options
superimg render videos/intro.ts -o output.mp4 --width 1080 --height 1920 --fps 60import { renderVideo, loadTemplate } from "superimg/server";
// Load template from file
const template = await loadTemplate("videos/intro.ts");
// Render to MP4
await renderVideo("videos/intro.ts", {
outputPath: "output.mp4",
width: 1920,
height: 1080,
});createRenderPlanexecuteRenderPlanPlaywrightEnginesuperimg/server${}rendersceneProgressglobalProgresssceneProgressglobalProgressbodydivwidth: ${width}px; height: ${height}px"superimg"defineTemplate"superimg/server"defaultsdata{ ...defaults, ...data }config.fontsconfig.fonts["Space+Grotesk:wght@400;700"]@import url(...)<link>