Communitygithub.com

kb114/motion-ad-studio

Use when asked to make a short motion-graphics ad or promo film (9:16 Reels/TikTok/Shorts, 4:5 feed). Builds a seek-safe HTML/GSAP film with every move locked to voice-over words and music beats, gates it by script, and learns each run. ElevenLabs optional.

What is motion-ad-studio?

motion-ad-studio is a Cursor agent skill that use when asked to make a short motion-graphics ad or promo film (9:16 Reels/TikTok/Shorts, 4:5 feed). Builds a seek-safe HTML/GSAP film with every move locked to voice-over words and music beats, gates it by script, and learns each run. ElevenLabs optional.

Works with~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/kb114/motion-ad-studio/tree/HEAD

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Motion Ad Studio (self-improving)

Make a 20-40 s vertical motion-graphics ad that a senior motion designer would sign: frame 0 already moving, one carrier object through the film, transitions cut mid-motion and velocity-matched, and every click, landing, drop and whoosh locked to a spoken word or a music beat. The film is a single HTML page (GSAP timeline + pure functions of time). It renders frame-accurately to MP4, and scripts check it before anyone watches it. Sound effects are placed from the film's own event list, so audio sync holds by construction rather than by ear.

Every run: read references/lessons.md and references/runs.md FIRST. Finish with the Retrospective (step 13). The skill improves only if you write the lessons back.

0. Preflight (always)

python scripts/preflight.py [--elevenlabs-mcp yes]   # pass yes if YOU have ElevenLabs tools connected

Paths used below: scripts/..., references/... and templates/... are relative to this skill's folder ($SKILLS/motion-ad-studio). $MOTIONMAXXING is the engine folder ($SKILLS/motionmaxxing; preflight prints it), and $SEAM_GATE is the optional seam-gate script that preflight finds (or pass --seam-gate FILE).

NeedWhyIf missing
motionmaxxing engine (Apache-2.0)render.mjs (frames → MP4), look.py gates, lint.mjs design lint, runtime/vendor/gsap.min.jsgit clone https://github.com/Tejashmakwana/motionmaxxing next to this skill, or set MOTIONMAXXING
ffmpeg + ffprobe, Node 18+, Chrome/Chromium/Edgerender, probe, mixinstall; set CHROME_PATH if Chrome is not auto-found
Python 3.9+ with numpy + Pillowbeats, synth bed, mix, sheetspip install numpy pillow
faster-whisper (optional)word timestamps for word-locked timingpip install faster-whisper, or any aligner that gives word start/end times
seam gate (optional)proves each transition is velocity-matchedHyperFrames motion-doctrine + patches/seam-gate-camera-overlap.patch
ElevenLabs (optional, recommended)expressive VO, composed music, custom SFXsee the next section

ElevenLabs is optional: notify the user once, never block

If preflight prints the NOTICE (no API key and you have no ElevenLabs tools), relay it to the user in their language before the audio step, in one short message, and let them choose:

  • connect: they add an ELEVENLABS_API_KEY or connect the ElevenLabs MCP server. Re-run preflight.
  • continue: build the full film with the no-key path below. Say once in the delivery note that ElevenLabs would lift VO and music quality. Do not ask again in the same project. If you cannot ask (batch or non-interactive run), continue without it and put the recommendation in NOTE.md.
LayerWith ElevenLabsWithout it (still ships)
Voice-overTTS with emotion tags, 2 takes, pick onethe user's own recording (best), or a local/OS TTS guide voice; or a music-led film with no VO
Musicmusic generated to the film length, drop on the payoffscripts/synth_bed.py (procedural, exact beat grid), or a track the user has the rights to
SFX1-3 hero samples (whip, whoosh, typing)scripts/mix_events.py synthesizes every SFX locally
Word timesspeech-to-text with timestamps (or TTS timestamps)scripts/words.py (local faster-whisper)

Details, prompts, credit costs and licence rules: references/audio-providers.md.

Pipeline

  1. Workspace. python scripts/new_film.py <project> copies the one-take kit, the GSAP runtime and local fonts. Render one still of the untouched demo to prove the toolchain: node $MOTIONMAXXING/scripts/render.mjs <project>/film/index.html --still 1.2,16.4 <project>/stills.

  2. Brand kit. Get the official logo files from the user and crop each to its alpha bbox. If a carrier will dock into the logo, measure the sub-glyph rects. Take colours by counting #hex in the site CSS and fonts as local woff2 (scripts/fetch_fonts.py). Use the CTA channel the brand actually uses. Never redraw or approximate a logo: ask for it.

  3. One idea + a new style. Write one idea only this brand could film. The cover-the-logo test: hide the logo and the film should still point to this brand. Write three directions, kill two, and pick a style DIFFERENT from the last row of references/runs.md.

  4. Hook. Write 3 variants under 12 words (question, negation, specificity, story) and pick one. Frame 0 is already busy and moving, the hook is on screen by frame 1, and nothing fades up from black.

  5. Voice-over. Write about 2.5 words/s (30 s ≈ 70-75 words). If the TTS supports emotion tags, mark each line ([curious] [confident] [energetic] [playfully] [warmly]). Generate or record it, trim long pauses, fit the length with atempo ≤ 1.1, then get word times: python scripts/words.py audio audio/vo.wav --lang <code>. Whisper gives you TIMES only. Caption text always comes from the approved script.

  6. Music. Generate (length + BPM + "drop at "), license a track, or run python scripts/synth_bed.py --bpm 120 --dur 32 --drop 15.8 --out audio/bed.wav. Get the grid with python scripts/beats.py audio/music.mp3 audio/beats.json (synth_bed writes an exact .beats.json). Trim the music head so the drop lands on the payoff VO word: film beat k = first_beat - trim + k·period.

  7. STORYBOARD.md + ledger.json (templates/one-take/ has a filled example). Include:

    • the page spec, the idea and the three directions;
    • a rhythm sheet (stretch / t / energy 0-10 / route / music);
    • a timing map: every event with the word w(i) or beat beat(k) it is locked to;
    • the VO and the list of illustrative data;
    • the ledger: one row per seam (exit/entry axis + direction, cut time).
  8. Build. Copy patterns from the kit (references/one-take-kit.md):

    • clocks w(i) / we(i) / beat(k);
    • per-frame order tl.seek → PRE (cameras, moving anchors) → cursors (read live rects) → UP (UI that follows);
    • keyframed 3D camera and a cursor engine that targets live DOM rects;
    • typing/text/class updaters as pure functions of t;
    • sfx() events and camera-derived whooshes.

    Contract the renderer needs: #main carries data-composition-id="main" data-width data-height data-duration. window.__seek / __duration / __timelines / __events / __player / __ready / __playerReady / __renderReady. Montage-style films (layered scenes, flat UI, logo carrier): references/montage-kit.md.

  9. Look at it. Render 15-20 stills (node $MOTIONMAXXING/scripts/render.mjs ... --still t1,t2,... stills), tile them (python scripts/sheet.py stills sheet.jpg 6 220) and run one vision pass: one line per frame, then a defect list. Fix and repeat 2-3 passes.

    • Geometry truth (what is where, visible, its opacity) comes from node scripts/probe.mjs index.html "t1,t2" "#a,#b".
    • Text truth comes from a full-res crop: vision misreads downscaled Arabic and CJK as "typos" that are not there.
  10. Seam gate (recommended). Serve the film folder on localhost, then run node "$SEAM_GATE" verify --ledger ledger.json --url http://127.0.0.1:<port> --comp-url http://127.0.0.1:<port>/index.html. The target is 0 FAIL. Rows where a one-take camera whip keeps both windows on screen need "overlap": "camera" plus the patch in patches/. Stop the server afterwards. See references/qa-gates.md.

  11. Mix. Every SFX lives in window.__events ({t, type:'sfx', name, gain, dur?, speed?}).

    • Dump them with node $MOTIONMAXXING/scripts/render.mjs index.html --still <t> ev, which writes ev/events.json.
    • Mix: python scripts/mix_events.py --events ev/events.json [--vo vo.wav --vo-at 0.10] [--music bed.wav --music-trim s] --dur <s> --out audio/mix.wav [--music-db -24] [--tp -2.5] [--whip/--whoosh/--typing samples] [--lift t0:t1:dB] [--fade t].
    • Levels: VO at -18 dBFS RMS, music at -30 with ducking under the VO, whooshes peak-aligned to the camera's fastest frame, two-pass loudnorm to -14 LUFS / TP ≤ -1.
  12. Render + gates + deliver.

    • Render: node $MOTIONMAXXING/scripts/render.mjs index.html <NEW-name>.mp4 --audio ../audio/mix.wav --crf 17 --grain 0.03. Use a new output name for every attempt: two renders into one file corrupt it.
    • ffmpeg -v error -i x.mp4 -f null - must print nothing. Then run python $MOTIONMAXXING/scripts/look.py x.mp4 --out look --expect <dur> --expect-audio (G0/G2/G3/G5 PASS) and node $MOTIONMAXXING/scripts/lint.mjs index.html.
    • Deliver: the 9:16 master, a 4:5 crop (crop=1080:1350:0:200 once the layout is checked), 2-3 cover PNGs, NOTE.md (idea, timing map, real vs illustrative data, audio sources + licences, gate numbers, what to improve) and a project/ folder (film + audio + scripts).
    • Clean up: stop servers and orphan headless Chrome.
  13. Retrospective (MANDATORY, before your final reply). Append dated rule + why lessons to references/lessons.md. Add a row to references/runs.md (date, product type, style, length, gate numbers, verdict when known). Patch this file, a script or the kit if a step changed. When the user reacts later, record the verdict. Lessons hold method only, never client names or private data.

Quality bar

  • Hook: real customer phrasing or a curiosity gap. Frame 0 moving, the hook readable by frame 1, no fade from black.
  • Continuity: one carrier through the film (cursor, chip, logo mark), no crossfades, and 2-3 transition types repeated. Cuts happen mid-motion and velocity-matched. Keep one current direction: for "next", RTL films travel content left→right and LTR films right→left. Reserved vectors are spent on meaning.
  • Timing lock: every click, drop and landing sits on w(i) or beat(k), and the music drop sits on a VO word. Whooshes derive from camera velocity. The timing map lists each event and what it is locked to.
  • No idle wobble: every hold has a camera move with intent, a staged reveal or live UI. Stillness belongs only right before a payoff.
  • Safe zones (1080×1920):
    • paid Meta placements: readable text in y 270-1250 and x 60-960 (the right 120 px is buttons);
    • organic Reels/TikTok: keep the bottom 320-370 px clear;
    • put the caption rail under the UI panels, never over a composer;
    • drop rail words the picture already shows.
  • End: wordmark plus one CTA in the brand's real channel. Hold the end 0.6-1.4 s and keep the final shot ≤ 25 % of the film.
  • Honesty: declare illustrative data in NOTE.md. Never invent performance claims, prices or testimonials.
  • Targets: look.py G0/G2/G3/G5 PASS, motion energy ≥ 5/255 mean luma change (dark UI films sit near 2), cut-sync ≥ 35 % (the measured pro benchmark), -14 LUFS ± 0.5 with true peak ≤ -1 dBTP, 0 decode errors.

Platform notes

  • Background jobs on Windows git-bash: call node.exe, not the node winpty alias ("stdin is not a tty").
  • An ESM import of an absolute Windows path needs file:///C:/....
  • Headless Chrome leaks from interrupted runs. At the end, list browsers started with --headless=new and --remote-debugging-port=0 whose parent is gone, and kill the root with its tree.
  • Render speed: flat UI ≈ 4 min per 35 s at 1080×1920. A 3D world ≈ 4-5 min at ~4 fps, but only if the 3D layer count stays small.

Files

  • scripts/:
    • preflight.py: dependencies and ElevenLabs notice;
    • new_film.py: scaffold a project;
    • fetch_fonts.py: local woff2;
    • words.py: word times;
    • beats.py: beat grid and drop;
    • synth_bed.py: no-key music;
    • mix_events.py: events-driven mix;
    • sheet.py: contact sheet;
    • probe.mjs: live geometry.
  • templates/one-take/: the full one-take 3D camera film as a runnable demo (fictional product, Arabic RTL copy), with STORYBOARD.md and ledger.json.
  • references/:
    • research.md: landscape survey, upstream skills, security notes, platform specs;
    • lessons.md, runs.md: the self-learning memory;
    • audio-providers.md, one-take-kit.md, montage-kit.md, qa-gates.md.
  • patches/seam-gate-camera-overlap.patch: opt-in seam-gate rule for one-take camera films.

Related Skills