Generative Illustration Make expressive, concept-driven pieces by combining AI-generated illustration assets with code-driven motion . This is the lane designers call 动态平面 / motion graphic design — built the agent-native way: an AI makes the visuals, code gives them life and meaning. When to use - An animated illustration, a poetic / ambient "motion poem", a visual metaphor. - A short brand or art piece where a beautiful illustrated scene moves with intent . - Any "AI-made visuals + code motion" hybrid. - Not for: data viz → use ; pure headline text → use ; 3D / GLSL / heavy particle systems…

,c)\n if m: return tuple(int(ch*2,16) for ch in m.group(1))\n m=re.match(r'#([0-9a-f]{6})

Generative Illustration Make expressive, concept-driven pieces by combining AI-generated illustration assets with code-driven motion . This is the lane designers call 动态平面 / motion graphic design — built the agent-native way: an AI makes the visuals, code gives them life and meaning. When to use - An animated illustration, a poetic / ambient "motion poem", a visual metaphor. - A short brand or art piece where a beautiful illustrated scene moves with intent . - Any "AI-made visuals + code motion" hybrid. - Not for: data viz → use ; pure headline text → use ; 3D / GLSL / heavy particle systems…

,c)\n if m: return tuple(int(c[1+2*i:3+2*i],16) for i in range(3))\n m=re.match(r'rgb\\(\\s*([\\d.]+)[ ,]+([\\d.]+)[ ,]+([\\d.]+)',c)\n if m: return tuple(int(float(x)) for x in m.groups())\n return None\n\ndef strip_bg(svg_text):\n head = svg_text[:svg_text.index('>', svg_text.index('\u003csvg'))+1]\n paths = re.findall(r'\u003cpath\\b[^>]*?(?:/>|>\u003c/path>)', svg_text)\n kept=[]\n for p in paths:\n m=re.search(r'fill=\"([^\"]*)\"',p); rgb=parse(m.group(1)) if m else None\n is_white = rgb and all(ch>=242 for ch in rgb) # near-white = background\n if not is_white: kept.append(p)\n return head + \"\".join(kept) + \"\u003c/svg>\"\n```\n\n## Use the sprite in canvas\n\nInline each cleaned SVG as a base64 data-URI `Image`, then `drawImage` per instance (anchor at the motion pivot — base/center/corner):\n\n```js\nconst img = new Image();\nimg.src = \"data:image/svg+xml;base64,\" + b64; // window.__ready() gates on img decode\n// rising from a line, anchored at its base:\nctx.save(); ctx.translate(x, baseY); ctx.rotate(sway); ctx.scale(s, s);\nctx.drawImage(img, -img.naturalWidth/2, -img.naturalHeight, img.naturalWidth, img.naturalHeight);\nctx.restore();\n```\n\n## Format notes & cost\n- Recraft v4.1 SVG: `\u003cpath …>\u003c/path>` (not self-closing), `viewBox 0 0 2048 2048`, `transform=\"translate(x,y)\"`, absolute coords.\n- Recraft via Replicate `recraft-ai/recraft-v4.1-svg` ≈ **$0.04/image**; via OpenRouter `recraft/recraft-v4.1-vector` ≈ $0.08.\n- **Often you need no asset at all** — if the concept is numbers/text/shapes (compounding, a blank page), pure code is stronger. Only generate assets when the concept wants a scene or character.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3325,"content_sha256":"138340ba97668b15f080d6dfa1e598d7b8fc26bf1f1ae27d64de6ba6550b3f65"},{"filename":"references/craft.md","content":"# Craft — what makes a piece good, not just moving\n\n## The first rule: the visual comes from the concept — never default to one device\n\nThe motion vocabulary must be derived from *this idea*. **Reusing one device — \"a thing grows up from the ground\" — across unrelated concepts is the #1 way these go stale.** If you reach for \"something grows\" a second time, stop and ask what the concept actually looks like.\n\n| Concept | Native visual (✓) | Lazy default (✗) |\n|---|---|---|\n| Prompting / iteration | a garden: prompts fall, most wither, one blooms | (here a plant is right — the concept is botanical) |\n| **Compounding** | a dot **doubling** until it floods the screen + a live counter | a tree growing |\n| **The blank page / cursor** | a lonely blinking cursor → first word → text **floods** the page | a sprout growing from the word |\n| Overload / focus | tabs/windows multiplying into noise, then all but one close | — |\n\n## Rhythm: resolve → a beat of stillness → reflect\n\nThe closing line is a *looking-back* statement; it only lands after the action stops. Let the motion finish, hold a beat, **then** reveal the slogan. Never overlap the conclusion with things still moving (it reads as \"the story isn't over yet\").\n\n## Restraint\n\nMotion needs fewer elements than static design — too many and it's noisy in motion. Keep only the core. (流白: \"只留核心要素,避免用力过猛.\")\n\n## Causality\n\nTie cause → effect literally: an input lands → something happens *at that spot*. Loosely-related parallel events read as random noise. (Prompt Garden: each falling prompt lands and grows a plant at that exact point.)\n\n## Spotlight the hero\n\nWhen one element matters, dim the rest and lift it — a halo, a scale, a color shift.\n\n## Easing\n\nNever linear on anything a viewer reads. Ease the *value*, not just opacity. Match the curve to the idea: ease-in (slow→explosive) for compounding; a count-up settling for a number; overshoot for a small pop.\n\n## Semantics — don't self-sabotage\n\nIf the piece depicts failure/withering, do **not** label it with your own product's features (it reads as \"our product fails at its core jobs\"). Use neutral or process language. (We changed falling \"prompts\" from product feature names — \"logo reveal\", \"bar chart\" — to iteration words — \"warmer\", \"again\", \"almost\" — and made the one that blooms say \"yes.\")\n\n## Text content\n\nPlain, concrete, lowercase-casual or quiet-serif depending on tone. The line should feel earned, not slapped on. Two-beat structure works well: a setup during the action, a payoff after it resolves.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2616,"content_sha256":"41e1509e320a86a40de9793c94dc9e15e95797864c641bbe0de0603946cb160c"},{"filename":"references/render-harness.md","content":"# Render harness — deterministic frames + music\n\n> Every value is a pure function of time `t`. Playwright screenshots each frame; ffmpeg assembles. No `requestAnimationFrame`, no CSS transitions (they desync from the grabber).\n\n## Toolchain (local, no API key)\n- Node + **`playwright-core`** (auto-fetches Chromium; or `npx playwright install chromium`)\n- **`ffmpeg`**\n\n## The HTML contract\n\n```html\n\u003ccanvas id=\"c\" width=\"1080\" height=\"1350\">\u003c/canvas>\n\u003cscript>\nconst ctx = document.getElementById('c').getContext('2d');\nfunction render(t){ /* draw EVERYTHING as a pure function of t */ }\nwindow.__T = 12.5; // total seconds\nwindow.__seek = (t) => render(+t||0); // grabber calls this per frame\nwindow.__ready = () => true; // or: () => imagesLoaded>=N\nwindow.__seek(new URLSearchParams(location.search).get('t') || 0); // ?t= renders one frame\n\u003c/script>\n```\n\n- Stage an exact pixel size (e.g. 1080×1350 vertical for RED, 1080×1080 square for X).\n- Stateful sims (particles): step forward deterministically with a **seeded RNG** (e.g. mulberry32) so every render is identical.\n- Use a seeded grain overlay for the soft/“Softcore” texture.\n\n## Capture (one browser, frame by frame)\n\n```js\n// capture.cjs — NODE_PATH must point at playwright-core\nconst path=require('path'); const {chromium}=require('playwright-core');\n(async()=>{\n const fs=require('fs'); fs.rmSync('frames',{recursive:true,force:true}); fs.mkdirSync('frames');\n const b=await chromium.launch();\n const p=await b.newPage({viewport:{width:1080,height:1350},deviceScaleFactor:1});\n await p.goto('file://'+path.resolve('anim.html')+'?t=0');\n await p.waitForFunction(()=>window.__ready&&window.__ready(),{timeout:9000}).catch(()=>0);\n const T=await p.evaluate(()=>window.__T); const FPS=30; const tot=Math.ceil(T*FPS);\n for(let f=0;f\u003ctot;f++){ await p.evaluate(t=>window.__seek(t), f/FPS);\n await p.screenshot({path:'frames/f'+String(f).padStart(4,'0')+'.png'}); }\n await b.close();\n})();\n```\n\n```bash\n# even dimensions + yuv420p (odd sizes fail libx264)\nffmpeg -y -framerate 30 -i frames/f%04d.png -c:v libx264 -pix_fmt yuv420p -crf 18 -movflags +faststart out.mp4\n```\n\n**QC:** sample a contact sheet across the WHOLE duration (not just the first seconds), and read it yourself — blank frames? overlaps? does the concept read? Fix and re-render.\n\n## Music (Lyria 3 via OpenRouter)\n\nAI-generated → royalty-free. **Audio output requires `stream: true`**; chunks arrive base64 in `delta.audio.data`:\n\n```bash\ncurl -s -N https://openrouter.ai/api/v1/chat/completions \\\n -H \"Authorization: Bearer $OPENROUTER_API_KEY\" -H \"Content-Type: application/json\" \\\n -d '{\"model\":\"google/lyria-3-clip-preview\",\"messages\":[{\"role\":\"user\",\"content\":\"soft slow emotional solo piano, warm, gentle forward motion\"}],\"modalities\":[\"audio\"],\"stream\":true}' \\\n | python3 -c \"import sys,json,base64;b=''.join(json.loads(l[5:]).get('choices',[{}])[0].get('delta',{}).get('audio',{}).get('data','') for l in sys.stdin if l.startswith('data:') and 'DONE' not in l);open('bgm.mp3','wb').write(base64.b64decode(b))\"\n```\n\nMix under the video (trim to length, fade in/out, duck volume):\n\n```bash\nffmpeg -y -i out.mp4 -i bgm.mp3 \\\n -filter_complex \"[1:a]atrim=0:12.5,afade=t=in:st=0:d=1.2,afade=t=out:st=11:d=1.5,volume=0.55[a]\" \\\n -map 0:v -map \"[a]\" -c:v copy -c:a aac -shortest out_music.mp4\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3414,"content_sha256":"7a04918bf9bdcdb85e53d6cc0d9dd4faf24a35a0510e935f7160af5d3e18ef71"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Generative Illustration","type":"text"}]},{"type":"paragraph","content":[{"text":"Make expressive, concept-driven pieces by combining ","type":"text"},{"text":"AI-generated illustration assets","type":"text","marks":[{"type":"strong"}]},{"text":" with ","type":"text"},{"text":"code-driven motion","type":"text","marks":[{"type":"strong"}]},{"text":". This is the lane designers call 动态平面 / motion graphic design — built the agent-native way: an AI makes the visuals, code gives them life and meaning.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"An animated illustration, a poetic / ambient \"motion poem\", a visual metaphor.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A short brand or art piece where a beautiful illustrated scene moves ","type":"text"},{"text":"with intent","type":"text","marks":[{"type":"em"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any \"AI-made visuals + code motion\" hybrid.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not","type":"text","marks":[{"type":"strong"}]},{"text":" for: data viz → use ","type":"text"},{"text":"chart-animation","type":"text","marks":[{"type":"code_inline"}]},{"text":"; pure headline text → use ","type":"text"},{"text":"kinetic-typography","type":"text","marks":[{"type":"code_inline"}]},{"text":"; 3D / GLSL / heavy particle systems → use ","type":"text"},{"text":"webgl-animation","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Setup / prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rendering needs no API key","type":"text","marks":[{"type":"strong"}]},{"text":" — Node + ","type":"text"},{"text":"playwright-core","type":"text","marks":[{"type":"code_inline"}]},{"text":" (auto-fetches Chromium) + ","type":"text"},{"text":"ffmpeg","type":"text","marks":[{"type":"code_inline"}]},{"text":", all local and free. This covers the compose + animate + export core.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AI generation (assets + music) needs one API key.","type":"text","marks":[{"type":"strong"}]},{"text":" The simplest path is a single ","type":"text"},{"text":"OpenRouter key","type":"text","marks":[{"type":"strong"}]},{"text":" — set ","type":"text"},{"text":"OPENROUTER_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" — because one key covers all three generators this skill uses:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"vectors → ","type":"text"},{"text":"recraft/recraft-v4.1-vector","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"painterly textures → ","type":"text"},{"text":"google/gemini-2.5-flash-image","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"music → ","type":"text"},{"text":"google/lyria-3-clip-preview","type":"text","marks":[{"type":"code_inline"}]},{"text":" (audio needs ","type":"text"},{"text":"stream: true","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional alternatives:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"REPLICATE_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" for cheaper Recraft (","type":"text"},{"text":"recraft-ai/recraft-v4.1-svg","type":"text","marks":[{"type":"code_inline"}]},{"text":", ~$0.04 vs $0.08/image); a Google AI Studio ","type":"text"},{"text":"GEMINI_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" for Lyria direct.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Or bring your own assets/music","type":"text","marks":[{"type":"strong"}]},{"text":" (hand-drawn, any AI tool) and skip generation entirely — the motion + craft core requires no key at all.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The one rule that prevents 90% of failures","type":"text"}]},{"type":"paragraph","content":[{"text":"Never try to animate one big AI-generated illustration. Generate many small ","type":"text","marks":[{"type":"strong"}]},{"text":"independent","type":"text","marks":[{"type":"strong"},{"type":"em"}]},{"text":" elements and let code animate each as a whole unit.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"AI image/vector models output a ","type":"text"},{"text":"flat pile of ungrouped paths","type":"text","marks":[{"type":"strong"}]},{"text":" — no ","type":"text"},{"text":"\u003cg>","type":"text","marks":[{"type":"code_inline"}]},{"text":", no ids, no \"this is the arm / these are the petals.\" You cannot rig a part inside it. Two failure modes follow:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Treat the whole image as static and crossfade → ","type":"text"},{"text":"looks like a PowerPoint.","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auto-cluster the paths by proximity and wiggle each → ","type":"text"},{"text":"uncanny","type":"text","marks":[{"type":"strong"}]},{"text":" (motion with no intent, everything wobbles).","type":"text"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"The structure lives ","type":"text","marks":[{"type":"strong"}]},{"text":"between","type":"text","marks":[{"type":"strong"},{"type":"em"}]},{"text":" assets, not inside one image.","type":"text","marks":[{"type":"strong"}]},{"text":" Each element is its own clean file; code moves it whole. (This is how working motion-illustrators do it — design the pieces separately, then code organizes and presents them.)","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pipeline","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Concept","type":"text","marks":[{"type":"strong"}]},{"text":" — one metaphor, ruthless restraint.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate each element separately","type":"text","marks":[{"type":"strong"}]},{"text":" with AI, isolated on a plain background.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Strip the background","type":"text","marks":[{"type":"strong"}]},{"text":" → transparent sprite.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code the motion","type":"text","marks":[{"type":"strong"}]},{"text":" — a deterministic seek-harness on a ","type":"text"},{"text":"\u003ccanvas>","type":"text","marks":[{"type":"code_inline"}]},{"text":"; place + animate sprites + text + grain.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Render","type":"text","marks":[{"type":"strong"}]},{"text":" — frame-grab every frame → ffmpeg → MP4.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Music","type":"text","marks":[{"type":"strong"}]},{"text":" — generate a custom track (Lyria), mix it in.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"1 · Generating assets","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Crisp vector elements","type":"text","marks":[{"type":"strong"}]},{"text":" (a flower, an icon, a creature) → ","type":"text"},{"text":"Recraft v4.1 SVG","type":"text","marks":[{"type":"strong"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Painterly textures","type":"text","marks":[{"type":"strong"}]},{"text":" (a watercolor wash, paper grain) → an ","type":"text"},{"text":"image model","type":"text","marks":[{"type":"strong"}]},{"text":" (Gemini \"nano-banana\").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Specificity wins.","type":"text","marks":[{"type":"strong"}]},{"text":" Name a real, characterful subject — generic prompts give generic art:","type":"text"}]}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prompt","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Result","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"a single delicate wildflower, soft petals…","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"flat, generic, cartoonish","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"a single **elegant poppy** on a **tall slender curved stem**, soft coral-red petals with a **delicate dark center**, one leaf, refined minimal flat vector, single isolated element, plain solid white background","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"a graceful, recognizable poppy — instantly \"designed\"","type":"text"}]}]}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always add ","type":"text"},{"text":"no text","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" — AI mangles lettering. Add every word in code (where it is exact and editable).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ask for ","type":"text"},{"text":"\"single isolated element, plain solid white background\"","type":"text","marks":[{"type":"strong"}]},{"text":" so it is trivial to cut out.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Strip the background path.","type":"text","marks":[{"type":"strong"}]},{"text":" Recraft draws a full-canvas white rectangle as path #0. Detect the path whose bounding box covers ~the whole viewBox and delete it → transparent sprite:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"# keep every path EXCEPT the one whose bbox spans the whole canvas\ndef is_bg(path, VBW):\n nums = [float(n) for n in re.findall(r'-?\\d+\\.?\\d*', d_of(path))]\n xs, ys = nums[0::2], nums[1::2]\n return xs and (max(xs)-min(xs)) > VBW*0.92 and (max(ys)-min(ys)) > VBW*0.92","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Recraft v4.1 SVG format: ","type":"text"},{"text":"\u003cpath …>\u003c/path>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (not self-closing), ","type":"text"},{"text":"viewBox=\"0 0 2048 2048\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"transform=\"translate(x,y)\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", absolute coordinates.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"2 · The deterministic seek harness (the key to clean rendering)","type":"text"}]},{"type":"paragraph","content":[{"text":"Every visible value is a ","type":"text"},{"text":"pure function of time ","type":"text","marks":[{"type":"strong"}]},{"text":"t","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" — never a ","type":"text"},{"text":"requestAnimationFrame","type":"text","marks":[{"type":"code_inline"}]},{"text":" loop or a CSS transition (those desync from the frame-grabber and flicker).","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"window.__T = totalSeconds; // total duration\nwindow.__seek = (t) => render(t); // bar width, x/y, opacity, scale — all derived from t\n// on load: render the frame named in the URL\nwindow.__seek(parseFloat(new URLSearchParams(location.search).get('t')) || 0);","type":"text"}]},{"type":"paragraph","content":[{"text":"A one-browser Playwright loop seeks ","type":"text"},{"text":"t = 0, 1/30, 2/30 …","type":"text","marks":[{"type":"code_inline"}]},{"text":", screenshots each, then ffmpeg assembles them (use ","type":"text"},{"text":"even","type":"text","marks":[{"type":"strong"}]},{"text":" dimensions + ","type":"text"},{"text":"yuv420p","type":"text","marks":[{"type":"code_inline"}]},{"text":"). For stateful sims (particles), step the sim forward deterministically with a ","type":"text"},{"text":"seeded RNG","type":"text","marks":[{"type":"strong"}]},{"text":" so every render is identical.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"3 · Animating sprites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Load each asset as an ","type":"text"},{"text":"Image","type":"text","marks":[{"type":"code_inline"}]},{"text":" (inline base64 data-URI so the HTML is self-contained); ","type":"text"},{"text":"drawImage","type":"text","marks":[{"type":"code_inline"}]},{"text":" per instance inside ","type":"text"},{"text":"save/translate/rotate/scale/restore","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Anchor each sprite at its motion pivot","type":"text","marks":[{"type":"strong"}]},{"text":" (base / center / a corner — whatever the motion needs). E.g. to rise from a line: ","type":"text"},{"text":"translate(x, baseY)","type":"text","marks":[{"type":"code_inline"}]},{"text":" then draw at ","type":"text"},{"text":"(-w/2, -h)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Match the entrance to the idea, don't reflex to \"grow\": a count-up, a split/double, a slide-in, a flood, a snap, a scale-from-0 — each says something different. Idle life: gentle ","type":"text"},{"text":"sin(t)","type":"text","marks":[{"type":"code_inline"}]},{"text":" drift.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add a ","type":"text"},{"text":"grain overlay","type":"text","marks":[{"type":"strong"}]},{"text":" (seeded noise) for a hand-made, Softcore texture.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"In capture, gate on an ","type":"text"},{"text":"window.__ready()","type":"text","marks":[{"type":"code_inline"}]},{"text":" flag once all images have decoded.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"4 · Craft — what makes it ","type":"text"},{"text":"good","type":"text","marks":[{"type":"em"}]},{"text":", not just moving","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Let the concept choose the visual — never default to one device.","type":"text","marks":[{"type":"strong"}]},{"text":" The motion vocabulary must be derived from ","type":"text"},{"text":"this idea","type":"text","marks":[{"type":"em"}]},{"text":", not reused: compounding → a dot doubling until it floods the screen (not a plant); a blank page → the first word breaking a dam of text (not a sprout); overload → tabs/windows multiplying into noise. ","type":"text"},{"text":"Reusing one device — \"a thing grows up from the ground\" — across unrelated concepts is the #1 reason these go stale and generic.","type":"text","marks":[{"type":"strong"}]},{"text":" If you reach for \"something grows\" a second time, stop and ask what the concept actually looks like. (The growing-plant in the original garden piece worked ","type":"text"},{"text":"only","type":"text","marks":[{"type":"em"}]},{"text":" because the concept was literally botanical.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Causality.","type":"text","marks":[{"type":"strong"}]},{"text":" Tie cause → effect literally (an input lands → something happens ","type":"text"},{"text":"at that spot","type":"text","marks":[{"type":"em"}]},{"text":"). Loosely-related parallel events read as random noise.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Restraint.","type":"text","marks":[{"type":"strong"}]},{"text":" Motion needs fewer elements than static design — too many and it gets noisy in motion. Keep only the core; don't overdo it.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spotlight the hero.","type":"text","marks":[{"type":"strong"}]},{"text":" When one element matters (the one that \"made it\"), dim the rest and lift it — a halo, a scale, a color shift.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rhythm: resolve → a beat of stillness → reflect.","type":"text","marks":[{"type":"strong"}]},{"text":" A closing line is a ","type":"text"},{"text":"looking-back","type":"text","marks":[{"type":"em"}]},{"text":" statement; it only lands after the action has stopped. Let the motion finish, hold a beat, ","type":"text"},{"text":"then","type":"text","marks":[{"type":"em"}]},{"text":" reveal the slogan — never overlap the conclusion with things still moving.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Easing.","type":"text","marks":[{"type":"strong"}]},{"text":" Never linear on anything a viewer reads. Ease the ","type":"text"},{"text":"value","type":"text","marks":[{"type":"em"}]},{"text":", not just opacity. Overshoot for small pops; slow ease-out for growth.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Semantics — don't self-sabotage.","type":"text","marks":[{"type":"strong"}]},{"text":" If the piece depicts failure/withering, do ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" label it with your own product's features (it reads as \"our product fails at its core jobs\"). Use neutral or process language instead.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"5 · Music","type":"text"}]},{"type":"paragraph","content":[{"text":"Generate a custom track with ","type":"text"},{"text":"Lyria 3","type":"text","marks":[{"type":"strong"}]},{"text":" (OpenRouter ","type":"text"},{"text":"google/lyria-3-clip-preview","type":"text","marks":[{"type":"code_inline"}]},{"text":"). It is AI-generated, so it is royalty-free. ","type":"text"},{"text":"Audio output requires ","type":"text","marks":[{"type":"strong"}]},{"text":"stream: true","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" — chunks arrive as base64 in ","type":"text"},{"text":"delta.audio.data","type":"text","marks":[{"type":"code_inline"}]},{"text":"; concatenate and decode to MP3. Mix with ffmpeg: trim to length, fade in/out, duck the volume (~0.5) under any narration.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"6 · Verify loop (do not skip)","type":"text"}]},{"type":"paragraph","content":[{"text":"Render the piece, build a ","type":"text"},{"text":"full-duration","type":"text","marks":[{"type":"strong"}]},{"text":" contact sheet (sample frames across the ","type":"text"},{"text":"whole","type":"text","marks":[{"type":"em"}]},{"text":" timeline, not just the first seconds), and ","type":"text"},{"text":"read it yourself","type":"text","marks":[{"type":"strong"}]},{"text":": blank frames? overlapping rows? does the concept actually read? Fix and re-render. This deliver-and-verify loop is what separates \"it ran\" from \"it's good.\"","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"references/","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"asset-pipeline.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — asset prompting, background-strip code, Recraft/Gemini format notes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"render-harness.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full seek-harness HTML + Playwright capture + ffmpeg recipe","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"craft.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — causality, rhythm, restraint, spotlight, with worked examples","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"paragraph","content":[{"text":"The craft behind iart.ai. Cost reference: a Recraft SVG ≈ $0.04, a Lyria track ≈ cents — a whole finished piece comes in under $1.","type":"text","marks":[{"type":"em"}]}]}]},"metadata":{"date":"2026-11-02","name":"generative-illustration","author":"@skillopedia","source":{"stars":8,"repo_name":"generative-illustration-skills","origin_url":"https://github.com/iart-ai/generative-illustration-skills/blob/HEAD/skills/generative-illustration/SKILL.md","repo_owner":"iart-ai","body_sha256":"1877ed4ec737351b0e21b76c5c3a04217b728555b12d7937914c36cfcec68662","cluster_key":"6bc19f4c6fdafe92284d73bf37a98bf6941f2e3eba54dba714818027ccdab4be","clean_bundle":{"format":"clean-skill-bundle-v1","source":"iart-ai/generative-illustration-skills/skills/generative-illustration/SKILL.md","text_bytes":9355,"source_commit":"e7d62437c875fc8ad5eff6e90fe0e25b30e43933","skills_sh_rank":16522,"attachment_count":3,"text_attachments":3,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/generative-illustration/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"0.1.0","category":"media-content","category_label":"Media"},"exact_dupes_collapsed_into_this":0},"version":"0.1.0","category":"media-content","import_tag":"skills-sh-top20k-2026-11","description":"This skill should be used when the user asks to \"make an animated illustration\", \"animate an illustration\", \"make a generative motion piece\" / \"动态平面\", \"turn AI-generated art into something that moves\", \"make a poetic or ambient motion piece / motion poem\", \"make a visual metaphor\", or \"combine AI-generated visuals with code-driven motion\". Covers generating isolated illustration assets with AI, compositing them as sprites, frame-accurate deterministic rendering, and the craft of expressive, concept-driven motion. NOT for data charts (use chart-animation), pure text (use kinetic-typography), or 3D/particles (use webgl-animation)."}},"renderedAt":1793666226991}

Generative Illustration Make expressive, concept-driven pieces by combining AI-generated illustration assets with code-driven motion . This is the lane designers call 动态平面 / motion graphic design — built the agent-native way: an AI makes the visuals, code gives them life and meaning. When to use - An animated illustration, a poetic / ambient "motion poem", a visual metaphor. - A short brand or art piece where a beautiful illustrated scene moves with intent . - Any "AI-made visuals + code motion" hybrid. - Not for: data viz → use ; pure headline text → use ; 3D / GLSL / heavy particle systems…