GitHub - Kallin/motion-contact-sheet: Give a coding agent eyes for motion: capture a UI animation as a timed burst and tile it into one labeled contact sheet a vision model can read. · GitHub
/" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
/;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
Kallin
motion-contact-sheet
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
main
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>1 Commit<br>1 Commit
.claude/skills/contact-sheet
.claude/skills/contact-sheet
demo
demo
scripts
scripts
.gitignore
.gitignore
LICENSE
LICENSE
README.md
README.md
package-lock.json
package-lock.json
package.json
package.json
View all files
Repository files navigation
motion-contact-sheet
Give a coding agent eyes for motion. Capture a running UI animation as a timed<br>screenshot burst, tile it into one labeled contact sheet , and hand that single<br>image to a vision-capable agent so it can actually see how the motion looks, not<br>just that the tests pass.
Coding agents are blind to animation. They read the test log, watch it go green, and<br>never notice that the card snaps the last few pixels into place, or flashes for a frame<br>before it flies. A contact sheet turns "perceive motion over time" (which agents are bad<br>at) into "read one picture" (which they're good at).
Full write-up, with live demos: Giving Coding Agents Eyes for Motion
Install
npm install<br>npx playwright install chromium<br>npm link # optional: exposes `mcs-capture` and `mcs-sheet` on your PATH
Dependencies are just playwright (the burst) and<br>sharp (the tiling).
Use
Two steps: burst-capture the animation against a running page, then tile it.
# 1. capture (slow it down so the frames land on the motion, clip to what moves)<br>mcs-capture --url http://localhost:3000 --clip ".toast" --slowdown 6 --out frames/
# 2. tile into one labeled contact sheet<br>mcs-sheet frames/ --edges
Open frames/contact-sheet.png, or have your agent read it. A .md companion lands<br>next to it with a per-cell timing table that stays readable after the image is downscaled.
The three things that matter
Slow it down. A browser screenshot has a floor around 40-50 ms, so a 200 ms<br>transition gives you two or three usable frames. --slowdown 6 stretches the animation<br>so the burst can sample it; the sheet header records the factor and the labels report<br>native time. Slow it enough that the whole animation fits the capture window:<br>count x interval (wall-clock) needs to exceed duration x slowdown. It slows both<br>CSS/WAAPI animations and a GSAP global timeline (auto-detected), so it works whether your<br>motion is CSS transitions or GSAP tweens. This matters: getAnimations() can't see GSAP<br>tweens, so without the GSAP path a GSAP-driven app would capture at full speed (a blur)<br>while the sheet still claimed the slowdown.
Clip to the motion. --clip "" captures only the region that moves.<br>Without it the motion is a thumbnail in a sea of static chrome. The builder also<br>auto-crops to the bounding box of pixels that actually change.
--edges for end-state bugs. Snaps and flashes live in the first and last few<br>frames, exactly where motion-aware sampling is thinnest. --edges densifies the launch<br>and settle windows so they don't get skipped.
mcs-capture
flag<br>default<br>what
--url<br>(required)<br>page to capture (http(s):// or file://)
--clip<br>none<br>CSS selector for the region to clip each frame to
--play<br>none<br>JS to (re)start the animation, e.g. "document.querySelector('.btn').click()"
--slowdown<br>slow animations by N — both CSS/WAAPI (getAnimations()) and a GSAP global timeline if window.gsap is present (auto-detected, no extra flag)
--count<br>60<br>frames to capture (oversample; the builder keeps the meaningful ones)
--interval<br>80<br>target ms between frames
--viewport<br>1000x700<br>browser viewport, WxH
--out<br>frames<br>output directory
mcs-sheet
flag<br>default<br>what
(required)<br>directory of frame-*.png + a meta.json/timestamps.json sidecar
--select<br>12<br>keyframes to keep (--edges adds a few endpoint/launch/settle anchors on top)
--edges<br>off<br>densify the launch + settle windows (catch flash /...