| name | svg-animate |
| description | Create, edit, or remix an animated SVG file from a natural-language description.
Use this whenever the user asks you to make, build, design, generate, animate,
or "draw something that moves" — mascots, logos, loaders, badges, icons,
characters, brand marks, or any visual asset that should animate. Always
prefer this skill over hand-writing SVG strings.
Trigger phrases (use this skill even on subtle variants): "做一个X动画",
"给我画个会动的Y", "搞一个跳跃/旋转/脉动的Z", "make me an animated X",
"create a bouncing/spinning/pulsing X", "build a logo that does Y",
"design a loader that ...", "I need an SVG mascot for ...".
Also use when the user provides a reference image or sketch and asks to
"animate this" or "make this lively". When the user says they want output
"as an SVG file" or asks for something to drop into output/, this skill is
the right entry point.
|
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash(node lib/*), Bash(npm test*), Bash(npm run dev*), Bash(npm run render*), Bash(node*) |
You are the entry point for SVG animation requests in this repository.
The repository (svg-animation-studio) is a compounding system: every animation
reuses primitives from lib/primitives/, every new primitive earns the right
to be added through /svg-add-primitive. Your job is to translate one
sentence of natural language into a working output/<slug>.svg that visually
delights — without breaking that compounding discipline.
Why this skill exists
LLMs that template-literal SVG strings end up with subtly broken markup,
inconsistent IDs, and no way to evolve. This skill funnels everything through
lib/composer.js so the system stays composable, snapshot-testable, and
reusable across sessions. If you find yourself wanting to write <svg ...>
inline anywhere, stop — composer.composeSVG exists for exactly that need.
Step 1 — Understand what's being asked
- Read any user-provided reference from
ref/ (sketches, sample SVGs, screenshots).
- Identify the asset class: character mascot, logo mark, loader/spinner,
notification badge, ambient background, or icon.
- Identify the motion verbs: jumping, bobbing, pulsing, spinning, waving,
talking, glowing, drifting, flickering.
- Identify the style cues: pixel-art, gradient-smooth, flat, neon, comic.
If the request is vague ("make it cool"), ask one clarifying question — pick
the highest-leverage one (usually: "what shape/character?" or "what motion?").
Don't grill the user.
Step 2 — Read the catalog before composing
Open lib/presets/index.js to see what already exists. Look at each
candidate's compose() (~30 lines each) to learn the patterns. Decide one of:
- Fork — closest existing preset is 80%+ right. Copy its file to a new
slug, adjust params/colors.
- Compose from primitives — no existing preset fits, but the needed
primitives all exist in
lib/primitives/. Build a new preset.
- Stop and request a primitive — a needed motion / shape / filter doesn't
exist. Output a one-line summary plus:
"I need a <primitive-name> primitive in lib/primitives/<category>.js.
Please run /svg-add-primitive <name>."
Do NOT add primitives inline. See docs/extension-protocol.md rule 1.
For inspiration mapping ("user wants X → reach for Y"), open
references/preset-cookbook.md.
For the full lib API (signatures + defaults), open references/lib-api.md.
For any branded / multi-layer asset (cover, banner, logo, loader), use the
modern composition path (it's how the quality bar is reproduced):
- Pull a palette with
getPalette(opts.palette || '<default>') from lib/palettes.js
and reference accents by index (pal.accents[0]) — never hardcode hex.
- Build the body with
composeScene({ layers, defs, style }) from lib/scene.js:
it orders layers back-to-front and auto-nests a layer's static transform +
animated className (so the CSS transform can't clobber positioning — Gotcha 2).
- Then fork the closest family template:
brandCover.js / gradientCover.js
(covers), heroStrip.js / repoHero.js (banners), orbitLogo.js /
pulseMonoLogo.js / spinBadge.js (logos/badges), dotLoader.js /
rippleLoader.js / bounceChar.js (loaders/characters). chan-cover is the
FROZEN reference — fork brand-cover instead.
For why timing / squash / staggering matter, open
references/animation-principles.md. The 5-point quality checklist there is
the same one /svg-verify will grade against — design with it in mind.
Step 3 — Write the preset
A preset file lives at lib/presets/<slugCamelCase>.js and exports:
module.exports = {
name: '<slug-kebab>',
viewBox: '0 0 W H',
width: W, height: H,
compose(opts = {}) {
motion.resetIdCounter();
}
};
Add the new slug to lib/presets/index.js.
Constraints, in priority order:
composeSVG is the only allowed way to assemble the root <svg> document.
- Call
motion.resetIdCounter() as the first line of compose() so class
names are deterministic across calls — this is what makes snapshots stable.
- Default colors and durations should match the source's intent; allow the
user to override via
opts.
- Re-use existing primitives wherever possible. Each new preset that re-uses
≥ 1 primitive validates the compounding contract.
Step 4 — Render
node lib/render-cli.js <slug>
This writes to output/<slug>.svg. Inspect file size — if it's < 200 bytes,
something is wrong (probably an empty body); if it's > 20 KB without good
reason (heavy gradients or many elements), something is duplicated.
Step 5 — Snapshot
npm run test:snapshot
First time for a new preset → snapshot is created. Subsequent runs → diff is
shown. If diff is intentional (you tuned params), update with:
npm run snapshot:update
Always re-verify visually after a snapshot update.
Step 6 — Visually verify
Invoke /svg-verify <slug> — it runs in an isolated subagent context, opens
the SVG in chrome-devtools MCP, scores against the 5-point rubric, and
returns ≤ 100 words. The screenshots and console messages stay in the
subagent's context so this main thread doesn't drown in image data.
Step 7 — Iterate or finalize
If the verdict is tweak: adjust params (duration, easing, magnitude,
stagger), re-render, re-verify. Cap at 3 rounds before pausing to ask the
user. If fail: probably a structural issue — re-read the source primitives
and reconsider composition rather than tweaking magnitudes.
If pass: report to the user with:
- Path:
output/<slug>.svg
- Primitives used:
motion.foo, shapes.bar, ...
- The verdict's one-line summary
- (Optional) Whether to run
/svg-export <slug> to optimize and ship.
Anti-patterns
- ❌ Writing
<svg ...> markup directly in a skill or preset. Always go
through composer.composeSVG.
- ❌ Adding a function to
lib/primitives/*.js without going through
/svg-add-primitive. The protocol exists to keep JSDoc, snapshot coverage,
and lib-api.md in sync.
- ❌ Calling
npm run snapshot:update without a visual recheck. Snapshots
are the only thing protecting you from silent regressions.
- ❌ Burning the user's time on > 3 silent iterations. After three rounds
without a pass, pause and ask.
- ❌ Hand-writing keyframe values when a
motion.* primitive already covers
the motion. Composability is the whole point.
When the user wants multiple variants
If the user asks for "a few options", "show me 3 styles", "give me variants",
or "compare different approaches", delegate to the svg-explorer subagent
instead of running variants serially in this thread. The Agent dispatch
pattern:
Use the svg-explorer agent to produce <N> variants of
"<user's description>" varying along axis <timing|palette|style|energy>.
It returns a comparison block with file paths + a recommendation. Then run
/svg-verify <recommended-slug> on the chosen winner, and /svg-export if
the user accepts.
Why delegate? Generating N presets in parallel + rendering N SVGs + reading
N preset diffs would pump verbose intermediate output into this thread.
The fork keeps the comparison clean: you only see the final summary.
When something goes wrong
npm test fails on a preset you didn't touch → you broke a primitive
signature. Diff your primitive changes and either fix them or update
the snapshot deliberately after visual recheck.
compose() throws → almost certainly a missing or renamed primitive.
Open docs/lib-api.md and grep for the function you tried to call.
- Visual verification keeps failing on stagger → check
animation-principles.md section "Staggering"; the magnitude is probably
outside 0.05–0.15 s.