--- name: motion-ad 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, for a person on camera ugc-video." featured: true --- # 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 `motion-graphics` with `load_skill` before the first `edit_timeline` call. It owns every op shape this skill quotes. ## 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 (`--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_graphics` is the default. Preserve the source image and build motion, type, shapes and transitions in the Timeline. - `hybrid` uses generated/existing footage for atmosphere or physical motion while product, logo and exact copy remain separate protected layers. - `generated_video` is 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` such as position, scale, crop, rotate, mask, opacity and composite. A finished-cut plan must pass `validateProductionMaterialization`: replacing or regenerating a protected asset, changing exact copy/colour, or applying an undeclared transform is a policy error. 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 1. Research the page and the brand. 2. Collect the assets and pick the hero pair. 3. Write the board: one shot per beat, the real image attached to each. 4. Build the timeline: pictures, type, shapes, animations. 5. Review preview frames and fix. 6. 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 because no browser is configured, 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: "", name}` copies it into the library and returns an `asset_id`. `download_file` writes a workspace file instead, which is not an asset and cannot go on a timeline. - A user upload: find it with `asset_search` or `list_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. Then attach each beat's real image as its still: ```json {"storyboard_id": "", "ops": [ {"op": "add_keyframe_version", "target": "2a", "asset_id": ""} ]} ``` 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. Create the sequence directly: ```json {"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://", trackId, startMs, durationMs, transform}` per image. An image has no duration of its own, so always pass `durationMs`. `transform.position` is in canvas pixels 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 1. `render_timeline {timeline_id, preview_scale: 0.5, wait: true}` for a draft while iterating. 2. `render_timeline {timeline_id, wait: true}` at full size when the frames are right. 3. `analyze_video` on the result. Check duration, width, height and frame count against the plan. 4. `preview_timeline_frame` at 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_summary` when 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 `--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.