Agent skill

Motion Curves

by nodetool-ai in nodetool-ai/nodetool

Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once.

AGPL-3.0Auto-check passedMedia & Creative

Install Motion Curves

skills CLI
$ npx skills add nodetool-ai/nodetool --skill motion-curves -a claude-code

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

GitHub CLI
$ gh skill install nodetool-ai/nodetool motion-curves --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/nodetool-ai/nodetool.git skills-src && mkdir -p .claude/skills && cp -r skills-src/packages/system-skills/motion-curves .claude/skills/motion-curves && 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
motion-curves
GitHub stars
560
Token cost
~3.2k tokens
SKILL.md length
1,367 words
Files
1
Skills in repo
127
Repo updated
First seen
Licence
AGPL-3.0

At a glance

Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once.

  • Style-property animation
  • SKILL.md covers When to write one, Curves by hand, The channels, and how they… and Style curves in a document, plus 4 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md
  • Motion a preset cannot express

What it does

Motion Curves is an agent skill from nodetool-ai/nodetool. Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once. Use for overshoot, bounce, anticipation, arcs, path draw-on, style-property animation, or motion a preset cannot express.

Its SKILL.md is about 3.2k 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 Media & Creative. It works with JavaScript. The repository describes itself as: Agent-first Creative Workspace. The licence is AGPL-3.0.

When your agent uses it

  • Style-property animation
  • Motion a preset cannot express

Example prompts

  • “/motion-curves”

What it can do on your machine

Read from SKILL.md and the folder at commit 339f069. 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

    No scripts in the folder and no shell commands in SKILL.md (its code samples are javascript and json).

    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

Motion Curves loads about 3.2k tokens when it runs. Until then it costs about 57 tokens; SKILL.md has 1,367 words of instructions outside code blocks.

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

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 nodetool-ai/nodetool at commit 339f069, republished under its AGPL-3.0 licence (© nodetool-ai). 1,367 words, ~3,164 tokens.

Download SKILL.mdSave it as .claude/skills/motion-curves/SKILL.md (or your agent's skills folder).
name
motion-curves
description
Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once. Use for overshoot, bounce, anticipation, arcs, path draw-on, style-property animation, or motion a preset cannot express.
featured
true

Motion Curves → animation nobody had a preset for

An animate_clip input with {"preset":"custom"} carries exactly one of curves (keyframes you write) or code (a JS body baked into curves once, host-side). Add mask when a curve drives wipeProgress. A stored document animation can instead carry styleTracks or textAnimator without numeric custom curves.

motion-graphics carries the op contract and the preset catalog this file is the escape hatch from. motion-principles gives the duration and the shape a curve should hit before you write its keyframes.

For numeric custom curves, call edit_timeline with an animate_clip op or the in-product ui_timeline_animate_clip tool. Both accept the flat curves or code input shown below. For stored styleTracks, textAnimator, or beat, read get_timeline, edit the full clip animation in its document, then call set_timeline_document with every existing document field. The animate_clip input does not author those stored fields.

When to write one

Write curves as soon as a preset is close but not right. The catalog is seven entrance shapes and a handful of beats — where motion starts, not where it stops — and a piece whose every title arrives with pop reads like a template. A curve costs one field and a few numbers, and the recipes below are the shapes worth reaching for: an entrance that overshoots twice, a hold in the middle of a move, two channels on different schedules, a decay, an arc, a wiggle, a path draw-on.

Two shapes need no curve, and writing one anyway only makes the document harder to read: a single overshoot and a physical settle are already easing strings. cubic-bezier(0.16,1,0.3,1) and spring(180,12,1) on an ordinary preset do both. Everything past that is this file.

Curves by hand

json
{"op": "animate_clip", "target": "Title", "animations": [
  {"role": "in", "preset": "custom", "durationMs": 600,
   "curves": [
     {"property": "offsetY", "keyframes": [
       {"t": 0, "value": 80}, {"t": 0.7, "value": -12, "easing": "easeOut"},
       {"t": 1, "value": 0, "easing": "easeInOut"}]},
     {"property": "opacity", "keyframes": [
       {"t": 0, "value": 0}, {"t": 0.4, "value": 1}]}]}]}

t runs 0..1 over the animation's window, not the clip. easing on a keyframe eases the segment ending there. Keyframes are sorted, t is clamped to 0..1, and a curve that stops short of either end is extended by holding its end value — so sampling 0.05..0.95 does what it looks like.

Limits: 16 curves per animation, one per property, 4096 keyframes each.

durationMs defaults to the clip's own duration, so curves span the whole clip unless you say otherwise.

The channels, and how they combine

Several animations can drive one channel. How they fold decides whether the second adds to the first or throws it away.

FoldChannelsIdentityResult
addoffsetX, offsetY, rotation (radians), blur (px), brightness (−1..1), hue (deg), temperature, tint0Values sum
multiplyscale, scaleX, scaleY, opacity (0..1), saturation (0..4), contrast (0..4)1Values multiply
minwipeProgress (0..1)1The tightest wipe wins
replacepositionX, positionY (canvas px), anchorX, anchorY (0..1), trimStart, trimEnd (0..1)noneThe last animation in document order wins; the other is discarded

The replace row is the trap. Two animations driving positionX over the same instants means one does nothing, reported as replace_curves_overlap. Move a clip with offsetX when it should compose with other motion, and with positionX only for an absolute placement nothing else touches.

Land the last keyframe on the identity value unless you mean the clip to stay where the curve stopped — and remember the role decides whether it stays: an in window holds its t: 0 values before it and contributes nothing after, an out window holds its t: 1 values after it, emphasis holds neither, and loop repeats its cycle, so a loop curve must end on the value it started on.

list_animation_presets reports this table from the engine. Read it there rather than from memory when a channel's range matters.

Style curves in a document

Use styleTracks on a stored animation when the target is a visual property rather than a numeric motion channel. The target names a clip style such as text.color, shape.cornerRadius, mask.featherPx, or clip.borderRadius; an effect target is effect.<effect-id>.<numeric-field> or a supported effect color such as effect.bg-field.colorB. A target needs the matching style, mask, or effect on the clip. Color values interpolate through the supported CSS color parser. shape.d and mask.d paths need matching commands and point counts. Glyph targets need a text stagger. validate_timeline reports an invalid target, incompatible path, or unsupported color.

This is one complete stored animation object to merge into an existing clip's animations array before the full-document write:

json
{"id":"round-in","role":"in","preset":"fade","durationMs":600,"styleTracks":[{"target":"clip.borderRadius","keyframes":[{"t":0,"value":0},{"t":1,"value":30,"easing":"easeOutExpo"}]}]}

styleTracks can accompany a preset, so a title can fade while its color changes. The id is required in the stored document; animate_clip creates one for its own inputs. Use motion-graphics for links, text animators, beat binding, and the document write boundary.

Show full SKILL.md (625 more words)Show less

The code body

A JavaScript body run once, at author time, in the QuickJS sandbox. It returns keyframes; the keyframes are stored on the clip; every renderer samples those. Nothing evaluates per frame, which is why a body cannot react to playback and why the same animation looks identical in the preview, the export and the headless compositor.

The bake is hermetic: no toolbelt, no nodetool.*, no network, no secrets. Just arithmetic, a 10-second ceiling, and inputs:

inputs fieldWhat it is
rolein, out, emphasis or loop
durationMsThe animation's own window
clipDurationMsThe clip it sits on
canvasWidth, canvasHeightSequence pixels — turn a normalized distance into px here
paramsThe animation's params, untouched
staggerCountUnits a text clip splits into, or 0
sampleCountSuggested density for a continuous function

Return {curves} — the same shape as the JSON above — or {samples}, one bag per point in time:

js
const n = inputs.sampleCount;
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  samples.push({ t, offsetY: 80 * (1 - t), opacity: t });
}
return { samples };

Returning both is an error, as is returning neither. A property present on some samples and absent from others is an error rather than a hole — interpolating across the gap would invent motion nobody wrote. A sample needs a finite numeric t; easing on a sample is optional and applies to the segment ending there. At most 4096 samples.

Because the bake runs once, a body using Math.random is frozen at the value it drew: the result is stable forever after, but re-baking gives different motion. Seed your own generator when the same body has to produce the same wiggle twice.

Recipes

Overshoot and settle. Two overshoots, the second smaller — what a single easeOutBack cannot express.

js
const p = [[0, 0.6], [0.45, 1.09], [0.68, 0.97], [0.85, 1.02], [1, 1]];
return { curves: [{ property: "scale",
  keyframes: p.map(([t, value]) => ({ t, value, easing: "easeInOut" })) }] };

Decaying bounce. Hops that die out and land exactly at rest.

js
const n = inputs.sampleCount, h = 0.06 * inputs.canvasHeight, hops = 3;
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  samples.push({ t, offsetY: -h * Math.abs(Math.sin(Math.PI * hops * t)) * (1 - t) });
}
return { samples };

Anticipation. A counter-move before the action, 60–120ms of the window.

js
return { curves: [{ property: "offsetY", keyframes: [
  { t: 0, value: 0 }, { t: 0.18, value: 10, easing: "easeInOut" },
  { t: 0.8, value: -4, easing: "easeOut" }, { t: 1, value: 0 }] }] };

Shaped wiggle. For a steady seeded wiggle, use a stored animationLinks entry with kind: "wiggle" as described in motion-graphics. Bake a curve when its amplitude must fade in or out. This seeded generator reproduces the same shape on a re-bake.

js
const n = inputs.sampleCount;
const amp = Number(inputs.params.amplitude ?? 6);
let seed = Number(inputs.params.seed ?? 1);
const rand = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648 - 0.5;
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  const fade = Math.min(1, 4 * t, 4 * (1 - t));   // rest at both ends
  samples.push({ t, offsetX: rand() * amp * fade, offsetY: rand() * amp * fade });
}
return { samples };

Inertia. A trailing part that lags the body and catches up — follow-through without a second clip.

js
const n = inputs.sampleCount, lag = 0.12;
const ease = (t) => 1 - Math.pow(1 - t, 3);
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  const behind = ease(Math.max(0, t - lag) / (1 - lag));
  samples.push({ t, offsetX: (1 - behind) * -0.1 * inputs.canvasWidth });
}
return { samples };

Arc. Straight-line travel looks mechanical. Drive one axis linearly and the other as a shallow parabola over the same window; both fold as add.

js
const n = inputs.sampleCount;
const dx = 0.25 * inputs.canvasWidth, rise = 0.06 * inputs.canvasHeight;
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  samples.push({ t, offsetX: -dx * (1 - t), offsetY: -rise * 4 * t * (1 - t) });
}
return { samples };

Pendulum. A decaying rotation, in radians.

js
const n = inputs.sampleCount, a = 0.12, cycles = 2.5;
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  samples.push({ t, rotation: a * Math.cos(2 * Math.PI * cycles * t) * (1 - t) });
}
return { samples };

A loop with no visible join. Any loop curve must return to its start, so build it from a whole number of cycles of a periodic function.

js
const n = inputs.sampleCount, amp = 0.02 * inputs.canvasHeight;
const samples = [];
for (let i = 0; i < n; i++) {
  const t = i / (n - 1);
  samples.push({ t, offsetY: -amp * Math.sin(2 * Math.PI * t) });
}
return { samples };

Path draw-on. trimEnd 0 to 1 on a path shape clip. Both trim channels fold as replace, so one animation drives each and their windows must not overlap.

js
return { curves: [{ property: "trimEnd",
  keyframes: [{ t: 0, value: 0 }, { t: 1, value: 1, easing: "easeInOut" }] }] };

Wipe with a shaped edge. A wipeProgress curve needs the mask on the same animation, because direction and softness cannot be inferred:

json
{"role": "in", "preset": "custom", "durationMs": 700,
 "mask": {"direction": "left", "softness": 0.1},
 "curves": [{"property": "wipeProgress", "keyframes": [
   {"t": 0, "value": 0}, {"t": 0.6, "value": 0.8, "easing": "easeOut"},
   {"t": 1, "value": 1}]}]}

Failure modes

  • A body returning nothing, or both shapes, fails the bake with the reason. Read it: the bake returns the error and the body's logs rather than throwing.
  • A property name outside the channel list fails the bake and names the ones it will take.
  • A curve driving wipeProgress with no mask is refused.
  • An easing string the grammar cannot read eases linearly and shows up as unknown_easing on validate_timeline. It is easeOut, not ease-out.
  • A window that does not fit the clip after its delay is animation_exceeds_clip — the motion is clamped or never runs.
  • A runaway loop hits the 10-second ceiling. A curve generator is arithmetic over a few hundred points; if it is slow, it is wrong.

Check the shape you wrote

Curves are the one place where reading the numbers is not enough. Use preview_timeline_frame with a range across the animation's window and sheet: true, which tiles the sweep into one labelled contact sheet — the way to watch an overshoot actually overshoot rather than checking three instants that all happen to look right.

© nodetool-ai, AGPL-3.0. 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/system-skills/motion-curves of nodetool-ai/nodetool.

Open the folder on GitHubat commit 339f069

Compare with similar skills

Motion Curves 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.

Motion Curves compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Motion Curves this skillnodetool-ai/nodetool560—~3.2kAutomated safety check: PassAGPL-3.0
Dotlottie WebLottieFiles/dotlottie-web892—~3.5kAutomated safety check: PassMIT
Speech To Texttadaspetra/loop2962 repos~2kAutomated safety check: PassMIT
HTML DesignNimaChu/html-design101—~2.7kAutomated safety check: PassNone
Explainroovincentsch/explainroo515—~439Automated safety check: PassMIT
OneWorks 3D Avatar Designeroneworks-ai/avatar251—~1.7kAutomated safety check: PassMIT

Similar skills

  • Dotlottie Web

    LottieFiles/dotlottie-web

    Implement Lottie animations using dotLottie runtimes (@lottiefiles/dotlottie-web and @lottiefiles/dotlottie-react).

    892 GitHub stars~3.5k tokensUpdated 3 days ago
    Media & CreativeAuto-check passed
  • Speech To Text

    tadaspetra/loop

    Transcribe audio to text using ElevenLabs Scribe v2. An agent skill from tadaspetra/loop.

    296 GitHub starsUsed in 2 repos~2k tokens
    Media & CreativeAuto-check passed
  • HTML Design

    NimaChu/html-design

    Create, redesign, repair, validate, preview, and package lightweight standalone HTML deliverables.

    101 GitHub stars~2.7k tokensUpdated 1 mo ago
    Media & CreativeAuto-check passed
  • Explainroo

    vincentsch/explainroo

    Make an explainer video (MP4) with a voice-over using explainroo.

    515 GitHub stars~439 tokensUpdated 6 days ago
    Media & CreativeAuto-check passed
  • OneWorks 3D Avatar Designer

    oneworks-ai/avatar

    Creates, refines and exports editable OneWorks 3D geometric avatars for mascots, bots or agents, keeping one shared scene state across preview, share link and export.

    251 GitHub stars~1.7k tokensUpdated 1 mo ago
    Media & CreativeAuto-check passed
  • Deepgram JS Audio Intelligence

    deepgram/deepgram-js-sdk

    A skill your agent uses when writing or reviewing JavaScript/TypeScript in this repo that calls Deepgram audio analytics overlays on /v1/listen - summarize, topics, intents, sentiment, diarize…

    276 GitHub stars~1.5k tokensUpdated yesterday
    Media & CreativeAuto-check passed

More from nodetool-ai/nodetool

All 127 skills in this repo
  • Beat Sync Editing

    nodetool-ai/nodetool

    Cut a NodeTool timeline to music and shape its pacing — detect the beat grid, place cuts on phrases, pick a cut type, build speed ramps with time remap, and give the piece an arc.

    560 GitHub stars~2.6k tokensUpdated today
    Auto-check passed
  • Caption Titles

    nodetool-ai/nodetool

    Add and animate a consistent text layer on an existing NodeTool timeline.

    560 GitHub stars~1.9k tokensUpdated today
    Auto-check passed
  • Color Motion

    nodetool-ai/nodetool

    Choose and animate colour on a NodeTool timeline, including shape and text gradients, colour grades, 3D LUTs, and dither.

    560 GitHub stars~2.3k tokensUpdated today
    Auto-check passed
  • Commercial Beat Sheet

    nodetool-ai/nodetool

    Write a shootable, precisely timed commercial beat sheet and store it as a NodeTool storyboard, with a consistent entity roster behind every shot.

    560 GitHub stars~4.6k tokensUpdated today
    Auto-check passed
  • Elevenlabs Audio Prompting

    nodetool-ai/nodetool

    Direct ElevenLabs speech, dialogue, sound effects and music — the bracketed audio tags v3 acts on and why the voice decides whether a tag lands, stability as the delivery dial, punctuation instead…

    560 GitHub stars~1.9k tokensUpdated today
    Auto-check passed
  • Frame Composition

    nodetool-ai/nodetool

    Stage the frame on a NodeTool timeline — grids, focal placement, safe areas per aspect ratio, depth layers and parallax, camera moves, and where elements enter and leave.

    560 GitHub stars~3.5k tokensUpdated today
    Auto-check passed

Works with

Questions about Motion Curves

What does Motion Curves do?

Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once. Motion Curves is an agent skill from nodetool-ai/nodetool. Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once.

When should I use Motion Curves?

Motion Curves fits situations like: style-property animation; motion a preset cannot express.

How do I install Motion Curves in Claude Code?

Run `npx skills add nodetool-ai/nodetool --skill motion-curves -a claude-code`. Or copy the skill folder (packages/system-skills/motion-curves in nodetool-ai/nodetool) into .claude/skills/motion-curves in your project. Claude Code loads it when a task matches its description.

How do I install Motion Curves in Codex?

Run `npx skills add nodetool-ai/nodetool --skill motion-curves -a codex`. Or copy the skill folder (packages/system-skills/motion-curves in nodetool-ai/nodetool) into .agents/skills/motion-curves in your project. Codex loads it when a task matches its description.

Can I use Motion Curves 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 nodetool-ai/nodetool --skill motion-curves -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/motion-curves, .gemini/skills/motion-curves, .github/skills/motion-curves and .opencode/skills/motion-curves in your project.

What does Motion Curves need to run?

SKILL.md names no scripts, command-line tools or credentials: Motion Curves is instructions for the agent only.

Does Motion Curves 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 Motion Curves 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 Motion Curves use?

Motion Curves is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Motion Curves use?

About 3.2k tokens (SKILL.md is roughly 13k 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 Motion Curves?

Skills that share tags, products or a category with Motion Curves: Dotlottie Web (LottieFiles/dotlottie-web, 892 stars), Speech To Text (tadaspetra/loop, 296 stars), HTML Design (NimaChu/html-design, 101 stars) and Explainroo (vincentsch/explainroo, 515 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Motion Curves?

nodetool-ai (a GitHub organization) maintains it in nodetool-ai/nodetool, which has 560 GitHub stars. The repository holds 127 skills in this directory. The repository was last updated on October 10, 2026.

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