icons
A showcase of a project's icon set — an animated grid, as video or a still.
icons is a local generator — no url. Point it at a set of uniform icons and it lays them out
in a centred grid on a solid backdrop, animates them with a sequence of effect steps, and
captures the result as a looping video (mp4) or a single still (png). Use it to show off the
icon set a project ships.
{
name: "icon-set",
generator: "icons",
options: {
dir: "public/icons", // every image file in the folder, sorted by name
template: "showcase",
},
}You provide uniform, single-colour icons (SVG or PNG). By default each icon is tinted through a
CSS mask — its shape comes from the file's alpha and its colour from a fill you control — so
single-colour icons can be recoloured live (that's what the recolour interactions animate). Set
layout.recolor: false to render the icons natively (original colours); then only the scale /
opacity / rotate effects apply.
Interactions are one primitive
Every preset interaction is the same step, differing only in order and stagger:
- A step sweeps one effect (
scale,color,opacity,rotate,spin) across the grid. ordersets each icon's phase across the grid —forward,reverse,rows,columns,diagonal,radial-in/radial-out(a ripple from the centre),spiral, orrandom. Icons that share a phase (a whole diagonal, a ring) fire together, which is what makes a pattern.staggersets how much of the step'sspanthat phase spreads over:0fires every targeted icon at once (a pattern flash),1walks them one at a time.
So "scale one at a time", "recolour one at a time", and "recolour many in a pattern" are all this one
primitive. Steps fold in order, so you stack them into layered, creative sequences (a colour
sweep riding over a scale ripple). The animation is a pure function of time, so the video is
frame-stepped (crisp, reproducible) and the still is just that timeline frozen at motion.posterTime.
Config options
Provide at least one icon (via icons and/or dir); everything else has a sensible default.
Reference is the interactive view; TypeScript is the same shape in code.
icons(string | { src: string })[]default []Icon sources: paths and/or { src } objects. Combined with dir (dir first, then these). At least one icon must resolve (from icons and/or dir).
dirstringA folder whose image files (svg/png/webp/jpg/gif/avif) become the icons, sorted by filename. Combined with icons.
template'showcase' | 'scale-sweep' | 'color-sweep' | 'ripple' | 'pattern'Load a named interaction preset (deep-merged; your explicit options still win). "showcase" (the default when no steps are given) is a radial scale ripple then a forward recolour sweep. "scale-sweep" scales each icon up one at a time. "color-sweep" recolours each icon one at a time. "ripple" is a scale wave from the centre. "pattern" flashes a checkerboard then alternate rows.
accentstringdefault '#7c9cff'Accent colour the recolour templates sweep to (any CSS colour). Ignored if you supply your own steps.
outputobjectdefault {}
Output frame + encoding settings. Override any subset.
format'video' | 'image'default 'video'Emit a looping video (mp4) or a single still frame (png).
widthnumberdefault 1080Output frame width in px.
heightnumberdefault 1080Output frame height in px.
deviceScaleFactornumber (≤4)default 2Render scale (higher = crisper capture, downscaled into the output).
fpsnumber (≤120)default 30Output frames per second (video).
crfnumber (0–51)default 18x264 quality (lower = better quality / larger file) (video).
workersnumberParallel render workers (video). Omit to auto-pick from cores + free memory.
fileNamestringOutput filename. Defaults to "<slug(asset name)>.<mp4|png>".
motionobjectdefault {}
Animation timeline — clip length + still sample point. Override any subset.
durationMsnumberdefault 8000Clip length in ms. The steps' at/span (and posterTime) are fractions of this.
posterTimenumber (0–1)default 0.5For output.format: "image" — which clip moment to freeze, as a fraction of the timeline.
layoutobjectdefault {}
Grid layout + appearance. Override any subset.
columnsnumberFixed column count. Omit to auto-pick a near-square grid from the icon count + frame aspect.
gapnumber (px)default 32Gap between icons.
paddingnumber (px)default 64Padding around the grid.
iconSizenumber (px)Icon cell size. Omit to fit the grid to the frame.
backgroundstringdefault '#0b0b0f'Backdrop behind the grid (any CSS colour).
recolorbooleandefault trueTint each icon via a CSS mask so it can be recoloured (shape from the file's alpha). Off = render icons natively (original colours) — then only scale / opacity / rotate effects apply.
baseobjectdefault {}
Resting icon appearance (before any step) + the animation seed.
colorstringdefault '#f4f4f5'Resting icon colour in tint mode (any CSS colour).
scalenumberdefault 1Resting icon scale multiplier.
opacitynumber (0–1)default 1Resting icon opacity.
seednumberdefault 1Seed for "random" sweep orders — same seed ⇒ identical animation.
stepsEffectStep[]default the template's steps
The animation: an ordered list of effect steps, folded in order so they layer. Defaults to the selected template's steps (or "showcase" when nothing is set).
kind'scale' | 'color' | 'opacity' | 'rotate' | 'spin'requiredWhat to animate. scale = size multiplier; color = tint; opacity = fade; rotate = to an angle; spin = full turns.
atnumber (0–1)requiredWhen the step starts, as a fraction of the clip.
spannumber (0–1)requiredHow long the step lasts, as a fraction of the clip.
order'forward' | 'reverse' | 'random' | 'rows' | 'columns' | 'diagonal' | 'radial-in' | 'radial-out' | 'spiral'default 'forward'Sweep order across the grid (each icon's phase). Icons sharing a phase fire together (a wave / ripple / ring).
staggernumber (0–1)default 0.6How much of span the phase spreads over: 0 = all targeted icons at once (a pattern), 1 = one at a time.
targets'all' | 'even' | 'odd' | 'checkerboard' | 'rows-alt' | 'cols-alt'default 'all'Which icons participate. The patterns select a subset for a checkerboard / striped effect.
easing'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'ease-out-strong' | 'ease-in-out-strong'default 'ease-in-out'Easing of each icon's ramp.
returnbooleandefault trueBounce back to base by the end of the icon's slice (true) or latch at the target and hold (false). Keep true for a seamless loop.
holdnumber (0–1)default 0.3Fraction of each icon's slice held at the peak before it returns. Ignored when return is false.
intensitynumber (0–1)default 1Scales the effect's strength.
scalenumberdefault 1.6scale: target size multiplier (1 = base).
colorstringcolor: target colour (any hex or rgb()).
opacitynumber (0–1)default 1opacity: target opacity.
anglenumber (deg)default 90rotate: target angle in degrees.
turnsnumberdefault 1spin: full turns over the icon's slice (a whole number loops seamlessly).
{
name: "icon-set",
generator: "icons",
options: {
// --- source (icons is local: no url) ---
dir: "public/icons", // folder of image files, sorted by name
// icons: ["public/logo.svg", { src: "assets/star.png" }], // and/or explicit sources
// template: "showcase", // optional preset (your options still win)
accent: "#7c9cff",
// --- grid layout & appearance ---
layout: {
// columns: 6, // optional; auto near-square by default
gap: 32,
padding: 64,
// iconSize: 96, // optional; fits the frame by default
background: "#0b0b0f",
recolor: true, // false = native colours (scale/opacity/rotate only)
},
// --- resting state & determinism ---
base: {
color: "#f4f4f5",
scale: 1,
opacity: 1,
seed: 1, // same seed ⇒ identical "random" sweeps
},
// --- the animation (order + stagger = the interaction) ---
steps: [
// scale ripple out from the centre, then settle back
{ kind: "scale", at: 0.04, span: 0.4, order: "radial-out", stagger: 0.7, scale: 1.5, hold: 0.15 },
// recolour to the accent, one icon at a time (forward)
{ kind: "color", at: 0.5, span: 0.42, order: "forward", stagger: 0.85, color: "#7c9cff", hold: 0.2 },
],
// --- the timeline ---
motion: {
durationMs: 8000, // steps' at/span (and posterTime) are fractions of this
posterTime: 0.5, // for output.format: "image" — which moment to freeze
},
// --- output & encoding ---
output: {
format: "video", // "image" freezes one PNG at motion.posterTime
width: 1080,
height: 1080,
deviceScaleFactor: 2,
fps: 30,
crf: 18,
// fileName: "icon-set.mp4", // optional; defaults to <slug(name)>.<mp4|png>
},
},
}Icons should be uniform (same canvas size) and, for the recolour effects, single-colour silhouettes — the mask takes the shape from the file's alpha. Turn
layout.recoloroff to show multi-colour icons in their native colours.
Layout. The grid auto-fits and stays centred in the frame at any
width×height:columnsdefaults to a near-square count for the frame's aspect (set it to drive wide or tall layouts), the icons size up to fill, and a short final row is centred under the full rows — so odd counts (7, 10, 13…) still look balanced, not lopsided. Cells are square, so non-square icons letterbox within their cell.layout.iconSizecaps the cell (it's clamped so it can never overflow the frame); leave it off to fill.
Examples
Reach for a template for an instant look, or compose your own steps — the interaction is just an
order + stagger on each step. Every clip below is a real icons render of the same 16-icon
storefront set.
The showcase template
The default template: "showcase" (used whenever you give no steps): a scale ripple out from the
centre, then a forward recolour sweep to the accent — settling back to rest so it loops.
{name: "icon-set",generator: "icons",options: { dir: "public/icons", template: "showcase", accent: "#8a5a3c", layout: { background: "#1a1714" }, base: { color: "#f6f3ed" },},}One at a time
stagger: 1 with a forward order walks the effect through the grid one icon at a time — the
clearest way to "read out" a whole set.
{name: "icon-rollcall",generator: "icons",options: { dir: "public/icons", layout: { background: "#1a1714" }, base: { color: "#f6f3ed" }, steps: [ { kind: "scale", at: 0.05, span: 0.9, order: "forward", stagger: 0.85, scale: 1.6, hold: 0.2 }, ],},}A pattern, all at once
stagger: 0 fires every targeted icon together; a targets pattern picks the subset — here a
checkerboard pulses to one colour, then the alternate rows to another.
{name: "icon-checker",generator: "icons",options: { dir: "public/icons", layout: { background: "#1a1714" }, base: { color: "#f6f3ed" }, steps: [ { kind: "color", at: 0.05, span: 0.4, stagger: 0, targets: "checkerboard", color: "#8a5a3c", hold: 0.45 }, { kind: "color", at: 0.52, span: 0.4, stagger: 0, targets: "rows-alt", color: "#b49a77", hold: 0.45 }, ],},}Layered motion
Steps fold in order, so stack them — a ripple from the centre, a diagonal colour sweep, and a scattered spin riding on top.
{name: "icon-showpiece",generator: "icons",options: { dir: "public/icons", layout: { background: "#1a1714" }, base: { color: "#f6f3ed" }, steps: [ { kind: "scale", at: 0.02, span: 0.5, order: "radial-out", stagger: 0.6, scale: 1.45 }, { kind: "color", at: 0.30, span: 0.5, order: "diagonal", stagger: 0.6, color: "#8a5a3c" }, { kind: "spin", at: 0.55, span: 0.4, order: "random", stagger: 0.7, turns: 1 }, ],},}A still contact sheet
Set output.format: "image" to freeze one moment as a PNG — a clean grid of the whole icon set, with
posterTime picking which beat to catch (here the checkerboard mid-flash).
{name: "icon-sheet",generator: "icons",options: { dir: "public/icons", template: "pattern", accent: "#8a5a3c", layout: { background: "#1a1714" }, base: { color: "#f6f3ed" }, output: { format: "image", width: 900, height: 900 }, motion: { posterTime: 0.25 },},}



