---
name: ideatomotion-video
description: Create a video with Idea to Motion — a narrated explainer (reel 9:16 or longform 16:9), a silent motion graphic (on-screen text, no voiceover), or an upload-&-animate video from the user's own recording. Use when the user asks to create, make, or generate a video, short, reel, explainer, animated graphic, or to animate/edit a video they recorded. Requires the Idea to Motion MCP server to be connected.
---

# Idea to Motion — client-driven video workflow

YOU write ALL the creative content (script, scene directions, Remotion scene code); the platform does voice synthesis, transcription, timing, assembly, and rendering. No server-side model ever makes a creative decision, and every voice service (TTS and transcription) runs on the USER's own key.

## Start by letting the user choose: `get_creation_options({ topic, kind, format })`

Call this FIRST unless the user already named both a theme and a voice (or told you to just go). Hosts that render panels show a picker — every look as a playing 8-second preview clip, plus playable narrator samples — and the user creates the video from there, handing you the jobId; **do not also call `create_job`, that produces a duplicate.** Text-only hosts get the same options as text, so show them and call `create_job` with the user's pick. A video the user chose the look and voice for is the whole point; silently defaulting is the worst outcome.

## Then: `create_job({ kind, topic, format, theme? | themeId?, voiceId?, voiceSettings? })`

### Make it look like THEIRS, not like one of our swatches

The single highest-leverage thing you can do for a video's quality is give it the user's own colours. Pass `theme`:

```
theme: { style: "keynote", mode: "dark", seedColors: ["#4F46E5"], name: "Acme" }
```

- `style` is the LOOK — lighting, type, motion grammar and cut grammar, plus a runtime kit of motion primitives the scene code composes with. Prefer a look id: `keynote` (lit black field — launches, products), `kinetic` (inverting fields, whip cuts — shorts and hooks), `depth` (glass cards in 3D — SaaS, dev tools), `editorial` (warm paper, serif — stories, journalism), `blueprint` (technical drawing — architecture, how-it-works), `swiss` (grid + giant numerals — brand films), `riso` (two-ink print — culture, indie), `datanoir` (one amber metric — finance, data), `chrome` (liquid metal — music, gaming, fashion), `cutout` (paper shapes — education, friendly). Those are the BRAND & PRODUCT looks. The NEWS & ARTICLES looks turn a written story into a video the viewer watches instead of reads — picture-led, every beat footage first, credits carried through — and pair with `style: "article"`: `newsdesk` (the broadcast bulletin — breaking news, funding alerts), `broadsheet` (the newspaper in motion — features, opinion, long reads), `signal` (the social explainer — reels, quick takes), `ledger` (data journalism — funding, earnings, markets), `feed` (the digital startup desk — roundups, launches). The SOFTWARE & SYSTEMS looks draw a developer explainer as a system diagram that builds one node, wire and packet at a time under a headline that stays up, and pair with `style: "systems"`: `schematic` (the black-field dev explainer — APIs, containers, backend), `nightlab` (one teal stage light, neon outlines — security, agents, internals), `graphpaper` (paper, ink and one blue — tutorials, docs, decks), `isoboard` (the architecture on a tilted board — cloud, infrastructure), `sketchpad` (the Excalidraw whiteboard — design reviews, RFCs), `hardware` (real devices — a silver laptop, a cyan rack, a graphite router, packets on cables — networking, infrastructure). Every look has a rendered preview clip in `get_creation_options`; the user can watch it before choosing. The legacy structures (`flat`, `glassmorphic`, `terminal`, `cinematic`, `neon`, `isometric`, `sketch`) still work.
- `seedColors` are 1–3 REAL brand hexes, most important first. Get them from the user, their site, or their logo — **do not invent a scheme.** The platform derives the full palette from them and guarantees every colour stays readable on the canvas, so you never have to reason about contrast.
- It is **saved automatically**. Every later video reuses it with no `theme` and no `themeId` at all, so ask once and never again.

**Brand kit — set it once, before the first video.** `set_brand_kit({ url })` reads the user's website deterministically (colours from theme-color and CSS brand tokens, faces from Google Fonts links, the header logo) and saves it; after that every `create_job` — any look, any kind — wears the brand's colours and fonts and carries the logo as a reference asset, with no `theme` argument at all. Report what was found and let them correct a swatch; `get_brand_kit` / `clear_brand_kit` read and remove it.

Use `themeId` instead when the user picks a preset from `get_creation_options`, or to re-select one of their saved looks (saved-look ids and preset ids both go in that one field). **Omit both** to inherit the look they already established — that is the right default for a returning user.

Pick the `kind`:
- **explainer** — narrated voiceover video. YOU write a SCRIPT. Needs a voice key (set_voice_key) for non-allowlisted accounts. The narrator is ANY voice on the user's own provider account — the ids `get_creation_options` lists, or a cloned/custom voice they name — and on ElevenLabs it is read by Eleven v3, the expressive engine: the script's `*cues*` are performed, not spoken, and `voiceSettings` tunes the read (stability 0 = expressive, 0.5 = natural, 1 = flat; speed 0.7–1.2). Pass `voiceSettings` on `create_job` or `approve` when the user says how it should sound ("calmer", "more energetic", "slower").
- **motion_graphic** — SILENT animated on-screen text, no voiceover. YOU write a SHOT LIST. No voice key, no social step. Great for a short overlay to drop into another video.
- **recording** — UPLOAD & ANIMATE. The user uploads their OWN talking-head/screen video; YOU review the transcript and choose which parts to keep. Their spoken audio is the narration (no script written), transcribed on their own voice key.

### Aspect and length are SEPARATE — infer both, don't ask

`aspect` is the canvas (`9:16` vertical or `16:9` widescreen; `format`'s `reel`/`longform` are the same setting under older names). `targetSeconds` is the length, and it works at EITHER aspect — a 15-second 16:9 clip and a 3-minute 9:16 video are both ordinary. Read both off what the user said:

| they say | aspect | targetSeconds |
|---|---|---|
| YouTube, explainer, tutorial, walkthrough | `16:9` | 60–180 |
| Short, Reel, TikTok, vertical | `9:16` | 15–45 |
| ad, bumper, intro, pre-roll | `16:9` | 5–15 |
| "deep dive", "full guide" | `16:9` | 300+ |

Then TELL them what you picked ("a 16:9 clip, about 15 seconds") — they can correct it before approving, and neither choice is recoverable once the voiceover is recorded against it.

## The front half is the SAME for every kind

0. `get_creation_options({ topic, kind, format })` → the user picks the look and narrator. If they create it in the panel you already have the jobId — skip step 1.
1. `create_job({ kind, topic, format, theme? | themeId?, voiceId?, voiceSettings? })` → returns a playbook + the first brief. Only when the user answered in chat rather than the panel. Pass `theme` with their brand colours if you know them; omit both to reuse their established look.
2. **recording only:** `attach_media({ jobId, role: "primary_recording" })` → gives a `curl` (local file) or a browser link (phone / chat attachment) to upload the video. The platform then detects silence and builds a cut plan — poll `get_job_status`. **If the user hasn't filmed yet and wants something to say, call `get_plan` BEFORE the upload** — it returns a teleprompter script brief (the same house script contract as a generated video: hook-strategy menu, beat arc, word budget). Write it, hand it to them in chat to read on camera, and never submit it — the submittable plan for this kind is the cut plan, after upload.
3. `get_plan({ jobId })` → the plan to write: a script brief (explainer), a shot-list brief (motion graphic), or the CUT PLAN (recording — the transcript split into keep/remove segments). For explainer/motion_graphic the brief also comes back from create_job; get_plan re-fetches it any time.
4. Write the plan, then `submit_plan({ jobId, ... })` — send `scenes` (explainer), `shots` (motion graphic), or `removeSegmentIndices` (recording; the indices to DROP, everything else kept). Fix any in-band validation errors and resubmit.
   **Cutting a silent motion graphic TO the music** — the "make the launch video hit on the beat" request. With a track attached, `get_plan` reports its bpm and bar length: write shot durations near multiples of a bar, then pass `beatSync: true` to `submit_plan`. Every boundary snaps onto the nearest beat (preferring downbeats) and the response NAMES each one it moved, or says why it snapped nothing. Each scene's code brief then lists the exact scene-local frames its beats land on, so the animation moves ON the beat instead of near it. Narrated kinds ignore `beatSync` — their timing comes from the voice, and making the animation serve the music instead puts the two in a fight the viewer hears.
5. Show the user the plan and WAIT for approval. If they want changes first, YOU make them — `get_plan` returns the current plan (for an explainer, the submitted script scene by scene) and `submit_plan` replaces it wholesale, so carry unchanged scenes through verbatim and revise as many rounds as they want. Then `approve({ jobId })` — starts production (explainer/motion graphic) or confirms the cuts (recording). Poll `get_job_status`.

Reference material (explainer/motion graphic): if the user has screenshots, a logo, b-roll, or a public link, call `attach_media({ jobId })` (upload link) or `attach_media({ jobId, url })` (public link), then `list_media`. Optional; never block on it.

**Background MUSIC (any kind).** A finished video has a bed under it, so give every video one unless the user says otherwise. `get_plan` lists the platform's CC0 music-library tracks already ranked for the job's look, genre and topic; pick the one whose mood fits what you are writing and call `attach_media({ jobId, role: "music", libraryTrackId })` — it plays immediately, no upload, and you tell the user which track you chose (they can audition and swap it in the editor's Music panel). If they mention their OWN track, a song, or background music they want, call `attach_media({ jobId, role: "music" })` for an upload link (or pass a public `url`). Uploading is all that is needed — the track plays under the whole video automatically, and on a narrated video it ducks by itself whenever anyone speaks. Do this BEFORE `submit_plan` on a silent motion graphic: that is the only kind whose TIMING can be cut to the music, and the plan is where the timing is decided. Level changes afterwards are `adjust_audio({ jobId, cueId: "music", gain })`.

## The back half is identical for every kind

6. DIRECTIONS: at `direction_generation`, `get_direction_brief({ jobId })` once for the system prompt, then per scene IN ORDER: `get_direction_brief({ jobId, sceneId })` → write → `submit_directions`. Use `resolve_brand_asset`/`resolve_scene_image` for real logos/photos — never invent URLs. **Name real products, and let them keep their own look.** When a beat shows something the viewer would RECOGNISE — a WhatsApp thread, a Slack channel, a Gmail draft, VS Code, GitHub, a terminal, an OS notification — write the visual as that product in ITS real colours ("WhatsApp's own green header and bubbles"), not as "a chat UI", and mark the beat `surfaceKind: "authentic"`. That is what stops the code stage repainting it in the video's theme, and it exempts the beat from the one-accent palette limit a real brand surface would otherwise fail. Everything you INVENTED to explain the point — charts, diagrams, stat cards, comparisons, headlines — is `"themed"` (the default) and wears the theme. The theme owns the stage; it does not own the inside of something real.
   **recording only — pick the STAGE per scene.** Each scene splits the frame between the speaker's footage and your graphics: add `stage: { preset }` to its `animationDirection`. Presets: `full_face` (speaker full-frame, your scene is a lower third), `full_motion` (graphics full-frame, speaker hidden — their voice keeps playing), `split` (graphics above, speaker below), `face_pip` (graphics full-frame, speaker in a corner box), `graphic_overlay` (speaker full-frame, one bold element on top). VARY it — the same preset three scenes running is what makes a video look templated — and match it to the content: dense diagram → `full_motion`, one-line payoff → `graphic_overlay`. Omit it and the server picks a sensible one. The scene brief tells you which stage is active and how much canvas you get. After a scene has code, `render_scene_preview` shows the SPEAKER composited into it — check their head is not cut off and your graphics aren't over their face, and fix it with `adjust_scene_framing({ jobId, sceneId, faceCrop: { x, y } })` (y=0 shows the top of the recording, y=1 the bottom; lower y when the head is cut off).
   **A scene can also CHANGE layout partway through** — hold a split while you explain, then push to the speaker for the line that lands: `stage: { preset: "split", keyframes: [{ preset: "full_face", atSecond: 10 }] }` (`atSecond` is scene-local). The platform cross-fades the speaker between layouts; your scene's CODE has to animate its own graphics out before the handover, and `get_code_brief` gives you the exact scene-local frame. Use it for a beat that genuinely changes register, not on a timer. Add or change one later — on any scene, including in a finished video — with `adjust_scene_framing({ jobId, sceneId, keyframes: [...] })`.
7. CODE (one isolated task per scene): do NOT write every scene inline in one chat — a long thread under-builds later scenes. If your environment can dispatch parallel tasks/agents, spawn ONE per scene; otherwise work sequentially, treating EACH scene as a fresh full-effort task. Per scene: `get_code_brief({ jobId, sceneId })` → write `function Main({ scene }) { ... }` (no imports/exports) → `submit_scene_code` (it validates + sandbox-runs and returns frames) → LOOK at the frames → `edit_scene_code` (surgical { oldStr, newStr }) → `render_scene_preview` until it looks right (≤3 passes).
8. PREVIEW: share the preview link (`get_job_status` returns `webUrl`) — the video plays ON THE PLATFORM, not in chat. For explainer/recording, write the Share kit: `get_social_brief` → `submit_social_metadata` (required before the platform will export). STOP — rendering the final MP4 is the user's to trigger, with the Export action on the platform. There is no export tool, so don't offer to render it.
9. TWEAKS after preview: `list_scenes` → `get_scene_code` → `edit_scene_code` (auto re-renders). Pass `previewAtSeconds` with the moment you changed — you get back that one frame instead of all three, which keeps a multi-pass fix loop cheap; the other frames are still listed as URLs. Works even after the final MP4 is rendered, so nothing is a dead-end.
   **PACING — "make this scene faster" means two different things, and only one of them is a code edit.** A scene occupies a fixed slot; its code can only change what happens INSIDE that slot, so a snappier animation does not make a scene, or the video, any shorter. Making the SLOT shorter is a retime, and on a silent motion graphic it is `get_plan({ jobId })` (which returns the plan the video is currently running, with real per-shot durations) → `submit_plan({ jobId, shots })` with revised `durationSeconds`, same ids/wording/types/order. It works right through a finished export. The response names every slot that moved and every scene whose code is now mistimed — rewrite exactly those (`get_code_brief` → `submit_scene_code`), then tell the user to re-export from the platform (free — a video they have already paid for never costs twice). Pass `beatSync: true` again if the video was cut to music, or the retime knocks it off the beat. `list_scenes` reports each scene's duration, so check it before assuming which edit the user means — and if "faster" is ambiguous, ask, or do both. For explainer and recording the slot comes from the recorded narration and CANNOT be retimed: say so plainly rather than making the motion busier and calling it shorter.
   **Captions.** Narrated video (explainer and recording) carries burned-in captions the platform draws from the word timestamps — on by default for 9:16, off but one call away for 16:9, and derived from the theme (a neon theme gets bold uppercase word-pop captions, a cinematic one a serif lower third). They are a job-level TRACK drawn at the composition root, not scene code: never write the narration into a scene as on-screen text, and never try to move or restyle them with `edit_scene_code` — that is `adjust_captions({ jobId, captions })`. Call it with only `jobId` to read the current look and the cues. `{ enabled: false }` turns them off; `{ preset: "bold" | "clean" | "karaoke" | "minimal" | "editorial" }` switches the whole look; `{ fontId }` picks a face by registry id (inter, poppins, montserrat, anton, bebas-neue, oswald, dm-sans, space-grotesk, lexend, nunito, lora, playfair-display, roboto-mono, geist, noto-sans-devanagari); `size` plus `scale` (0.4–2.5, a continuous multiplier — below 1 is smaller than "sm"), `highlight`, `anchor`/`offsetFraction` for the edges or `position: { x, y }` (canvas fractions of the block's centre — anywhere on the frame), `textTransform` and `colors` (hex hues — the platform guarantees they read) tune it; `wordOverrides: { "<transcript index>": "Framehatch" }` fixes a mis-heard word. It works on a finished export (the user re-exports, free). Every code brief names the band the captions occupy — keep it clear. You cannot see captions in the tool result; `render_scene_preview` composites them into its stills.
   **Sound.** Every video gets generated sound effects the client never wrote: a looping ambient bed per scene, a whoosh at each scene start, and one-shots on beats — plus the user's own music if they attached one. When the user says the background sound is distracting, the music is too loud, or a scene should be silent, that is `adjust_audio({ jobId, sceneId?, cueId?, mute, muteKinds?, gain? })` — NOT a code edit; these all live outside the scene components, so `edit_scene_code` cannot reach them. Call it with only `jobId` to list every sound first. Scope: `cueId` = one sound, `sceneId` = one scene, neither = the whole video; the user's track is `cueId: "music"`. `muteKinds: ["ambience"]` drops only the looping SFX beds (usually what "too noisy" means), `["music"]` drops only their track, and `gain: 0.5` turns a scope down instead of off. The voiceover is never touched, and `mute: false` undoes it. Music can also be ADDED or REPLACED at this point with `attach_media({ jobId, role: "music" })` — it does not change the animation, so it is cheap even on a finished video (re-export with `forceRerender: true`). You cannot hear any of it — confirm from the returned cue list and let the user judge from the preview.

`get_job_status` always tells you which tool comes next — follow its guidance.
