A Claude Code skill: turn a still photo into a cinemagraph, a mostly-static image with one small looping or one-shot motion, and wire it into a page section so the motion plays once the image scrolls into view, then resets to the still frame when it scrolls back out.
Works for more than portraits: a person winking, flowers opening, a product label coming to life on a shop page, water rippling, a hand raising a cup. Anything where the rest of the frame stays locked and one contained thing moves.
Same skill, same pipeline, works just as well for product marketing as it does for portraits or nature shots. All four base photos below, including the fictional brands and labels, are fully AI-generated too:
![]() A case hinging open |
![]() A can cracking open |
![]() Me, holding a product that doesn't exist |
![]() Beer pouring into a glass |
Live, scroll-triggered versions: tobeworks.de/skills/cinemagraph-section
- Astro, Vue, React, or WordPress — ready-made example components for
all four under
examples/, same logic, each translated idiomatically. No hand-translating the vanilla JS version yourself. - Accessible by default — the video is
aria-hidden, the photo carries the alt text.prefers-reduced-motionis checked before anything loads, not just before playing. Runtime under 5s keeps it exempt from WCAG 2.2.2. - SEO is not an afterthought — lazy-loading, explicit image dimensions
against layout shift, real
alttext required. The photo carries all the SEO weight, the video is a bonus layered on top. - No photo? No problem — the examples above are fully AI-generated, fictional brands and labels included. The skill generates a fitting base image itself if none exists yet, before animating it.
- Deliberately simple, on purpose — a hand-rolled loading state
machine looked more sophisticated but broke in production more than the
plain version does. Real
<source>tags in the markup, the nativeplayingevent, two gotchas solved (not four), and a documented reason not to reintroduce the complexity, see "Why static<source>tags" in EMBED-PATTERN.md. - MIT-licensed, fully open — read it, change it, fork it. No hidden dependency on a service or on me.
- Generates the clip — calls the Seedance API (api.seevio.ai) directly from a local photo, no manual prompt-copying into another tool. Submits the job, polls for completion, downloads the result, converts it to webm alongside the mp4.
- Wires it into the page — a battle-tested scroll-triggered crossfade
pattern (static image ↔ video), kept deliberately simple: real
<source>tags in the markup, not JS-injected ones, and the browser's nativeplayingevent instead of a hand-rolled loading state machine. Two gotchas that actually bite in practice already solved: don't observe adisplay:noneelement withIntersectionObserver, and don't reset the animation at the same scroll position that triggered it or it visibly flickers.
Copy this folder into ~/.claude/skills/ (all projects) or
<project>/.claude/skills/ (one project):
git clone https://github.com/Tobeworks/cinemagraph-section.git ~/.claude/skills/cinemagraph-sectionNeeds jq and ffmpeg installed, plus a Seedance API key
(SEEDANCE_API, get one at seevio.ai).
Ask Claude Code something like: "Turn this photo into a cinemagraph, the flowers should slowly open, and wire it into the hero section so it plays when it scrolls into view."
Full details: SKILL.md (workflow), API-REFERENCE.md (Seedance endpoint contract), EMBED-PATTERN.md (the scroll-crossfade markup/script on its own, framework-agnostic).
MIT




