motion-ad
GitHub用于制作15秒社交媒体动态广告的技能,基于真实产品截图和照片生成4:5或9:16比例的MP4。严格遵循品牌规范,确保所有宣传主张与目标页面一致,禁止虚构内容。支持从故事板到渲染的完整生产流程及版本迭代。
Trigger Scenarios
Install
npx skills add nodetool-ai/nodetool --skill motion-ad -g -y
SKILL.md
Frontmatter
{
"name": "motion-ad",
"featured": true,
"description": "Make a short sound-off motion-graphics ad (a 15s Facebook, Instagram or TikTok ad, 4:5 or 9:16) from a product's real screenshots and photos, with every claim checked against the page it links to. Use for an animated promo, a kinetic-type ad, a 9:16 version of one, or a revision of one (swap the hero, change the CTA, re-render). For generated footage use product-commercial, or launch-commercial for a finished spot with voice and music from a product page; for a person on camera ugc-video."
}
Motion ad
A motion ad animates real product imagery and short headlines on the timeline.
Nothing in the picture is generated by default. The storyboard is the beat plan
and holds the real image each beat shows. The timeline carries the motion, and
render_timeline produces the MP4.
Load /storyboard-core first for the board contract. Load timeline-edit-ops
with load_skill before the first edit_timeline call: it owns every op shape
this skill quotes, and motion-graphics owns the build loop.
Rules that do not bend
- Every claim matches the destination page. Read the page before writing a word. A price, a count, a speed, a guarantee, a rating or "free" appears in the ad only if it appears there. When the user asks for copy the page contradicts, write it and name the mismatch once in the report.
- Real assets only. Use the user's files or images taken from their live site. Never invent a customer, a review, a rating, a result or a logo. A beat with no real proof to show is cut, not filled.
- Never fabricate a person. A beat that needs more pictures of someone than exist gets crops or mirrors of the real ones, and the report says so. A generated face is out even when the rest of the board uses a model.
- The brand decides the look. Colours, logo, tone and type come from the brand, not from the beat table below. Default to plain claims: no hype, no exclamation marks, no emoji unless the brand writes that way.
- Never overwrite a delivered version. Each revision is a new timeline
version and a new rendered asset with a versioned name
(
<brand>-<topic>-ad-4x5-v2).
Production policy
The canonical machine-readable policy is the shot's production requirement.
For social ads, choose media_strategy deliberately:
still_motion_graphicsis the default. Preserve the source image and build motion, type, shapes and transitions in the Timeline.hybriduses generated/existing footage for atmosphere or physical motion while product, logo and exact copy remain separate protected layers.generated_videois reserved for actor performance, lifestyle movement or scene motion that materially benefits from video generation.
Put source truth in protected_inputs. Product/logo/source assets carry their exact asset_id; copy and brand colours carry their exact value. Declare only non-destructive allowed_transformations: position, scale, crop, rotate, mask, opacity, composite. Replacing or regenerating a protected asset, changing exact copy or colour, or applying an undeclared transform breaks the policy. The render tools enforce part of it: render_storyboard_stills refuses a selection holding a shot with a protected asset, and render_storyboard_clips refuses a still_motion_graphics shot. The rest is yours to keep on the timeline.
Do not use generated_video merely because a keyframe exists. Price cards, packshots, feature callouts, carousels, comparisons, typography hooks and CTA endings normally stay still_motion_graphics.
The loop
- Research the page and the brand.
- Collect the assets and pick the hero pair.
- Write the board: one shot per beat, the real image attached to each.
- Build the timeline: pictures, type, shapes, animations.
- Review preview frames and fix.
- Render, measure, report.
Stop where the request stops. "Just the storyboard" ends at step 3.
1. Research
Ask for, or infer from context: the product, the destination URL, the audience and the format. The format defaults to 4:5 feed (1080×1350). Stories and reels are 9:16 (1080×1920).
browser {url}on the destination page, then on the homepage and pricing page. Pull the headline promise, prices, rating and review counts, speed claims, and what is free versus paid. Record each fact with the page it came from.http_request {url}returns the raw HTML. Read the brand's hex values, font family and logo URL from its CSS variables or stylesheet links. Tokens in the source beat colours picked from a screenshot.take_screenshot {url, full_page: true}captures the live product UI. For software, that screenshot is often the best hero image. When it fails, say so once and continue with downloaded images.
A fact the page does not state is [CLIENT INPUT NEEDED], never a guess.
2. Assets
Every image the ad shows must be a stored asset before it goes on a board or a timeline.
- An image URL from the page:
save_asset {source: "<url>", name}copies it into the library and returns anasset_id.download_filewrites a workspace file instead, which is not an asset and cannot go on a timeline. - A user upload: find it with
asset_searchorlist_assets. Prefer the user's files over scraped ones when they offer any. - Logos: keep one for light backgrounds and, when the site has one, a white version for the brand-colour end card.
Look before choosing. view_image each candidate and note its pixel size.
Site images are often thumbnails, and a 400px image on a 1080px canvas reads as
soft. Say which images are low resolution in the report instead of upscaling
them silently.
Pick the hero pair for the clearest transformation. For a physical product, that is before and after. For software, it is the messy input and the finished output, as screenshots. Portrait images near 4:5 fill the hero and showcase slots best.
edit_image may crop, mirror or extend a real image to fit a slot. It may not
add a person, a product or a claim that was not in the source.
3. The board
create_storyboard {name, brief, style, aspect_ratio: "4:5"} (or "9:16").
Put the researched facts and their source URLs in brief so a later revision
can re-check them. Put the brand palette and type in style.
Add one shot per beat with edit_storyboard. Each shot carries:
slug:1a,2a, and so on. "Change the 3a headline" then means one shot.action: what is on screen, in plain words.notes: the exact on-screen copy for the beat, including line breaks.duration_seconds: the beat length from the table below.production: the policy from § Production policy, for example{"media_strategy": "still_motion_graphics", "protected_inputs": [{"id": "hero-after", "kind": "product", "asset_id": "<hero after>", "allowed_transformations": ["position", "scale", "crop", "mask"]}, {"id": "headline-2a", "kind": "exact_text", "value": "<copy>"}]}.
Then attach each beat's real image as its still:
{"storyboard_id": "<id>", "ops": [
{"op": "add_keyframe_version", "target": "2a", "asset_id": "<hero after>"}
]}
The shot moves to keyframe_ready with the real picture and no generation. A
still-only board is what the user reviews before any motion work.
Do not call render_storyboard_stills without targets on this board. It
selects every shot without a still, so a beat you forgot to attach would be
generated. Generate a shot only when the user asked for it, and only for a
background or texture, never for the product, a screen or a person.
Beats
Four or five beats in 15 seconds. One short headline per beat, at most about 20 characters per line, sized to read muted on a phone. The shape below is a starting point. Cut or replace a beat that does not fit the product. A desktop tool with no mobile app has no phone beat, so open on the hero instead.
| Slug | Time | Beat | Picture |
|---|---|---|---|
1a |
0–2.2s | Hook headline over the product in context: a phone or browser frame scrolling a real screenshot, a pointer or tap on the input | The screenshot |
2a |
2.2–5.4s | The tapped card grows into the hero. A scan line crosses it and reveals before becoming after. Two or three benefit chips | Hero before, hero after |
3a |
5.5–8.7s | The hero shrinks into a tilted wall of more real outputs | Four to nine outputs |
4a |
8.7–11.8s | Three showcase cards, optionally with a small "before" inset, and one proof line | Showcase images, a real rating or count |
5a |
11.8–15s | Brand-colour wipe to the end card: logo, CTA headline, one chip, the button | Logo |
Beat 4a loses its proof line when the page has no number to quote. Beat 3a
is cut when there are not enough distinct real outputs to fill a wall without
repeats.
4. The timeline
assemble_storyboard_timeline lays down rendered clips only and skips shots
that have a still and no clip, so it builds nothing here.
When the shots carry graphics intent (the exact copy and assets as elements),
finish_storyboard {storyboardId, expectedStoryboardRevision} writes the board as
editable timeline layers with no generation. Its default deterministic strategy
returns an unreviewed scaffold; layout_storyboard composes the static layout
with a vision model first. Review either through § 5 like a hand build.
storyboard-core § Still-motion-graphics boards has the arguments. Without
graphics on the shots, create the sequence directly:
{"name": "acme-scan-ad-4x5-v1", "width": 1080, "height": 1350, "fps": 30}
create_timeline with those arguments. Then create_timeline_version before
the first edit, and build with edit_timeline in batches of at most 60 ops.
Read get_state for track and clip ids before animating anything.
Tracks, top to bottom. A lower index draws on top, and add_track appends
at the bottom, so add them in this order: headlines, chips and buttons, the
scan line and wipes, pictures, background. Give every element whose timing
overlaps another its own track. Two clips that overlap on one track dissolve
into each other.
Pictures. add_media_clip {asset: "asset://<id>", trackId, startMs, durationMs, transform} per image. An image has no duration of its own, so
always pass durationMs. transform.position is in canvas pixels measured from
the frame centre ({x: 0, y: 0} is centred, positive y moves down), and scale
is a factor. Frame each image with frame-composition rules.
Type. add_text_clip per headline, with the brand colour and a
fontFamily NodeTool bundles: Inter, Space Grotesk, Bebas Neue, Playfair
Display, Lora or JetBrains Mono. When the brand font is not among them, use
the closest bundled family and say so. Put an accent word in its own text clip
in the accent colour, aligned to the line. What the copy says and when it
appears belongs to caption-titles.
Shapes. add_shape_clip draws the background, chips, the button, the scan
line and the brand-colour disc of the end-card wipe.
Motion per beat. One easing family and one timing unit for the whole ad,
fixed before the first animation (motion-direction). Durations and stagger
come from motion-principles.
| Beat | Build it with |
|---|---|
| Hook | Headline animate_clip in pop with a word stagger. The screenshot's scroll is a custom positionY curve inside a masked frame (set_mask). The tap is a small shape with pop then pulse |
| Reveal | The "after" image sits on the track above "before". animate_clip in wipe on "after", with the scan line moving on the same window and easing. Benefit chips enter with stagger_animations |
| Wall | Output images with rotationY and perspective on their transforms, entering with stagger_animations. A slow kenBurns loop keeps the wall alive |
| Showcase | Cards slide in from one side. The proof line enters last and holds at least 1.5s |
| End card | set_transition iris or a scaled brand-colour disc, then the logo per logo-reveal. The CTA holds still for the last second |
Sound is optional. The ad must work muted. When the user wants a bed,
find_model with text_to_music, load_skill the prompting_skill it
names, then generate_music at the exact runtime. Put cuts on its beats with
beat-sync-editing.
validate_timeline after every batch. Fix text_illegible,
text_backing_unproven, font_not_portable, stagger_compressed and
animation_exceeds_clip before looking at frames.
5. Review frames
preview_timeline_frame {timeline_id, times_ms, sheet: true}, then
view_image on the sheet. For the table above, sample
[300, 1900, 2700, 3800, 4900, 6600, 9800, 11500, 13600], plus the midpoint
of every entrance you added.
Look for:
- text overlapping text, or a headline under a shape it should sit above
- empty gaps where one beat ends before the next starts
- a label that contradicts its image ("after" on the before picture)
- the same image twice side by side in the wall
- a headline that cannot be read at thumbnail size
- a soft image at hero size
Name each defect, fix it, and preview the same timecodes again.
6. Render and report
render_timeline {timeline_id, preview_scale: 0.5, wait: true}for a draft while iterating.render_timeline {timeline_id, wait: true}at full size when the frames are right.analyze_videoon the result. Check duration, width, height and frame count against the plan.preview_timeline_frameat each transition to check the frames the stills review did not sample.
Report:
- the rendered asset and the timeline and board to open
- the copy, beat by beat
- every caveat: low-resolution sources, crops and mirrors, the font
substitution, copy that differs from the page, and any
[CLIENT INPUT NEEDED]left in the board - actual spend from
get_cost_summarywhen anything was generated
A second format
A 9:16 cut is its own timeline, not a crop. retarget_format derives a
sequence by cropping, which cuts headlines and chips placed for 4:5. Create a
1080×1920 timeline from the same board, re-space every vertical position
(headlines, frame, hero, carousel, end card), and review and render it the
same way. Name it <brand>-<topic>-ad-9x16-v1.
Revisions this loop handles
Swap the hero for the image I uploaded. Keep everything else.
Change the CTA to "Start free". Check the page says free first.
Make a 9:16 version.
The headline in 1a is too long. Two lines, same words.
Drop the proof line. The review count is out of date.
Re-render as v3. Keep v2.
Each one is create_timeline_version, the smallest edit that answers it, a
validate, a preview of the changed beats, and a new render with the next
version number.
Version History
-
97aad94
Current 2026-10-19 19:19
重构技能以对齐当前代码:更新工具名称、操作符和参数引用;将重叠的作业和技能指向统一的storyboard-core合同;解决冲突的时间参数;增加付费生成的权限门控检查。
- 85ff020 2026-09-28 10:24


