Agent skill

Animation

by nimbalyst in nimbalyst/nimbalyst

Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor.

MITAuto-check passedDevelopment

Install Animation

skills CLI
$ npx skills add nimbalyst/nimbalyst --skill animation -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install nimbalyst/nimbalyst animation --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .claude/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .claude/skills/animation && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
animation
GitHub stars
1.8k
Token cost
~7.8k tokens
SKILL.md length
4,158 words
Files
1
Skills in repo
14
Repo updated
First seen
Licence
MIT

At a glance

Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor.

  • Works in 3 steps: The document says what is true when,… → States are cumulative. A step asserts… → Ids are names, not handles. store,…
  • The user wants to animate a diagram
  • SKILL.md covers When to use this, The mental model, Document structure and Geometry, plus 7 more sections
  • Calls node

What it does

Animation is an agent skill from nimbalyst/nimbalyst. Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor. Use when the user wants to animate a diagram, show how a system/protocol/algorithm behaves over time, build a motion explainer, or turn a static architecture diagram into something that plays.

Its SKILL.md is about 7.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Development, covering Diagrams. The repository describes itself as: Nimbalyst - The open-source visual workspace for Claude Code, Codex, and OpenCode. Run multiple coding agents in parallel, edit their work visually in markdown, mockups, and… The licence is MIT.

When your agent uses it

  • The user wants to animate a diagram
  • Show how a system/protocol/algorithm behaves over time
  • Build a motion explainer
  • Turn a static architecture diagram into something that plays

Example prompts

  • “/animation”

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  1. The document says what is true when, never how to tween. There are no keyframes, easing curves, or property tracks. You assign a state to…
  2. States are cumulative. A step asserts only what changes; every part it does not mention keeps whatever the previous step left it in. Write…
  3. Ids are names, not handles. store, title-card, queueTask01. You will reference them constantly in set blocks; make them readable.

What it can do on your machine

Read from SKILL.md and the folder at commit a3dbdb4. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • node

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Animation loads about 7.8k tokens when it runs. Until then it costs about 75 tokens; SKILL.md has 4,158 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~75
When it runs · the whole SKILL.md, loaded when a task matches
~7.8k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from nimbalyst/nimbalyst at commit a3dbdb4, republished under its MIT licence (© nimbalyst). 4,158 words, ~7,845 tokens.

Download SKILL.mdSave it as .claude/skills/animation/SKILL.md (or your agent's skills folder).
name
animation
description
Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor. Use when the user wants to animate a diagram, show how a system/protocol/algorithm behaves over time, build a motion explainer, or turn a static architecture diagram into something that plays.

Animation - step-based explainer diagrams

.anim.json files open in Nimbalyst's Animation editor: a named scene plus an ordered list of steps that assign states to the scene's parts. You write plain JSON. Any agent can author or edit one with Write and Edit -- there is no binary format and no tool call required.

When to use this

  • Explaining how a system behaves over time: a request crossing a network, a consensus round, a cache filling, a build pipeline, a queue draining.
  • Turning a static architecture diagram into something that plays.
  • Showing a failure and a recovery: the retry, the rollback, the rejected review.

Do not use it when a static diagram says the same thing. If nothing changes between the first frame and the last, you want Excalidraw or a Mermaid block, not an animation.

The mental model

Three rules drive every decision in this format:

  1. The document says what is true when, never how to tween. There are no keyframes, easing curves, or property tracks. You assign a state to a part, and CSS transitions interpolate. Adding motion means naming a state, not scripting a timeline.
  2. States are cumulative. A step asserts only what changes; every part it does not mention keeps whatever the previous step left it in. Write deltas. To turn something off you must explicitly set it back -- it will not decay on its own.
  3. Ids are names, not handles. store, title-card, queueTask01. You will reference them constantly in set blocks; make them readable.

Times are integer milliseconds. Never frame indices, never floats.

Document structure

json
{
  "version": 1,
  "stage": { "width": 1200, "height": 640, "fps": 25 },
  "parts": { "<id>": { "type": "node" | "edge" | "label" | "shape", ... } },
  "steps": [ { "id": "...", "duration": 800, "caption": "...", "set": { ... } } ]
}
stage
FieldNotes
width, heightClamped to 16..8192. The stage scales to fit the pane, so these set the aspect ratio and the coordinate system, not the pixel size.
fpsOnly affects frame snapping and the readout. Use 25 unless you have a reason. Whole-millisecond frame rates: 10, 20, 25, 50.
backgroundOptional override. Omit it and the stage uses the theme surface, which is what you want -- the scene then follows the user's light/dark theme.

1200x640 is a good default. Landscape, room for a header row and a bottom rail.

parts

Part ids are the keys. All four types share label, tone, and state (their baseline, before any step runs).

node -- the workhorse. A titled card with an optional subtitle and key/value rows.

json
{ "type": "node", "label": "Object store", "x": 740, "y": 118, "w": 240, "h": 176,
  "subtitle": "SHA -> BYTES",
  "rows": [ { "key": "f7a9", "value": "commit  182 B" }, { "key": "e816" } ] }
  • label is uppercased automatically. Write "Merge gate", it renders MERGE GATE. Falls back to the id.
  • subtitle is a small mono line under the header. Keep it short and caps-ish; it is where model names, worktrees, and units go.
  • rows render as boxed key/value pairs. value is optional. Key is left-aligned, value right-aligned.
  • A row that would spill past the bottom is silently dropped. Size the node to its rows (see Geometry).

edge -- a line between two parts, optionally carrying packets.

json
{ "type": "edge", "from": "client", "to": "store", "text": "GET <sha>", "packets": 4 }
  • from/to are part ids. Drawn centre-to-centre and trimmed to the box borders, so stacked and side-by-side both look right.
  • A dangling from/to renders nothing at all -- it looks like a broken renderer, not a broken document. Check your ids.
  • packets is how many squares travel the line while it is flowing (default 3). Set 0 for an edge that means a relationship rather than traffic. One trip takes 1.6s.
  • text draws a caption at the midpoint on an opaque background plate roughly max(40, len × 7.6 + 16) px wide. It will punch a hole through anything behind it. Only put text on an edge whose gap is wider than the plate.

label -- free-standing text.

json
{ "type": "label", "x": 56, "y": 48, "text": "COMMIT DAG", "align": "start", "caps": true }
  • align: start | middle | end (the anchor, at x).
  • caps: true gives the faint, tracked-out micro-caption style used for section headings.
  • There is no font-size control. Every label is 12px. Do not try to build a large title; build hierarchy with caps, tone, and position instead.

shape -- a plain rect or circle, with optional centered text.

json
{ "type": "shape", "shape": "rect", "x": 89, "y": 438, "w": 40, "h": 18, "tone": "accent" }

Shapes are how you show quantity, because text never changes (see Hard constraints). A grid of small shapes that go hidden one group at a time is a queue draining, a battery discharging, a work list being claimed.

html -- freeform markup, for the things the primitives above cannot express: real typography, a type scale, flow layout, a UI that has to look like a real product rather than like a diagram of it.

The markup comes from one of two places:

json
{ "type": "html", "x": 55, "y": 108, "w": 1090, "h": 534,
  "htmlFile": "./partials/app-window.html",
  "vars": { "title": "acme-api", "branch": "main" } }
  • htmlFile is a path to a .html file next to the document. Relative only; .. is allowed, absolute is refused.
  • html is markup inline. Right for a few lines, wrong for a widget.

vars fills {{name}} placeholders in whichever source won. Values are HTML-escaped, so a var is text and cannot change the structure of the partial it lands in. An unfilled placeholder resolves to empty, never to its own name. That is the entire template language -- no conditionals, no loops, no expressions.

Three things about html parts that will otherwise cost you a build:

  • --anim-tone inherits into the markup. Give a border, a badge, a button color: var(--anim-tone) plus a transition, and a step's tone change animates the whole widget. This is how you avoid a second copy of the markup for a second state.
  • Script, event handlers, <style> and non-https/data:image urls are stripped at the render boundary. Inline style= attributes are fine.
  • An opaque html part hides every edge underneath it. Edges are drawn before all non-edge parts, so a background on a container is enough to make a nested diagram's lines vanish with no error. Keep containers transparent and set stage.background to supply the surface colour instead.
stage.theme

stage may carry a stamped palette that every renderer reads:

json
"stage": { "width": 1200, "height": 675, "fps": 25, "theme": {
  "bg": "#1c1c1c", "border": "#383838", "accent": "#3b82f6",
  "--nim-panel": "#2f2f2f"
} }

Keys naming a stage token (bg, surface, surfaceRaised, border, borderStrong, text, textMuted, textFaint, accent, success, warning, error, purple) override that token. Keys of the form --some-name are emitted as extra custom properties, which is how a project carries its own vocabulary into the stage. Anything else is dropped.

Values, not a theme name: no renderer then needs a theme registry, and the extension stays neutral about whose product this is. The cost is that editing a theme in the app does not reach existing documents until they are restamped.

Write markup against token names, not literal hex. var(--anim-border) rather than #383838 means switching palettes is a one-field edit with nothing to redraw. Reserve literal colours for things that genuinely are fixed -- macOS traffic lights.

Note that stage.background, if set, still wins over theme.bg.

sub-parts

Markup inside one html part can declare regions a step addresses individually:

html
<div class="anim-subpart" data-part="chrome/session-a">…</div>

A step then writes "set": { "chrome/session-a": { "tone": "success" } }, and the stage drives it with the same querySelector + setAttribute it uses for a top-level part. Declare each one in the part's subParts map so the baseline resolver knows it exists:

json
"subParts": { "session-a": { "tone": "accent" }, "session-b": {} }

Two things to know:

  • The class is anim-subpart, never a nested anim-part. .anim-part sets --anim-tone to neutral unconditionally, so a nested one resets to grey instead of inheriting its container's tone.
  • For a sub-part, neutral means "inherit", not "grey". That is what lets a whole window go accent without every region inside it snapping back.

Writing data-part by hand is fine for two or three regions. Past that, the markup is worth compiling from a component -- see the compiler below.

The spinner utility (anim-spin)

The stage ships one rotating primitive for html parts. Give any element class="anim-spin" and it becomes a lit ring turning at ~0.8s -- a running or loading indicator that reads as live rather than a static glyph. It is the rotational counterpart to the edge packet, and the only self-driven rotation the format has.

html
<span class="anim-spin" style="width:8px;height:8px"></span>
  • Colour is currentColor, so whatever wraps it sets the hue -- drop it inside a blue "running" pill and the ring is blue, no extra styling.
  • It spins on its own, across every step. You do not drive it from set; it is a CSS animation, not a state. To make it stop, hide the part or sub-part it lives in on the step the work finishes (put it in its own anim-subpart if only the spinner should disappear).
  • It freezes but stays visible while the playhead is scrubbed and under prefers-reduced-motion, and both recorders capture it exactly as they capture packets -- so it survives export_html and export_gif.

Reach for it wherever the real UI shows a spinner: an agent session mid-run, a build in progress, a request in flight. It is the honest way to show "this is working right now" without faking motion the format cannot do.

Building a kit for your project

Nothing product-specific ships with this extension, and that is deliberate: the markup worth reusing is always your product, so anything bundled here could only ever be somebody else's app.

Instead, build a small kit once and reuse it across every animation you make:

docs/animations/
  partials/
    app-window.html      <- your chrome: title bar, sidebar, main pane
    list-row.html        <- one row, used eight times with different vars
    toolbar.html
  onboarding.anim.json
  sync-explainer.anim.json

Three rules make a kit that lasts:

  1. Go look at the real thing first. Chrome drawn from memory is wrong in structure, not just styling, and structural wrongness is invisible to you and instantly obvious to anyone who uses the product. Screenshot the app and draw against it.
  2. Simplify hard, then make one thing big. Keep only what makes it recognisable at a glance; cut every pane that is not part of the story. Then pick one hero and draw it at a size you can actually read.
  3. Decide loop-vs-part per piece. Markup that stays put lives inside a bigger partial. Anything a step changes is either its own top-level part positioned over the container, or -- if the container is a compiled component -- a sub-part the component declares. A .html partial has no way to declare one, so with partials a list's eight resting rows are one file and the row that turns green is its own part with its own coordinates.

A partial has no defaults -- a var you leave out renders as empty, not as an error -- so document the vars each one expects at the top of the file in a comment.

Compiling components (optional, needs Node)

vars is deliberately not a template language: no conditionals, no loops, no types, no defaults. When that starts to hurt -- a list whose length varies, a phase colour repeated across fifty documents, a region you want a step to address -- author the markup as a .tsx component instead and compile it into the part:

json
{ "type": "html", "x": 55, "y": 108, "w": 1090, "h": 534,
  "component": "./components/DesktopWindow.tsx",
  "props": { "sessions": [ { "id": "sync", "title": "Sync explainer", "phase": "implementing" } ] } }
node packages/extensions/animation/tools/anim-compile.mjs docs/onboarding.anim.json [--theme dark] [--check]

The compiler renders the component with those props and writes three fields back: html (the markup, sanitizer-clean), subParts (the regions the markup actually declared) and build (hashes of the props and of the component sources). Nothing renders the component at play time. Every consumer keeps drawing the same static string, which is the only reason they agree frame for frame -- and one of them, the offscreen screenshot host, has no filesystem to resolve a component from at all.

What you get that vars cannot give you: real conditionals, typed props (a mistyped phase name is an error before anything renders), shared defaults, children for static panes, and AnimPart -- a wrapper that declares a step-addressable region from the data it was handed, so twelve kanban cards need no coordinates and a thirteenth needs no edit to the other twelve.

It lints as it goes and refuses to write on a failure: markup that the sanitizer would alter, a step addressing a part or sub-part that does not exist, components that do not typecheck. It warns about an opaque container root and about a part sized away from the component's declared size.

Constraints worth knowing before you reach for it:

  • Sub-part ids come from your data, so renaming sessions[0].id rewires the steps addressing it. The step-target lint turns that into an error rather than a region that silently stops animating.
  • Layering inside a component is DOM order, while layering between top-level parts is alphabetical id order. Two models.
  • subParts is generated. The compiler rewrites it from the markup on every run, so edit the component, not the map. A label you add by hand survives as long as the region does.
  • v1 runs from a checkout of the Nimbalyst repo, because it bundles this extension's own document reader and writer from source so its output and the editor's save cannot drift. htmlFile and inline html need none of this and are unchanged.
tones

neutral accent data success warning error muted

They map to theme tokens, so they follow the user's theme: accent is blue, data purple, success green, warning amber, error red, neutral/muted a faint grey. Assign them semantically and keep the meaning fixed for the whole animation -- if amber means "under review" in step 4 it cannot mean "slow" in step 7.

states

The state vocabulary is defined by the stylesheet, not the schema. Any other string parses fine and renders as idle, so a typo fails silently.

TypeStates
nodeidle (default), active (tinted fill, tone border, status dot, first row highlighted), waiting (dashed amber border), offline (dashed red border, dimmed title), hidden
edgeidle (default, dashed grey), flowing (line fills in, packets travel from -> to), returning (same, packets travel backwards), active (same as flowing), hidden
labelidle (default), active (takes its tone colour), hidden
shapeidle (default, 14% tone fill), active (65% tone fill, reads as solid), hidden

returning is the most useful state in the set and the most under-used: a reply, a rejection, a rollback travelling back down the same wire. Reversing packets beats drawing a second edge -- two overlapping edges between one pair of nodes look like a rendering fault.

steps
json
{ "id": "response", "duration": 1000,
  "caption": "The objects come back down the same wire.",
  "set": { "fetch": { "state": "returning", "tone": "success" } } }
  • id is a readable slug, unique. It shows in the step strip.
  • duration is how long this step holds before the next begins, in ms (1..600000; the editor's drag-to-retime floor is 40ms).
  • caption is one sentence of narration. Read end to end, the captions should form a coherent paragraph -- they are also the animation's accessibility description.
  • set maps part id -> { state?, tone? }. Omit either and it inherits.

Playback loops by default, and the wrap is a hard cut: the stage jumps from your last step straight to the baseline-plus-first-step. Design that cut deliberately -- it should read as a reset, not as a glitch.

Geometry

Layout is hand-placed, so these numbers matter.

Node internals. Header is 34px. The subtitle baseline sits at y+56. Rows start at y+72 (or y+56 with no subtitle), each row 26px tall with a 6px gap -- 32px of pitch. A row is dropped if it would come within 6px of the bottom.

Node height: h = 32 × rows + 80 with a subtitle, h = 32 × rows + 64 without. Three rows plus a subtitle -> h = 176. Going much taller leaves a visible dead band under the last row.

Row text is 11px mono: key at x+28, value right-aligned at x + w − 28. At w = 228 you have room for roughly a 6-character key and a 12-character value.

Edges. Keep every edge between adjacent boxes. An edge routes as a straight centre-to-centre line trimmed to the borders, so a diagonal across a grid will run straight through the cards in between. If two boxes you want to connect are not neighbours, move them.

Leave the gap wide enough for what the edge carries: ~40px for a bare edge with packets, ~70px if it has text.

Layering. Edges draw first (behind everything), then all other parts in alphabetical id order. That is the only layering control there is. To draw a part on top of another, give it an id that sorts later: queue (the panel) then queueTask01..queueTask12 (the chiclets inside it).

Show full SKILL.md (1,602 more words)Show less

Canonical form

The editor rewrites the file on save with a fixed key order. Hand-write it in canonical order or your first save will reformat the whole file and bury the real edit in the diff.

  • Root: version, stage, parts, steps
  • stage: width, height, fps, background
  • parts: sorted alphabetically by id. Within a part: type, label, tone, state, then
    • node: x, y, w, h, subtitle, rows
    • edge: from, to, text, packets
    • label: x, y, text, align, caps
    • shape: x, y, w, h, shape, text
  • steps: document order -- it is the animation. Within a step: id, duration, caption, set. set keys sorted alphabetically; each assignment state then tone.
  • Two-space indent, one trailing newline.

Unknown keys are preserved and written after the known ones in sorted order, so a field this build does not model still round-trips.

Hard constraints

Design around these; they are not bugs to work around.

  • No text changes. No part's label, text, subtitle, or rows can differ between steps. A counter that ticks 36 -> 24 -> 12 is impossible. Show quantity with shapes going hidden, and write static captions that stay true for the whole run ("CLAIMED IN ORDER", not "16 REMAINING").
  • No movement. x/y are fixed. Parts appear, disappear, and change colour; they do not travel. The only continuous motion in the format is edge packets and the anim-spin spinner (see sub-parts). Nothing else rotates, slides, or eases.
  • No font sizes. Labels are 12px, node titles 13px, rows and subtitles 11px.
  • No z-index. Alphabetical ids, as above.
  • No per-step easing or delay. One transition duration (320ms) for everything.

Making a good one

Structure

  • 8 to 12 steps. Fewer feels like a slideshow, more and the viewer loses the thread.
  • 600-1200ms per step. Under 400ms nobody reads the caption; over 1500ms it drags. Give the beat where something changes meaning the longest hold.
  • One idea per step. If a caption needs "and", it is two steps.
  • Total 8-15 seconds. It loops; it does not need to be a documentary.

Layout

  • Put it on a grid. Align tops and bottoms across columns. Equal gutters. The format has no auto-layout, so sloppy coordinates read as a sloppy diagram.
  • Give it a header (a title label and a caps subtitle) and a bottom rail of caps labels that light in sequence. That rail is cheap and does more for legibility than anything else: it tells the viewer where they are in a process they have not seen before.
  • Group with proximity, not boxes. Two columns 70px apart with a caps heading over each beat any amount of nesting.
  • Flow left to right, or top to bottom. Pick one.

Motion

  • Turn things off. The single most common failure is an animation where every part is lit by the end, so the final frame is noise. Set edges back to idle once their traffic is done.
  • Land on a resolution. The last step should look settled -- one tone, everything quiet - not mid-flight.
  • Animate the interesting part. The beat worth the viewer's attention is almost never the happy path. It is the retry, the cache miss, the review that sends the work back. Use waiting and returning for it. An explainer that only shows success explains nothing.
  • Do not light every part in step 1. Start quiet and let the scene fill in; that is most of the perceived quality.

Colour

  • Two working tones plus grey carries most animations. Reach for a third only when it means a genuinely different thing.
  • Let neutral/muted do real work. Contrast comes from what is dim.

Workflow

  1. Sketch the steps first, in prose. Write the captions before you place a single coordinate. If the captions do not read as a paragraph, the animation will not read either.
  2. Place the scene on a grid. Nodes and their coordinates, then labels, then edges last -- edges are constrained by where the boxes ended up.
  3. Write the steps as deltas, in canonical order.
  4. Open it and look at it. Write to <name>.anim.json, open it in Nimbalyst, and capture the stage with mcp__nimbalyst__capture_editor_screenshot. Scrub to a few different steps and capture each. Do not skip this -- coordinate arithmetic is exactly the kind of thing that is right in your head and wrong on screen.
  5. Iterate on what you see: dead space under a node's last row, an edge label punching through a card, two things lit that should not both be lit, a step you cannot read in time.

If the user gave you a style reference image, match its vocabulary -- caps micro-labels, card density, how much is dim at rest - rather than trying to reproduce it pixel for pixel. Say plainly which parts of it the format cannot express.

Exporting

animation.export_html writes a .anim.json out as a self-contained HTML file that plays and loops on its own, with no external references. Point it at a path; it does not need the file open in an editor.

animation.export_html { filePath: "docs/cache.anim.json" }
-> docs/cache.html

Pass outputPath to put it somewhere else. It refuses to export a document with parse errors, and returns any warnings alongside the result. Clicking the exported page pauses it.

animation.export_gif writes an animated GIF instead, for destinations that cannot run a web page: a GitHub issue, a README, a chat message, a slide.

animation.export_gif { filePath: "docs/cache.anim.json", fps: 12, maxWidth: 720 }
-> docs/cache.gif

Prefer export_html whenever the destination can display a web page. The GIF is recorded by playing the animation in an offscreen window and capturing it in real time, so it takes about as long as the animation runs, it is capped at 256 colours, and it is one to two orders of magnitude larger than the HTML. The HTML is instant, sharp at any size, and a few tens of kilobytes.

All four renderers -- the editor preview, export_html, both recorders -- use the palette in stage.theme, or a fixed dark fallback when the document names none. They agree by construction. This used to be untrue: the preview read the app's live theme while every export hardcoded the fallback, so the same document was two different colours depending on who asked, and the authoring advice worked around it by telling you to hardcode your palette. That advice is withdrawn.

Keep GIFs small: fps 8-12 is plenty for this kind of diagram, and maxWidth 720 is usually enough. Both file size and recording memory scale with the square of the width.

There is no video export. If someone wants MP4, say so rather than implying the GIF is a substitute.

Showing it live inside Nimbalyst

To present a finished animation to someone inside Nimbalyst you do not export at all. A .anim.json has a registered custom editor, so a link to it alone on its own paragraph in a markdown document upgrades into the live, playing embed:

[Session kanban](/abs/path/board.anim.json "width=1040 height=585")

That is the right way to hand over an existing animation -- it is live and full-fidelity, where a screenshot is frozen and (for an animation) misleading. Two things decide whether it works:

  • The link must be the only thing in its paragraph (no other text, no second link), or it stays a plain link.
  • A prose link only upgrades in the document (Lexical) editor. A link pasted into AI transcript / chat prose does not upgrade -- put it in an actual .md document and open it. Absolute paths work; the file may be anywhere on disk.

When you author the .anim.json yourself, you do not need the link at all. This editor sets supportsTranscriptEmbed in its manifest, so the moment your Write/Edit creates or changes a .anim.json, the transcript renders it inline in the tool card as a live, click-to-activate stage. That is the meta payoff -- the agent writes the scene and it plays right there in the session. (This is a per-editor opt-in; editors that don't set the flag show a plain file row.)

capture_editor_screenshot is for your iteration in the workflow above (checking coordinates), not for presenting the finished piece -- present that with the inline embed.

Why frames cannot be stamped

Worth knowing, because it rules out the obvious shortcut: interpolation is CSS transitions and the packets are CSS animations, so writing data-state into a series of snapshots and stitching them gives a stepped slideshow with motionless packets. Any frame-based output has to drive a real browser through real playback. Do not build a frame stitcher.

Common mistakes

SymptomCause
An edge is invisiblefrom/to names a part that does not exist. Dangling edges render as nothing.
A row is missing from a nodeh is too small. h = 32 × rows + 80 with a subtitle.
A state does nothingTypo. Unknown state strings parse fine and render as idle.
An edge label sits on top of a cardThe gap is narrower than the label plate. Widen the gap or drop the text.
A part is hidden behind anotherAlphabetical draw order. Rename it to sort later.
Something stays lit foreverCumulative states. You never set it back to idle.
The whole file reformats on first saveIt was not written in canonical order.
A line crosses a cardThe two boxes are not adjacent. Move them, do not fight the router.

Worked example

A complete, canonical, three-part file. Copy it and grow it.

json
{
  "version": 1,
  "stage": {
    "width": 1080,
    "height": 420,
    "fps": 25
  },
  "parts": {
    "caption": {
      "type": "label",
      "x": 80,
      "y": 48,
      "text": "READ THROUGH CACHE",
      "caps": true
    },
    "cache": {
      "type": "node",
      "label": "Cache",
      "x": 420,
      "y": 130,
      "w": 240,
      "h": 176,
      "subtitle": "LRU / 512 MB",
      "rows": [
        { "key": "hit", "value": "0.4 ms" },
        { "key": "miss", "value": "18 ms" },
        { "key": "keys", "value": "12.4 K" }
      ]
    },
    "client": {
      "type": "node",
      "label": "Client",
      "x": 80,
      "y": 130,
      "w": 220,
      "h": 176,
      "subtitle": "GET /user/42",
      "rows": [
        { "key": "attempt", "value": "1" },
        { "key": "budget", "value": "50 ms" },
        { "key": "result" }
      ]
    },
    "fetch": {
      "type": "edge",
      "from": "client",
      "to": "cache",
      "text": "get",
      "packets": 3
    },
    "load": {
      "type": "edge",
      "from": "cache",
      "to": "origin",
      "packets": 3
    },
    "origin": {
      "type": "node",
      "label": "Origin",
      "x": 780,
      "y": 130,
      "w": 220,
      "h": 176,
      "subtitle": "POSTGRES",
      "rows": [
        { "key": "rows", "value": "1" },
        { "key": "cost", "value": "18 ms" },
        { "key": "load", "value": "moderate" }
      ]
    }
  },
  "steps": [
    {
      "id": "ask",
      "duration": 800,
      "caption": "The client asks the cache for a key.",
      "set": {
        "client": { "state": "active", "tone": "accent" },
        "fetch": { "state": "flowing", "tone": "accent" }
      }
    },
    {
      "id": "miss",
      "duration": 900,
      "caption": "It is not there, so the client waits.",
      "set": {
        "cache": { "state": "active", "tone": "warning" },
        "client": { "state": "waiting", "tone": "warning" },
        "fetch": { "state": "idle" }
      }
    },
    {
      "id": "load",
      "duration": 1100,
      "caption": "The cache reads through to the origin.",
      "set": {
        "load": { "state": "flowing", "tone": "data" },
        "origin": { "state": "active", "tone": "data" }
      }
    },
    {
      "id": "fill",
      "duration": 900,
      "caption": "The row comes back and the entry is filled.",
      "set": {
        "cache": { "state": "active", "tone": "success" },
        "load": { "state": "returning", "tone": "success" }
      }
    },
    {
      "id": "serve",
      "duration": 1000,
      "caption": "The client gets its answer; the next read will hit.",
      "set": {
        "client": { "state": "active", "tone": "success" },
        "fetch": { "state": "returning", "tone": "success" },
        "load": { "state": "idle" },
        "origin": { "state": "idle", "tone": "neutral" }
      }
    }
  ]
}

Note what it does: starts quiet, holds longest on the beat that costs 18ms, uses waiting for the stall and returning for both replies, sets fetch and load back to idle once they are done, and ends on one tone with the origin dimmed back out.

If you are working in the Nimbalyst repo itself, packages/extensions/animation/samples/ has two longer references: git-storage.anim.json (a request crossing a network) and meta-agent-sessions.anim.json (a grid of cards, a draining queue, and a review that sends work back).

© nimbalyst, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in packages/extensions/animation/claude-plugin/skills/animation of nimbalyst/nimbalyst.

Open the folder on GitHubat commit a3dbdb4

Compare with similar skills

Animation next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Animation compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Animation this skillnimbalyst/nimbalyst1.8k—~7.8kAutomated safety check: PassMIT
JSON Canvasheyitsnoah/claudesidian2.6k18 repos~3.5kAutomated safety check: PassMIT
Archify Diagramstt-a1i/archify79k—~2.9kAutomated safety check: PassMIT
Diagram Designcathrynlavery/diagram-design44k1 repos~7.5kAutomated safety check: PassMIT
Fireworks Tech Graphtisfeng/Easydict15k1 repos~1.4kAutomated safety check: PassMIT
Excalidraw Diagramcoleam00/excalidraw-diagram-skill4.9k2 repos~6.1kAutomated safety check: PassNone

Similar skills

  • JSON Canvas

    heyitsnoah/claudesidian

    Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections.

    2.6k GitHub starsUsed in 18 repos~3.5k tokens
    DevelopmentAuto-check passed
  • Archify Diagrams

    tt-a1i/archify

    Creates interactive architecture, workflow, sequence, data-flow and lifecycle diagrams as standalone HTML with inline SVG, themes and image or video export.

    79k GitHub stars~2.9k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Diagram Design

    cathrynlavery/diagram-design

    Creates branded diagrams, from architecture, flowchart and sequence to charts and maps, as self-contained HTML with inline SVG, with import from draw.io, Mermaid and Excalidraw.

    44k GitHub starsUsed in 1 repo~7.5k tokens
    DevelopmentAuto-check passed
  • Fireworks Tech Graph

    tisfeng/Easydict

    Create precise SVG technical diagrams, export PNG or offline HTML, and animate supported semantic SVGs to GIF.

    15k GitHub starsUsed in 1 repo~1.4k tokens
    DevelopmentAuto-check passed
  • Excalidraw Diagram

    coleam00/excalidraw-diagram-skill

    Create Excalidraw diagram JSON files that make visual arguments.

    4.9k GitHub starsUsed in 2 repos~6.1k tokens
    DevelopmentAuto-check passed
  • Draw.io Diagram Studio

    Agents365-ai/drawio-skill

    Creates and edits editable draw.io diagrams from descriptions, code, infrastructure files, SQL and API schemas, with sync, review, test and export tools.

    10k GitHub stars~2.4k tokensUpdated 5 days ago
    DevelopmentAuto-check: notes

More from nimbalyst/nimbalyst

All 14 skills in this repo
  • Knowledge Graph

    nimbalyst/nimbalyst

    Write a project's knowledge pages in Nimbalyst Pages -- record what people said and decided in the page it affects, keep typed pages for the things the team tracks (its own types, such as modules…

    1.8k GitHub stars~3.5k tokensUpdated 2 days ago
    Auto-check passed
  • Knowledge Setup

    nimbalyst/nimbalyst

    Set up or check a project's knowledge pages in Nimbalyst Pages -- install the editable "How we write this wiki" guide page, define the team's own page types (and subtypes) and the named relations…

    1.8k GitHub stars~1.8k tokensUpdated 2 days ago
    Auto-check passed
  • Canvas

    nimbalyst/nimbalyst

    Author Nimbalyst Project Canvas boards (.canvas files) — an infinite canvas whose cards are live editors for real workspace files and shared documents, arranged spatially and wired with edges.

    1.8k GitHub stars~2.6k tokensUpdated 2 days ago
    Auto-check passed
  • Datamodellm

    nimbalyst/nimbalyst

    Create visual data models for database schemas using Nimbalyst's DataModelLM editor.

    1.8k GitHub stars~713 tokensUpdated 2 days ago
    Auto-check passed
  • Excalidraw

    nimbalyst/nimbalyst

    Create diagrams and visual drawings using Excalidraw (.excalidraw files).

    1.8k GitHub stars~1.4k tokensUpdated 2 days ago
    Auto-check passed
  • Extension Development

    nimbalyst/nimbalyst

    Build, install, and hot-reload Nimbalyst extensions using MCP tools.

    1.8k GitHub stars~1.5k tokensUpdated 2 days ago
    Auto-check passed

Categories

Questions about Animation

What does Animation do?

Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor. Animation is an agent skill from nimbalyst/nimbalyst.json files for Nimbalyst's Animation editor.

When should I use Animation?

Animation fits situations like: the user wants to animate a diagram; show how a system/protocol/algorithm behaves over time; build a motion explainer; turn a static architecture diagram into something that plays.

How do I install Animation in Claude Code?

Run `npx skills add nimbalyst/nimbalyst --skill animation -a claude-code`. Or copy the skill folder (packages/extensions/animation/claude-plugin/skills/animation in nimbalyst/nimbalyst) into .claude/skills/animation in your project. Claude Code loads it when a task matches its description.

How do I install Animation in Codex?

Run `npx skills add nimbalyst/nimbalyst --skill animation -a codex`. Or copy the skill folder (packages/extensions/animation/claude-plugin/skills/animation in nimbalyst/nimbalyst) into .agents/skills/animation in your project. Codex loads it when a task matches its description.

Can I use Animation in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add nimbalyst/nimbalyst --skill animation -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/animation, .gemini/skills/animation, .github/skills/animation and .opencode/skills/animation in your project.

What does Animation need to run?

Going by SKILL.md and its folder, Animation needs the command-line tools its instructions call (node).

Does Animation access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Animation safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Animation use?

Animation is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Animation use?

About 7.8k tokens (SKILL.md is roughly 31k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Animation?

Skills that share tags, products or a category with Animation: JSON Canvas (heyitsnoah/claudesidian, 2.6k stars), Archify Diagrams (tt-a1i/archify, 79k stars), Diagram Design (cathrynlavery/diagram-design, 44k stars) and Fireworks Tech Graph (tisfeng/Easydict, 15k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Animation?

nimbalyst (a GitHub organization) maintains it in nimbalyst/nimbalyst, which has 1,847 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on October 5, 2026.

Source: nimbalyst/nimbalyst on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.