VMD — CINEMATIC GENERATION GUIDE "Markdown renders text. VMD renders understanding." --- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ABSOLUTE RULE — READ THIS BEFORE WRITING A SINGLE LINE OF VMD !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DO NOT WRITE COMMENTS IN VMD CODE. NOT ONE. NOT EVER. No # comments. No inline comments. No explanatory comments. No "# this does X". No "# timing note". No "# see above". Nothing. Zero. Not a single character that begins with # inside an element block or anywhere in the generated .vmd output. The VMD parser does not have a comment syntax. A # inside element YAML will break the file silently or corrupt output. Comments are for drafts. You are not writing a draft. You are writing the final .vmd file that will be sent directly to the renderer. If you feel the urge to explain something with a comment — don't. Name the element clearly instead. That is the only documentation allowed. THIS RULE HAS NO EXCEPTIONS. NO COMMENTS. EVER. --- WHAT IS VMD VMD is a declarative language for cinematic animation. You write plain text. VMD renders video. A .vmd file describes a scene — its images, its elements, their motion, their timing — and the VMD renderer produces a cinematic animation from it. No timeline scrubbing. No keyframe dragging. No design software. The animation lives in the file, travels in version control, and can be written by a human or generated by an AI. VMD occupies the space between a static document and a produced video. A PDF cannot move. A video cannot be written in a text editor, diffed in git, or generated from a one-line prompt. VMD can do all of these. It is the file format for ideas that need to be seen to be understood. VMD is built for two things: cinematic documentaries that make concepts physically visible — physics, history, biology, systems — and motion decks that make arguments feel inevitable rather than presented. Both are produced from the same plain-text syntax. Both live in the same file format. Both render to video. This document is the complete reference for writing VMD well — not just correctly, but cinematically. It covers the syntax, the craft, and the reasoning that separates a VMD scene that proves an argument from one that merely illustrates it. It is written for AI systems generating VMD, for developers integrating VMD into their tools, and for anyone who wants to understand what VMD requires of a scene before a single line of code is written. --- BEFORE YOU TOUCH ANY SYNTAX — MANDATORY This is not a warmup. This is the work. The code you write later is only as good as what you produce here. Write these four things out. Not in your head. Actually write them as a visible reasoning block before generating any VMD. --- 1. THE MECHANISM What does this concept actually do? Not its name. Not its definition. What physically happens? Describe the mechanism in one or two sentences as if explaining it to someone who has never heard of it. *Ohm's Law: voltage pushes current through a conductor; resistance opposes that push; raise voltage and current rises proportionally, raise resistance and current falls. The relationship is exact and simultaneous.* *Metcalfe's Law: as nodes join a network, the number of possible connections grows as n² while the nodes themselves grow as n. Five nodes have ten connections. Ten nodes have forty-five. The connections always outrun the count.* --- 2. THE SHAPE If this mechanism were happening in front of you in physical space — not on a screen, in the real world — what would you actually see? Not a diagram. A scene. Close your eyes and watch it happen. Write what you see. *Ohm's Law: a closed loop of wire. Small glowing particles moving through it. When you turn up a dial, the particles move faster. When you squeeze the wire at one point, the particles slow there and the wire heats. The particles never stop. The loop never breaks.* *Metcalfe's Law: a handful of dots appear one by one. Then lines start drawing between them. At first slowly. Then faster. Then the lines are appearing so fast the whole structure lights up at once — brighter and brighter with every new connection — and you realize the light is outrunning the dots.* This shape is the scene. If you cannot see it, you are not ready to write code. --- 3. THE VMD PRIMITIVE THAT IS THAT SHAPE Not the closest example from this guide. Not the pattern that looks most similar. The primitive that is geometrically and physically identical to the shape you described in step 2. *Ohm's Law shape → circle primitives with fill+stroke traveling between fixed corner points. Duration of travel encodes speed. Color progression encodes energy state. The loop is line primitives drawing themselves corner to corner.* *Metcalfe's Law shape → circle primitives as nodes. Line primitives drawing between them in sequence. Opacity and glow radius of nodes increasing with each new connection. No wave rings — there is no propagation. No electron chains — there is no current. The shape requires dots and lines and brightness. Use dots and lines and brightness.* If you find yourself writing "wave rings" ask: does this concept propagate outward through a medium? If no — no rings. If you find yourself writing "electron chains" ask: does this concept involve particles flowing through a conductor? If no — no chains. If you find yourself writing "parametric rings" ask: does this concept involve something blooming, opening, or expanding from a center? If no — no rings. Every pattern in this guide exists because its concept had that exact shape. Your concept has its own shape. Use that. --- 4. THE MOMENT What is the single instant in this scene where the viewer feels the concept rather than understands it? Not the title card. Not the equation. The moment the argument becomes undeniable on screen. *Ohm's Law: the moment the resistance rises and the electron visibly slows and the resistor body glows orange and the fire icon blooms — all simultaneously. The viewer watches physics happen in real time.* *Metcalfe's Law: the moment the fifth node appears and six lines draw themselves at once — faster than the previous additions — and the whole network brightens. The viewer feels the explosion before they calculate it.* Everything in the scene builds toward this moment. If you cannot identify it before writing code, the scene has no destination. --- Only after all four are written do you read the rest of this guide and write code. The examples in this guide are proofs that their concepts had shapes worth animating. They are not templates. Reading the Doppler scene and building rings into your next scene because rings looked good is the failure mode this protocol prevents. Your concept has its own shape. You wrote it in step 2. Honor it. --- PART 1 — THE STANDARD The three questions that govern every scene: What is the single argument this scene makes? Not topic. Argument. "Ohm's Law" is a topic. "Voltage drives current — raise it, electrons accelerate; raise resistance, they slow and the wire heats" is an argument. If you cannot state it in one sentence, the scene does not exist yet. What physically happens on screen to BE that argument — not illustrate it, BE it? The animation must make the concept physically real. If the viewer needs to read a label to understand what they're watching, the visual layer has failed. Who is the viewer when this ends? What do they know, feel, or believe that they didn't before? If the answer is "they saw some nice animations," start over. --- THE TWO MODES These are not style variations. They are different disciplines. Getting the mode wrong produces something that fails at both. CINEMATIC DOCUMENTARY The animation IS the concept. Physics happening. Systems working. Forces competing. History unfolding. The image sets the world. The animation is the proof. The text arrives as confirmation of what the viewer already watched — never as explanation of what they're about to see. Duration 20–35s for deep physics, 13–20s for documentary beats. Density high — 40–80+ elements is normal. The AI's job: make the concept physically real on screen. *Signals you're in this mode:* science, physics, biology, history, engineering, systems, any concept where the mechanism can be visualized directly. MOTION DECK The story is told in bold claims arriving one by one. Each scene is one statement — proved visually in 3–5 seconds, held, then the next builds. The image is atmosphere, not subject. The viewer is being persuaded, not taught. Duration 10–16s per scene. The AI's job: make each claim feel inevitable, not announced. *Signals you're in this mode:* product reveals, pitch decks, portfolio showcases, any sequence where the argument is rhetorical rather than mechanistic. Motion Deck has three architectures. The choice is an argument decision, not a style preference. Split Panel — the claim needs protected space. Dark panel covers 35–45% of canvas. Image breathes on the open side. Text lives only inside the panel. Use when the argument is complex, multi-line, or needs to be read carefully without competing with the image. Tesla is this mode. Immersive — the claim is made from inside the world. Full bleed image. Letter-box bars rise top and bottom creating cinema framing. Text sits centered over the image — not beside it, inside it. The world is behind the words. Use when the claim should feel like a statement of fact about the world the viewer is standing in. "No dragging boxes." lands inside a city at night, not next to a photo of one. Living Canvas — the claim IS a transformation happening on screen. Full bleed image with active physics layered on top simultaneously with the text. Parametric rings blooming, particles drifting, petals falling — the effect and the argument are the same event. Use when the subject is growth, emergence, creativity, or any concept where the transformation can be shown literally while the text names it. "Just write. Watch it bloom." — and it is blooming, right now, on screen. Wrong architecture destroys the argument. A precision product claim over a living canvas feels like noise. An emergence claim over a split panel feels like a brochure. Read the argument, then choose the architecture. The failure mode for both: adding effects that look good but serve no argument. Every element must exist because the story needed it. If you cannot explain why an element is there in terms of the argument, cut it. --- PART 2 — SYNTAX REFERENCE FILE STRUCTURE concept: "Title" emoji: "⚡" total_duration: 30 # exact sum of all phase durations — no tolerance subtitles: - "" # always present, always empty string, never remove phases: scene_name: duration: 30 description: "One sentence." elements: element_name: type: primitive # or: morph shape: shape[...] -> command(...) # ALWAYS ONE LINE. NEVER BREAK. Multi-scene: one phase per scene. total_duration = exact sum of all durations. --- CANVAS | | | |---|---| | x | −8.0 to +8.0 | | y | −6.0 to +6.0 | | center | (0,0) | | positive y | UP | | Anchor | Position | |---|---| | Scene tag | (−5.2, 4.85) | | Top bar | (0, 5.0) | | Bottom bar | (0, −4.6) | | Whisper line | y: −3.2 | | Corners TL/TR/BL/BR | (±7.8, ±5.2) | | Off-canvas spawn | x: ±9.0–10.0 | --- PRIMITIVES | Primitive | Syntax | |---|---| | circle | circle[r:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)] | | rectangle | rectangle[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)] | | square | square[w:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)] | | ellipse | ellipse[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)] | | line | line[from:(x,y) to:(x,y) stroke:#hex/op strokeWidth:N at:(x,y)] | | triangle | triangle[size:N fill:#hex/op at:(x,y) direction:up\|down\|left\|right] | | text | text[text:"Label" fontSize:N fill:#hex/op fontWeight:bold at:(x,y)] | | functionGraph | functionGraph[fn:"sin(x)" xStart:N xEnd:N segments:N stroke:#hex/op strokeWidth:N at:(x,y)] | | parametric | parametric[paramX:"cos(t)" paramY:"sin(t)" tStart:N tEnd:N segments:N stroke:#hex/op strokeWidth:N at:(x,y)] | | image | image[src:"images/image_1.jpg" w:N h:N fit:cover opacity:N grayscale:N blur:N tint:#hex/op crop:(x,y,w,h) at:(x,y)] | | icon | icon[icon:"prefix:name" size:N color:#hex/op rotation:Ndeg flip:horizontal at:(x,y)] | | group | group{at:(x,y)} [ shape, shape ] — entire expression one line | | figure | figure[use:"figures/name.vmd" at:(x,y) scale:N] -> gesture("name", duration, delay) | text width limits — never exceed: | fontSize | 64 | 52 | 46 | 40 | 36 | 30 | 22 | 16 | 13 | |---|---|---|---|---|---|---|---|---|---| | max chars | 8 | 10 | 12 | 14 | 18 | 22 | 30 | 44 | 54 | fontWeight:normal|bold snaps at 50% in morph. outline:#hex/op for hollow text — never both fill and outline. functionGraph: type: primitive only. Never morph. Functions: sin cos tan exp log sqrt pow. parametric recipes: | Shape | paramX | paramY | tEnd | |---|---|---|---| | Circle | r*cos(t) | r*sin(t) | 6.283 | | Spiral | t*cos(t) | t*sin(t) | 8 | | Figure-8 | sin(t) | sin(2*t) | 6.283 | image: fit:cover|contain / crop:(x,y,w,h) interpolates / src must be images/image_N.ext — user pre-loads images in order, they auto-rename. --- COMMANDS Chain with -> on any primitive. | Command | Signature | Use for | |---|---|---| | fadeIn | (duration, delay) | World icons, sub-lines, whispers — never primary claims | | fadeOut | (duration, delay) | Clearing, beat transitions | | moveTo | ((x,y), duration, easing, delay) | Character travel | | rotate | (angle, duration, easing, delay) | Spin, orbit — 360deg full rotation | | scale | (factor, duration, easing, delay) | Drama, reaction, emphasis | | typewriter | (duration, delay) | Titles, code, equations — reveals character by character | | wordReveal | (stagger, delay) | Bold claims — each word lands separately | | focusPull | (duration, delay) | Primary claims only — blooms from blur to sharp | focusPull before ~> stays blurred through the morph, lands sharp at destination. --- MORPH type: morph shape: A[...] ~> B[...] (duration, easing, delay) ~> C[...] (duration, easing, delay) Delay is absolute from phase start — not relative to the previous step. A ~> B (2s, ease.out, 1s) ~> C (1s, ease.out, 3s) B starts at t=1s. C starts at t=3s from phase start. Not 3s after B ends. Commands chain before the first ~>: shape: A[...] -> fadeIn(0.5s, 0s) ~> B[...] (1s, ease.out, 3s) Interpolated: at r w h fill stroke color opacity strokeWidth fontSize grayscale blur tint crop Snaps at 50%: icon name image src fontWeight fontStyle Spawn gate — triple-zero any element that must be invisible at t=0: Stroke-only circles (wave rings, auras, pulse rings): Omit fill entirely. Zero all three: r:0.0 stroke:#hex/0.0 strokeWidth:0.0. WRONG — fill:/0.0 on stroke-only breaks morph interpolation silently: circle[r:0.15 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.80 strokeWidth:1.4 at:(0,0)] WRONG — r:0.3 with opacity:0 still renders an artifact at frame zero: circle[r:0.3 stroke:#4fc3f7/0.0 strokeWidth:0.0 at:(0,0)] CORRECT — triple-zero, no fill property at all: circle[r:0.0 stroke:#4fc3f7/0.0 strokeWidth:0.0 at:(0,0)] Fill circles and glows (electrons, particles, fill+stroke circles): Use fill:#hex/0.0 in seed. Fill is expected — zero opacity hides it correctly. circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(0,0)] Hold step — wait before appearing: A[invisible] ~> A[invisible] (Ns, linear, 0s) ~> B[visible] (dur, easing, Ns) --- EASING | Easing | Use when | |---|---| | ease.out | Arrivals, settling, resolution | | ease.in | Collapse, departure, inevitability | | ease.inOut | Travel, Ken Burns, organic motion | | linear | Mechanical, orbit, data flow, hold steps | | ease.spring | Activation pop, character reactions | | ease.elastic | Surprise, unstable systems | | bounce | Physical landing | --- COLOR State colors — fixed meaning, never reassign: | Hex | Meaning | |---|---| | #4fc3f7 cyan | alive, active, working | | #ffd54f amber | cinematic moment only — sacred. Appears nowhere before it. | | #ffffff white | labels, neutral structure | | #ff6b9d pink | broken, failed, overloaded | | #6bffb8 mint | resolved, correct, complete | | #c77aff violet | deep system, unknown, AI layer | World / atmosphere colors: #38bdf8 sky blue / #ff9f43 warm orange / #60a5fa soft blue / #fbbf24 warm yellow / #67e8f9 light cyan / #b9f3ff pale cyan / #c4b5fd soft violet / #f9a8d4 soft pink / #a7f3d0 pale mint Opacity roles: | Role | Fill | Stroke | |---|---|---| | World / ambient | /0.06–0.12 | — | | Background structure | /0.06–0.20 | /0.15–0.35 | | Resting / seed | /0.25–0.40 | /0.45–0.65 | | Active | /0.40–0.65 | /0.65–0.85 | | Triggered / fired | /0.65–0.85 | /0.85–1.00 | | Cinematic / hero | /0.85–1.00 | /1.00 | | Whispers / scene tag | /0.28–0.35 | — | --- ICONS mdi — 7,600+ filled icons. Default for people, objects, nature, systems. mdi:fire mdi:human mdi:car-side mdi:battery-high mdi:lightning-bolt mdi:earth mdi:rocket mdi:music-note mdi:waves mdi:brain mdi:server mdi:account-group mdi:snowflake mdi:flower mdi:shield-sword mdi:cog mdi:atom mdi:sigma mdi:star mdi:leaf mdi:tree mdi:pillar mdi:crown lucide — 1,700+ outline icons. For architecture, data, technical diagrams. lucide:database lucide:cpu lucide:server lucide:git-branch lucide:orbit lucide:brain-circuit lucide:satellite lucide:star lucide:mountain-snow logos — brand logos only, never atmosphere. logos:react logos:docker Icon format — both parts required: "prefix:name" — wrong format = silent placeholder. Icon scale: | Role | Size | |---|---| | Lead actor, single | 2.5–4.0 | | Lead actor, paired | 2.0–3.0 | | Supporting actor | 1.4–1.8 | | Background world | 0.8–1.2 | | Ambient atmosphere | 0.5–0.9 | Icons are mandatory in every scene — not decoration, not clip art. They are the cast. An icon IS the system component, the person, the force, the concept. It travels, reacts, changes color when something happens to it, sends packets to other icons, dims when it fails, brightens when it resolves. A scene with no icon actors is not a VMD scene. Before generating any scene, answer: - What are my 4–8 world icons? (edges/corners, /0.05–0.12, from frame zero — establishes what world this is) - What are my lead actor icons? (size 2.0–4.0, they are the main characters doing the argument) - What are my supporting actor icons? (size 1.4–1.8, they react to the lead actors) If you cannot answer all three, stop and think before writing code. --- FIGURES & CHARACTERS A figure is a fully-rigged 2D character — body, arms, legs, head, face, one shared skeleton — designed entirely as text and animated with the same -> chain as any primitive. Use one when the scene's argument needs an actor with a body: a presenter walking the viewer through a concept, two characters in dialogue, a reaction shot. An icon is a symbol. A figure is a cast member. Most documentary and motion-deck scenes need zero figures — reach for one only when the argument is literally about a person or character doing something. Design every character inline, in an avatars: section, in the same file that uses it. This is the only pattern to generate — avatars: parses in the same single pass as the rest of the scene, so it renders identically in the CLI, video export, and the live preview. avatars: hero: figure[name:"hero"]{root[shape:none] chest[...] neck[...] head[...] eye_l[...] eye_r[...] mouth[...] shoulder_l[...] shoulder_r[...] elbow_l[...] elbow_r[...] hand_l[...] hand_r[...] hip_l[...] hip_r[...] knee_l[...] knee_r[...] foot_l[...] foot_r[...] extra[...] ...} phases: scene1: elements: hero: type: figure shape: figure[use:"hero" at:(0,-1.9) scale:85] -> gesture("wave", 2s, 0s) avatars: goes after subtitles:/total_duration:/background:, before phases:. Define as many named avatars as the scene needs. at: is the root position — feet land near here. scale:85 is the standard adult height. THE 19-POINT CONTRACT — every figure must define a shape for all 19 of these or the build fails silently (figure just doesn't appear): root (shape:none) → chest → neck → head — the spine chain. eye_l, eye_r, mouth — offset off head. shoulder_l/r → elbow_l/r → hand_l/r — one arm each side, offset shoulder then chain elbow/hand. hip_l/r → knee_l/r → foot_l/r — one leg each side, offset hip then chain knee/foot. Never change a point's length/direction/offset/width unless deliberately resizing the body — the entire gesture library is numerically tuned against the contract's rest angles. For a new design, only change shape/fill/extras. SHAPE KINDS — the only 5 legal shape: values: circle — r, fill. Rotation-invariant, safe for joint markers (shoulder/hip circles). ellipse — w, h, fill, angle (static authored tilt, e.g. eyebrow angle:0.08). capsule — width, widthEnd, fill. Contract chain-points ONLY (chest/neck/head/elbow/hand/knee/foot) — draws the bone segment from the point's parent to the point's own position, tapered. A capsule on an extra renders nothing. path — points:[[x,y],...] local offsets relative to the point's own position, smoothing 0–1 (Catmull-Rom; ~0.3–0.4 reads structured, ~0.55–0.7 reads organic/hair). none — root only. EXTRAS — decorations not bound to a contract point: extra[attachTo:"" shape:circle|ellipse|path ...props offset:(dx,dy) role:"eyebrow_l"] - attachTo inherits that point's world position AND rotation every frame — it follows a moving limb for free. - attachTo:"eye_l"/"eye_r" automatically gets pupil gaze-tracking (driven by look-left/right/up) — no role tag needed or recognized for this. - role:"eyebrow_l"/"eyebrow_r" is the only other recognized role value — it makes that extra react to expressions (tilt + vertical shift). Any other role value is silently dropped. - Extras paint after all 19 point shapes, in listed order, each on top of the last. There is no "behind the body" — never design an accessory that needs to tuck behind the torso or head. DESIGN STANDARD — a new character should: 1. Define all 19 points deliberately — no bare defaults (those are only correct on the debug skeleton rig). 2. Override neck length:0.22, head length:0.20, head r:0.33–0.34 for a person-like head-to-body ratio. 3. Size joints relative to head r: shoulder/hip circle r 0.13–0.14, elbow capsule width 0.18–0.20 (taper to widthEnd 0.15–0.17), hand capsule width 0.13–0.14 (taper to 0.10–0.11), knee capsule width 0.24–0.26 (taper to 0.20–0.22), foot capsule width 0.18–0.20 (flare to widthEnd 0.22–0.24 — wider reads as a shoe sole). 4. Joint-cap color rule: color shoulder_l/r and hip_l/r to match chest's own fill, never the limb beyond them. Put the fabric→skin or trouser→shoe color change at hand_l/r and foot_l/r instead — the renderer auto-paints a joint-cap there in that point's own color, which handles the seam with no extra shape needed. 5. Add head shading: shadeFill (two shades darker than the skin fill), shadeOpacity:~0.2, shadeDx:0.12 shadeDy:-0.14 shadeScale:0.75 — a flat-filled head with no shadeFill reads sticker-like. 6. Add a cosmetic layer: pupils as extra[attachTo:"eye_l"/"eye_r" shape:circle ...] (attach directly to the eye, never to head, or gaze tracking won't apply), eyebrows as role:"eyebrow_l"/"eyebrow_r" extras (untagged eyebrows never react to expressions), 2–3 small ellipse extras fanned on each of hand_l/hand_r (a bare capsule hand with no finger extras reads as a mitten). 7. If hair is a path extra with a fringe, keep the fringe's lowest points at y > ~0.27 in head-local space — lower visually covers the eyes (offset ~0.08) and eyebrows (~0.10). 8. Verify by rendering: happy, sad, surprised, angry individually, plus one plain gesture (explain or bare standing). ACCESSORY EXAMPLES — copy, recolor, reposition. Never use capsule on an extra: Glasses (two lenses + bridge, all on head): extra[attachTo:"head" shape:ellipse w:0.16 h:0.13 fill:none offset:(-0.15,0.08)] extra[attachTo:"head" shape:ellipse w:0.16 h:0.13 fill:none offset:(0.15,0.08)] extra[attachTo:"head" shape:ellipse w:0.10 h:0.02 fill:#2b2b2b offset:(0,0.08)] Bow/pin (two angled loops + a knot, all on head): extra[attachTo:"head" shape:ellipse w:0.10 h:0.07 fill:#e0607e angle:0.5 offset:(0.16,0.42)] extra[attachTo:"head" shape:ellipse w:0.10 h:0.07 fill:#e0607e angle:-0.5 offset:(0.28,0.42)] extra[attachTo:"head" shape:circle r:0.035 fill:#b8425c offset:(0.22,0.42)] GESTURES — chain after a figure exactly like any other command: wave · head-tilt · talk · look-left · look-right · look-up · point-left · point-right · point-up · explain · walk · happy · sad · surprised · angry · shrug · idle · flinch · laugh Each gesture owns its own [delay, delay+duration] window and writes only the channels it drives (talk writes mouth_open; happy writes expr_happy; wave writes chest/neck/shoulder_r/elbow_r/hand_r). Gestures merge per channel, not by overwrite — give two gestures the same delay AND duration to layer them; give increasing delays to sequence them. moveTo((x,y), duration, delay) translates the root independent of any gesture — pair with walk for a character that actually crosses the frame, since walk alone only cycles the legs in place. gesture and moveTo are the only chained command types a figure responds to — fadeIn/fadeOut/scale/rotate parse but do nothing visually on a figure. figure[use:"hero" at:(3,-1.9) scale:85] -> gesture("explain", 1.5s, 0s) -> gesture("wave", 3.5s, 1.5s) -> gesture("talk", 3.5s, 1.5s) explain plays alone for 1.5s. wave (arm channels) and talk (mouth channel) then play together for the next 3.5s — same delay, same duration, different channels, so they layer instead of overwriting each other. figure[use:"hero" at:(-3,-0.45) scale:85] -> gesture("walk", 7s, 0s) -> gesture("talk", 7s, 0s) -> gesture("happy", 7s, 0s) -> moveTo((3,-0.45), 7s, 0s) All four run concurrently for the full 7s — locomotion, speech, emotion, and travel as one beat. PROCEDURE — designing a new character from a prompt: 1. Read the prompt for skin tone, hair style + color, clothing colors (top vs. legwear vs. shoes), eye size, any accessory. 2. Start from the worked example below and change only fill colors and the extra entries — never length, direction, offset, or width/widthEnd unless deliberately changing body proportions. 3. Apply the Design Standard checklist above, in order. 4. Pick accessories from the cookbook above, adjusting only colors/offsets. 5. Write the result as one figure[name:""]{...} line inside avatars:. WORKED EXAMPLE — complete, valid, animatable, standard-passing: avatars: hero: figure[name:"hero"]{root[shape:none] chest[shape:capsule width:0.6 widthEnd:0.5 fill:#3a5a78] neck[shape:capsule width:0.16 fill:#e3a878 length:0.22] head[shape:circle r:0.34 fill:#e3a878 shadeFill:#5a2f17 shadeOpacity:0.22 shadeDx:0.12 shadeDy:-0.14 shadeScale:0.75 length:0.20] eye_l[shape:ellipse w:0.10 h:0.075 fill:#f7f3ea offset:(-0.15,0.08)] eye_r[shape:ellipse w:0.10 h:0.075 fill:#f7f3ea offset:(0.15,0.08)] mouth[shape:ellipse w:0.13 h:0.03 fill:#a85a52 offset:(0,-0.16)] shoulder_l[shape:circle r:0.14 fill:#3a5a78 offset:(-0.20,-0.05)] shoulder_r[shape:circle r:0.14 fill:#3a5a78 offset:(0.20,-0.05)] elbow_l[shape:capsule width:0.20 widthEnd:0.17 fill:#3a5a78] elbow_r[shape:capsule width:0.20 widthEnd:0.17 fill:#3a5a78] hand_l[shape:capsule width:0.14 widthEnd:0.11 fill:#e3a878] hand_r[shape:capsule width:0.14 widthEnd:0.11 fill:#e3a878] hip_l[shape:circle r:0.14 fill:#3a5a78 offset:(-0.20,-0.04)] hip_r[shape:circle r:0.14 fill:#3a5a78 offset:(0.20,-0.04)] knee_l[shape:capsule width:0.26 widthEnd:0.22 fill:#2b3a4a] knee_r[shape:capsule width:0.26 widthEnd:0.22 fill:#2b3a4a] foot_l[shape:capsule width:0.20 widthEnd:0.24 fill:#1f1611] foot_r[shape:capsule width:0.20 widthEnd:0.24 fill:#1f1611] extra[attachTo:"eye_l" shape:circle r:0.032 fill:#211a16] extra[attachTo:"eye_r" shape:circle r:0.032 fill:#211a16] extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#2b211c angle:0.08 offset:(-0.15,0.10) role:"eyebrow_l"] extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#2b211c angle:-0.08 offset:(0.15,0.10) role:"eyebrow_r"]} phases: intro: duration: 5 elements: hero: type: figure shape: figure[use:"hero" at:(-3,-0.45) scale:85] -> gesture("walk", 5s, 0s) -> gesture("talk", 5s, 0s) -> moveTo((0,-0.45), 5s, 0s) LIMITATIONS — honest, design around these: 2D frontal rig, no depth axis (head-tilt is a side-to-side arc, not a nod; walk is a march in place, not a true gait — pair with moveTo for travel). Extras never render behind the body. No physics/collision — nothing prevents posing a figure standing inside another element. --- TIMING | Action | Sweet spot | Breaks if | |---|---|---| | focusPull primary | 1.6–2.2s | <1.2s cheap / >2.5s waits | | wordReveal stagger | 0.15–0.22s | <0.10s blur / >0.30s typed | | typewriter title | 0.9–1.4s | <0.6s snaps / >2.5s drags | | Icon activation | 0.4–0.6s | <0.3s flash / >0.9s slow | | Cinematic word rise | 0.5–0.7s | <0.4s snaps / >1.0s telegraphed | | Cinematic word hold | 2.5–3.5s | <1.5s unread / >5.0s overstays | | Line self-draw | 0.9–1.5s | <0.5s snap / >2.5s reluctant | | Ken Burns motion | 10–18s | <6s too fast / >22s static | | Text beat gap | 3.0–5.0s | <1.5s unreadable | Causality: Cause completes at T. Effect begins at T + 0.3s. Always. Simultaneous = coincidence. Scene windows: | Window | Timing | What | |---|---|---| | Initialization | 0–2s | World arrives. Image blooms. Nothing else. | | Settlement | 2–5s | Ken Burns begins. Overlay rises. World icons settle. | | Drama | 5s to end−3s | Text beats. Physics. Activations. 3s+ between beats. | | Hold | Last 3s | Final beat landed. Nothing new starts. | --- PART 3 — LAYER REASONING Every scene has five layers operating simultaneously. This is not a rule — it is the reason cinematic output feels alive while slideshows feel dead. A slideshow has one layer: text appearing. VMD has five layers moving at once. The viewer's eye is never bored because something is always happening in every layer. Layer 1 — World (image) The image never exists just to look good. It sets the emotional register of everything that follows. The Earthrise image growing from a single dot doesn't just show Earth — it says "something cosmic is being born." The soldier image blurred then sharpening doesn't just establish a battlefield — the blur-to-sharp IS the moment of focus, of attention being demanded. Choose the image for what it argues, not what it depicts. Then move it always — Ken Burns never stops. A static image is a photograph. A breathing image is a world. Layer 2 — Atmosphere (overlay) The overlay is the emotional temperature of the scene. Crimson overlay on the Caesar scene doesn't just darken the image — it says "power, blood, history." Amber flood on the bloom scene says "warmth, creativity, peak." The overlay always rises then retreats — it never stays at peak because that would dominate and kill the drama layer. It establishes feeling then steps back. The color choice is not aesthetic — it encodes the argument's emotional register. Layer 3 — Stage Frame (vignette, panels, bars) This layer controls attention without the viewer knowing it. The dark panel in the Tesla split-panel scene doesn't just look cinematic — it removes everything outside the panel from competition. The viewer's eye has nowhere to go except the text. The vignette darkens the corners, centering attention. Panel bars at top and bottom frame the drama like a cinema aspect ratio. This layer forms slowly — 2.5–4.0s — because it should feel like the world narrowing to what matters. Layer 4 — Drama (text, icon actors, connections, physics) This is where the argument lives. But here is what separates cinematic from slideshow: drama must have causality. Things happen *because* other things happened. The electron spawns because the circuit closed. The label updates because the variable changed. The kid reacts because the sound arrived. The connection draws itself between two icons because a relationship is forming in real time. If elements just appear at scheduled times with no causal chain — it is a slideshow. If every appearance is a response to something — it is alive. Layer 5 — Physics / Detail (particles, ambient icons, background elements) This layer makes the world feel like it exists independently of the story. The particles in the city night scene drift upward whether or not anyone is watching. The cog icons rotate at the scene edge with no relationship to the drama. That indifference is what makes the world feel real. This layer never reacts to drama events. It never tells the story. It breathes. Keep it at canvas edges. Keep opacity at /0.05–0.12. Keep motion linear or ease.inOut — never spring, never elastic. --- INITIALIZATION — choose one, use it for what it argues Scanline Boot — for technical, product, system, precision scenes. The scan line sweeps the image from darkness to visibility. This says: a machine is booting, a system is initializing, precision is being assembled. It's not decorative — it establishes that what you're about to see was built, engineered, loaded. beam rect w:16 h:0.06–0.10 accent color /0.45–0.65 linear + warmth rect h:0.7–1.0 same color /0.06–0.10 + image grayscale:1.0 opacity:0.0 → visible during sweep → grayscale:0.0 after. All three start at t=0, sweep completes in 1.5–2.0s, color transition begins 0.2–0.3s after sweep ends. Iris Birth — for historical, mythic, portrait, high-weight documentary. The image grows from a single seed point. This says: something is being revealed, something important is emerging from nothing. The seed circle color matches the overlay — they are the same event. Shockwave rings expand from the same point — the birth has force. seed circle r:0.04–0.06 fill=overlay color → image grows w:0.2 h:0.2 radius:8.0 → w:16.5 h:12.5 radius:0.0 → shockwave A and B from same point. ease.in mandatory — the reveal accelerates, like something breaking through. Blur-to-Sharp — for documentary establishing shots, zoom-out reveals. The world arrives blurred and large then sharpens as it contracts to frame. This says: we are moving from chaos to clarity, from distance to focus. Scale reduction is mandatory — the blur is not a filter, it's a perspective shift. w:20–24 blur:3.0–5.0 → w:15–17 blur:0.0 over 2.5–4.0s ease.inOut. Dark overlay rises simultaneously. Size-Zero Bloom — for revelation, cosmic scale, abstract concepts. Something emerges from a point and expands to fill everything. Use when the argument is about scale, about emergence, about something small becoming everything. w:0.3 h:0.3 radius:0.0 → full canvas. ease.in mandatory. --- KEN BURNS — mandatory on every image scene The image must always be moving. A still image is a photograph. Motion says the world exists in time. Pattern: hold at start state linear for 4–7s → then move. The hold breath before motion is what makes the motion feel intentional, not restless. - Approach: w:13–15 → w:16–18 over 6–10s ease.inOut. World growing toward viewer. - Drift: position shifts ±0.5–1.5 units over 8–12s ease.inOut. World drifting, not panning. - Curtain reveal: crop morphs from zero width to full over 3.5–5.0s ease.inOut. Drama begins only after curtain >80% complete. - Fixed breathe: 5–10% scale change over 7–12s ease.inOut. Stability, permanence, weight. Never completes before scene ends. Never moves more than 15% scale on breathe — that reads as zoom, not life. --- OVERLAY ARC — mandatory on every image scene Full-canvas rectangle. Always four steps: 1. Hold at opacity:0.0 for 2.5–4.0s — image establishes alone first 2. Rise to peak /0.18–0.32 over 3.0–4.5s ease.inOut 3. Hold at peak through drama 4. Retreat to /0.10–0.20 over 3.5–5.0s ease.inOut — never to zero, atmosphere must persist | Name | Hex | Peak | When | |---|---|---|---| | Crimson | #8b1a1a | /0.20–0.24 | Power, dominance, historical force | | Amber flood | #92400e | /0.28–0.32 | Warmth, creative peak, fire | | Violet memory | #2e1065 | /0.24–0.30 | Legacy, depth, the past | | Bronze shadow | #2c1810 | /0.26–0.32 | Weight, endurance, history | | Deep green | #052e16 | /0.24–0.28 | Nature, biological, ancient | | Indigo gravity | #0d0d2b | /0.18–0.24 | Cosmic scale, myth, vastness | The overlay color appears nowhere else in the scene. It is the atmosphere's exclusive register. --- WORLD ICONS — mandatory, every scene 4–8 icons at canvas edges and corners. Never within 3 units of center. Opacity /0.05–0.12. These arrive from frame zero — the world exists before the story starts. They tell the viewer: what kind of world is this? A server room has cog and CPU icons at the edges. A nature scene has leaf and tree icons drifting slowly. A cosmic scene has orbit and satellite icons rotating. Choose them for the world being depicted, not for decoration. Motion: rotate(360deg, 7–11s, linear, 0s) for mechanical worlds. Slow drift 8–12s ease.inOut for organic worlds. No motion for permanence and weight. On density — what a real scene actually contains: A production VMD scene has 40–100 elements total across all five layers. This is not excess — it is what makes the world feel real and the argument feel inevitable. The Ohm's Law scene has 93 elements. The Doppler scene has 43. A typical scene breakdown looks like this: - World layer: 4–8 ambient icons at edges, all present from frame zero - Atmosphere layer: 3–5 elements — overlay rectangle, vignette, Ken Burns image morph, scanline or iris birth components - Stage frame layer: 1–3 panels, bars, or dividing lines - Drama layer: 20–60+ active elements — icon actors, text beats, connecting lines, physics morphs, variable labels, reaction icons, particles, wave ring clusters, function graphs — all encoding the same argument from different angles. The Doppler drama layer alone contains 18 wave ring morphs, 12 music note morphs, 3 function graphs, a car morph chain, a kid reaction chain, 3 sound path lines, and 6 label morphs. - Physics/detail layer: 4–10 ambient particles, drifting icons, background pulses There is no cap on drama layer elements. The constraint is coherence, not count. Ten elements arguing the same thing simultaneously feel like one powerful statement. Three elements arguing three different things feel like noise. The Ohm's Law scene has electrons, a resistor body, a battery glow, three variable labels, fire icons, an equation, and contextual labels all active at once — because every single one encodes the same argument: voltage drives current, resistance opposes it. They harmonize. They do not compete. If you find yourself thinking "this scene has too many elements" — the question is not how many. The question is: are they all saying the same thing in different ways? If yes, keep them all. If no, cut the ones that aren't. --- CINEMATIC MOMENT — only when earned, only once per scene The entire scene builds toward one phrase. It lands as the summary of everything shown, not as a label announcing what comes next. This is the moment the viewer feels the argument rather than understanding it. World dim: circle r:22 fill:#000000/0.65–0.70 expands from center 0.8s ease.out at moment T. Text: rises from opacity:0.0 to /0.95–0.97 over 0.5–0.7s ease.out at T + 0.3s. Holds 2.5–3.5s. Shrinks to fontSize:16–18 fill:/0.20–0.25 over 0.8–1.0s ease.in. Amber #ffd54f appears nowhere before this moment. If amber appears early, the moment is destroyed. Whisper lines arrive 0.3–0.5s after the cinematic text shrinks. --- MOTION DECK ARCHITECTURES Three structures. Each serves a different argument. Never mix them within one scene. --- SPLIT PANEL Use when: the claim is multi-line, complex, or needs to be read without competing with the image. Canvas split vertically. Dark panel fill:#000000/0.78–0.88 on right or left, 35–45% of canvas width. Image fills the remaining side and breathes behind it. Vertical dividing line stroke:#accent/0.45–0.60 strokeWidth:1–1.5 rises 0.6–0.8s ease.out at the same moment the panel rises. Text lives only inside the panel — never crosses the line. Image never encroaches into panel territory. Panel timing: rises 1.2–1.5s ease.out after image establishes (settlement window). Text beats begin only after panel is fully formed. Diagram elements, icon chains, and sub-labels also live inside the panel. The panel does not need to be black. For technical scenes: #0a0a0a. For warm scenes: #0d0a06. Always dark enough that fill:#ffffff/0.90 text reads cleanly against it. --- IMMERSIVE Use when: the claim should feel like a statement of fact about the world the viewer is standing inside. Full bleed image — no panel, no division. Two letter-box bars create cinema framing: - Top bar: rectangle w:16 h:2.5–3.5 fill:#000000/0.72–0.82 anchored at y:5.0–5.5 - Bottom bar: rectangle w:16 h:5.0–6.5 fill:#000000/0.82–0.90 anchored at y:-4.0–-4.5 Both bars start at h:0.1 fill:/0.0 and rise after image establishes — 1.4–1.6s ease.out staggered 0.3s apart. The bottom bar is taller — it creates a stage for icon nodes, data points, or sub-elements to sit in. Text lives centered on the image between the two bars. focusPull or wordReveal mandatory for primary claims — the text must emerge from the world, not be placed on it. Scene tag sits inside the top bar at y:4.7. Icon diagram nodes sit inside the bottom bar at y:-4.2 to -4.5. Vignette mandatory: rectangle w:16 h:12 fill:#000000/0.38–0.48 rises 3.0–4.0s ease.out to center viewer attention. Without it the image competes with the text. Physics layer: 6–10 particle lights drifting upward from mid-canvas, fading to transparent as they exit top frame. Fill circles, r:0.04–0.08, ease.out, staggered delays. These are city lights, bokeh, embers — whatever the world contains. They move because the world is alive, not because the argument needs them. --- LIVING CANVAS Use when: the subject is growth, emergence, transformation, creativity — when the physics of the scene IS the argument. Full bleed image — no panel, no bars. The image and the physics share the canvas as equal partners. The text arrives into a world that is already moving. Active physics layers (choose what fits the argument — never all at once): - Parametric rings drawing outward from image center: use only when the concept involves something opening, blooming, or expanding from a source. tStart:0 tEnd:0 → tEnd:6.28 over 3.0–5.0s ease.inOut. Three rings, staggered 0.8–1.0s, expanding radii 1.6 / 2.8 / 4.2. Each a different accent color at low opacity /0.14–0.35. They bloom like something opening. If your concept does not open or bloom — find the physics that IS your concept instead. - Falling/drifting particles: fill circles r:0.035–0.07, starting above mid-canvas, fading as they travel downward or float upward. ease.inOut travel 8–10s. 3–5 particles staggered. - World icon drift: organic icons (flowers, leaves, petals) at canvas edges, very slowly shifting 0.3–0.5 units over 8–12s ease.inOut. Never rotating. Just alive. Text positioning: left of center or right of center — never dead center, because the physics are happening there. focusPull mandatory for primary claim. The text arrives after the physics have started — the world is already in motion when the claim lands. Black cut-in: Living Canvas scenes often begin with a rectangle fill:#000000/1.0 that fades to 0.0 in the first 0.6–0.8s ease.out. This gives a hard start to the scene that feels like a breath in before the bloom. Use it when the previous scene was dense or bright. The failure mode: loading the canvas with so many physics elements that the text becomes invisible. The physics exist to make the world feel alive. The text still carries the argument. Keep physics below /0.55 opacity. Keep text at /0.72 minimum. --- PHYSICS MORPH CHAIN — encoding variables in motion When a scene argues about variables changing, the variables must be visible in the motion — not just in labels. Every variable has one visual encoding, consistent across all morph steps. | Variable state | Visual encoding | |---|---| | Value increasing | Opacity rises, color brightens toward lighter hue | | Value at peak | Color shifts warmer or lighter | | Value decreasing | Opacity drops, color shifts cooler | | Speed / frequency up | Travel duration decreases in morph chain | | Energy / intensity up | r increases, strokeWidth increases, aura expands | | Heat / friction | Fire icons bloom on element: size 0.5–0.8 ease.spring | | System stress | Color morphs toward #ff6b9d | | System resolution | Color morphs toward #6bffb8 | Text labels confirm what the visual already shows. They never replace it. If the viewer needs the label to understand the physics, the animation has failed. Particle circuit: circuit lines draw first. Particles spawn after circuit is complete — 0.3s causality gap. Each particle visits circuit corners in sequence. Travel duration per segment encodes speed — shorter = faster current. Multiple particles staggered 0.3–0.6s. Energy progression through the scene: #4fc3f7/0.88–0.95 normal → #67e8f9/0.95 faster, shorter durations → #b9f3ff/0.97–0.99 peak, shortest → #4fc3f7/0.60–0.70 reduced. --- WAVE RING CLUSTER — encoding propagation and motion Wave rings mean one thing: something is propagating outward through a medium. Sound waves, electromagnetic pulses, signal broadcast, pressure waves. If the concept has no propagation, there are no rings. Stroke-only circles expanding from a source. Triple-zero seed. Expand 1.2–2.0s ease.out, fade as they dissipate. Multiple rings staggered 0.3–0.5s. Moving source — this is the physics: ring centers are NOT at the source's current position. They are offset in the direction of motion. Approaching source: centers displaced forward of emission point — rings compress ahead of the car, expand behind. Receding source: centers displaced backward. The offset IS the Doppler effect. The animation doesn't explain it. The animation IS it. 5–6 rings per cluster is normal. Each gets its own morph chain. The stagger makes the cluster read as a continuous emission, not a single burst. --- SEQUENTIAL TEXT BEATS Primary arrives via focusPull or wordReveal. Supporting arrives 0.3s after (same beat, causally linked). Next beat minimum 3.0s after previous beat ends. Sub-lines fadeIn. Whispers fadeIn last. Every line invisible at t=0 — use hold step or fill:/0.0 seed. Never a visible color with a delayed fadeIn — the element would be visible from frame zero. Text hierarchy: - Primary: fontSize:46–64 fontWeight:bold accent or #ffffff/0.88–0.96 - Supporting: fontSize:22–52 #ffffff/0.75–0.92 - Sub-line: fontSize:14–22 #ffffff/0.40–0.65 - Whisper: fontSize:13–15 #ffffff/0.28–0.35 --- CORNER GHOST WORDS — multi-scene arcs Previous scenes leave their cinematic word as a ghost at the canvas bottom. Accumulated proof. fontSize:13–15 fontWeight:bold at y:−5.2–-5.3. Color = that concept's accent at /0.22–0.30. Arrive quietly during drama window. Never animate after arrival. Accumulate left-to-right. This tells the viewer: everything we've shown is still here, still true, building toward something. --- PART 4 — SCENE REASONING Four complete scenes broken down as director's decisions. Read the reasoning first. The code is proof of the reasoning, not the other way around. --- SCENE 1 — OHM'S LAW (Cinematic Documentary / Physics) The argument: Voltage drives current through resistance. Raise voltage, electrons accelerate. Raise resistance, electrons slow and the resistor heats. The relationship is exact: V = I · R. The directorial question: How do you make that relationship *visible* rather than stated? The answer: Make current a physical thing. Electrons are real objects moving through a real circuit. Their speed directly encodes the value of current — when voltage rises, the electrons move faster, and you watch it happen in real time. When resistance rises, they slow and the resistor body glows hotter. The equation V = I · R lands *after* the physics — as confirmation of what the viewer already watched, not as explanation of what they're about to see. Why the image blooms then retreats: The electric bolt image establishes "this is a world of electricity" in the first 1.5 seconds. Then it fades to near-invisible grayscale at opacity 0.20. If the image stayed bright, the circuit and electrons would compete with it. The image steps back so the physics can live on top of it. Why the circuit builds piece by piece: Top wire draws first, then bottom, then right side, then left sides, then battery, then components. This is not sequential animation for visual interest — it is the circuit being assembled. The viewer watches the path that electrons will travel being constructed. When the circuit closes (all pieces drawn), the electrons spawn 0.3s later. That gap is causality. The circuit closes, then current flows. Why there are three electrons staggered 0.3s apart: One electron moving around a circuit is not current. Three electrons staggered produce the visual impression of continuous flow. The stagger is not cosmetic — it encodes the distributed nature of electron movement through a conductor. Why the electron chain has 14+ morph steps: A single pass around the circuit takes roughly 7.5s. The scene is 30s. The electron needs to complete multiple laps so that speed differences between physics states are legible to the eye — one lap isn't enough time to feel the difference between "slow" and "fast." The chain encodes: lap 1 (normal, cyan /0.95), lap 2 (faster, lighter cyan /0.97), lap 3 (peak, pale cyan /0.99), beginning of lap 4 (reduced, cyan /0.65 — current dropping as resistance rises). The color progression IS the physics narrative. Why the grid appears at t=4.0s: The grid provides spatial reference for the circuit components that arrive next. It draws itself (lines extend from zero length) because the world is being measured, not just seen. A pre-existing grid would feel like a template. A grid that draws itself feels like precision being imposed. Why the variable labels update with the physics: When voltage rises to 3V at t=12.5s, the voltage label morphs from "V = 1V" to "V = 3V" in 0.5s — simultaneously with the electron speeding up. The viewer sees the number change and sees the electron move faster. One confirms the other. Neither needs explanation. Why there are two fire icons on the resistor: When resistance rises at t=19s, the resistor body glows hotter (fill opacity rises, color intensifies) and two fire icons bloom above it via ease.spring. Fire is the universal visual language for heat. The viewer doesn't need a label saying "heat generated." They see it. Why the cinematic moment is "OHM'S LAW" at fontSize:52 not the equation: The equation V = I · R already landed at t=8.2s. By the time the cinematic word arrives at t=25.8s, the viewer has watched all three variables change, watched the electrons respond, watched the resistor heat. The cinematic word is not teaching them Ohm's Law — it is naming what they just witnessed. concept: "Ohm's Law" emoji: "⚡" total_duration: 30 background: "#000000" subtitles: - "" phases: scene_ohm: duration: 30 description: "Electric image blooms full color, typewriter title, slow grayscale, circuit builds piece by piece, equation holds, electrons spawn immediately at circuit close, speed and color change with each physics beat, image stays dark, cinematic word lands" elements: electric_image: type: morph # Blooms to full color in 1.5s, then fades to near-invisible grayscale by 3.3s. # Image steps back so the circuit and electrons can live on top of it. shape: image[src:"images/icons/electric.png" w:18.0 h:13.0 fit:cover radius:0.0 blur:0.0 opacity:0.0 grayscale:0.0 at:(0,0)] ~> image[src:"images/icons/electric.png" w:18.0 h:13.0 fit:cover radius:0.0 blur:0.0 opacity:0.94 grayscale:0.0 at:(0,0)] (1.5s, ease.out, 0.0s) ~> image[src:"images/icons/electric.png" w:18.5 h:13.5 fit:cover radius:0.0 blur:0.0 opacity:0.20 grayscale:1.0 at:(0,0)] (1.8s, ease.out, 1.5s) ~> image[src:"images/icons/electric.png" w:18.5 h:13.5 fit:cover radius:0.0 blur:0.0 opacity:0.20 grayscale:1.0 at:(0,0)] (26.7s, linear, 3.3s) title_typewriter: type: primitive # Typewriter: reveals character by character — feels like precision, like a label being applied to something real. shape: text[text:"OHM'S LAW" fontSize:36 fill:#ffffff/0.90 fontWeight:bold at:(0,4.5)] -> typewriter(1.1s, 0.4s) title_whisper: type: morph # Title shrinks to whisper after the circuit begins building. It served its purpose. Now it steps to the margin. shape: text[text:"OHM'S LAW" fontSize:36 fill:#ffffff/0.90 fontWeight:bold at:(0,4.5)] ~> text[text:"OHM'S LAW" fontSize:13 fill:#ffffff/0.28 fontWeight:bold at:(0,5.2)] (0.7s, ease.inOut, 3.2s) grid_x: type: morph # Grid draws itself at t=4s — spatial reference for circuit components arriving next. Self-drawing = precision being imposed. shape: line[from:(0,0) to:(0,0) stroke:#ffffff/0.08 strokeWidth:0.5 at:(0,0)] ~> line[from:(-8,0) to:(8,0) stroke:#ffffff/0.08 strokeWidth:0.5 at:(0,0)] (1.0s, ease.out, 4.0s) grid_h1: type: morph shape: line[from:(0,1) to:(0,1) stroke:#ffffff/0.04 strokeWidth:0.4 at:(0,0)] ~> line[from:(-8,1) to:(8,1) stroke:#ffffff/0.04 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.1s) grid_hn1: type: morph shape: line[from:(0,-1) to:(0,-1) stroke:#ffffff/0.04 strokeWidth:0.4 at:(0,0)] ~> line[from:(-8,-1) to:(8,-1) stroke:#ffffff/0.04 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.1s) grid_h2: type: morph shape: line[from:(0,2) to:(0,2) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] ~> line[from:(-8,2) to:(8,2) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.2s) grid_hn2: type: morph shape: line[from:(0,-2) to:(0,-2) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] ~> line[from:(-8,-2) to:(8,-2) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.2s) grid_v2: type: morph shape: line[from:(2,0) to:(2,0) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] ~> line[from:(2,-4) to:(2,4) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.15s) grid_vn2: type: morph shape: line[from:(-2,0) to:(-2,0) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] ~> line[from:(-2,-4) to:(-2,4) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.15s) grid_v4: type: morph shape: line[from:(4,0) to:(4,0) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] ~> line[from:(4,-4) to:(4,4) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.25s) grid_vn4: type: morph shape: line[from:(-4,0) to:(-4,0) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] ~> line[from:(-4,-4) to:(-4,4) stroke:#ffffff/0.03 strokeWidth:0.4 at:(0,0)] (1.0s, ease.out, 4.25s) circuit_top: type: morph # Top wire draws first — the path electrons will travel is being assembled. # Later morph steps change color when current increases: orange tint = more energy in the wire. shape: line[from:(0,2.2) to:(0,2.2) stroke:#ffffff/0.0 strokeWidth:2.2 at:(0,0)] ~> line[from:(-5.0,2.2) to:(5.0,2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (1.0s, ease.out, 5.0s) ~> line[from:(-5.0,2.2) to:(5.0,2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (5.0s, linear, 7.5s) ~> line[from:(-5.0,2.2) to:(5.0,2.2) stroke:#ff9f43/0.40 strokeWidth:2.4 at:(0,0)] (0.8s, ease.out, 5.0s) ~> line[from:(-5.0,2.2) to:(5.0,2.2) stroke:#ff9f43/0.65 strokeWidth:2.6 at:(0,0)] (0.8s, ease.out, 3.0s) ~> line[from:(-5.0,2.2) to:(5.0,2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (0.8s, ease.out, 3.5s) circuit_bottom: type: morph shape: line[from:(0,-2.2) to:(0,-2.2) stroke:#ffffff/0.0 strokeWidth:2.2 at:(0,0)] ~> line[from:(-5.0,-2.2) to:(5.0,-2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (1.0s, ease.out, 5.5s) ~> line[from:(-5.0,-2.2) to:(5.0,-2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (4.5s, linear, 7.5s) ~> line[from:(-5.0,-2.2) to:(5.0,-2.2) stroke:#ff9f43/0.25 strokeWidth:2.3 at:(0,0)] (0.8s, ease.out, 4.5s) ~> line[from:(-5.0,-2.2) to:(5.0,-2.2) stroke:#ff9f43/0.40 strokeWidth:2.4 at:(0,0)] (0.8s, ease.out, 3.0s) ~> line[from:(-5.0,-2.2) to:(5.0,-2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (0.8s, ease.out, 3.5s) circuit_right: type: morph shape: line[from:(5.0,0) to:(5.0,0) stroke:#ffffff/0.0 strokeWidth:2.2 at:(0,0)] ~> line[from:(5.0,-2.2) to:(5.0,2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (0.9s, ease.out, 6.2s) circuit_left_top: type: morph shape: line[from:(-5.0,0.7) to:(-5.0,0.7) stroke:#ffffff/0.0 strokeWidth:2.2 at:(0,0)] ~> line[from:(-5.0,0.7) to:(-5.0,2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (0.9s, ease.out, 6.2s) circuit_left_bot: type: morph shape: line[from:(-5.0,-0.7) to:(-5.0,-0.7) stroke:#ffffff/0.0 strokeWidth:2.2 at:(0,0)] ~> line[from:(-5.0,-0.7) to:(-5.0,-2.2) stroke:#ffffff/0.55 strokeWidth:2.2 at:(0,0)] (0.9s, ease.out, 6.2s) battery_plate_long: type: primitive # Battery component appears after circuit closes. Warm yellow = power source. The component IS the concept. shape: line[from:(-4.35,0.55) to:(-5.65,0.55) stroke:#fbbf24/0.95 strokeWidth:4.5 at:(0,0)] -> fadeIn(0.7s, 7.0s) battery_plate_short: type: primitive shape: line[from:(-4.6,-0.55) to:(-5.4,-0.55) stroke:#fbbf24/0.60 strokeWidth:2.5 at:(0,0)] -> fadeIn(0.7s, 7.1s) battery_plus: type: primitive shape: text[text:"+" fontSize:16 fill:#fbbf24/0.85 at:(-4.2,0.55)] -> fadeIn(0.5s, 7.4s) battery_minus: type: primitive shape: text[text:"−" fontSize:16 fill:#fbbf24/0.60 at:(-4.2,-0.55)] -> fadeIn(0.5s, 7.4s) battery_icon: type: primitive shape: icon[icon:"mdi:battery-high" size:1.4 color:#fbbf24/0.70 at:(-6.2,0)] -> fadeIn(0.7s, 7.0s) resistor_body: type: morph # Resistor morphs through three states: neutral → current flowing (orange tint) → high resistance (more intense). # Fill opacity rising = more energy being dissipated as heat. The body IS the resistance variable. shape: rectangle[w:2.2 h:0.55 fill:#000000 stroke:#ff9f43/0.90 strokeWidth:2.0 at:(0,2.2)] ~> rectangle[w:2.2 h:0.55 fill:#000000 stroke:#ff9f43/0.90 strokeWidth:2.0 at:(0,2.2)] (7.5s, linear, 0.0s) ~> rectangle[w:2.2 h:0.55 fill:#ff9f43/0.08 stroke:#ff9f43/0.90 strokeWidth:2.0 at:(0,2.2)] (0.6s, ease.out, 7.5s) ~> rectangle[w:2.2 h:0.55 fill:#ff9f43/0.16 stroke:#ff9f43/0.95 strokeWidth:2.5 at:(0,2.2)] (0.6s, ease.out, 5.0s) ~> rectangle[w:2.2 h:0.65 fill:#ff9f43/0.30 stroke:#ff9f43/0.98 strokeWidth:3.0 at:(0,2.2)] (0.6s, ease.out, 3.0s) ~> rectangle[w:2.2 h:0.65 fill:#ff9f43/0.25 stroke:#ff6b9d/0.95 strokeWidth:3.0 at:(0,2.2)] (0.6s, ease.out, 3.5s) resistor_label: type: primitive shape: text[text:"R" fontSize:16 fill:#ff9f43/0.85 at:(0,2.9)] -> fadeIn(0.6s, 7.6s) ammeter_circle: type: primitive # Ammeter at the right side: measures current. Mint green = measurement, confirmation, correctness. shape: circle[r:0.42 fill:#000000 stroke:#6bffb8/0.85 strokeWidth:2.0 at:(5.0,0)] -> fadeIn(0.7s, 7.2s) ammeter_label: type: primitive shape: text[text:"A" fontSize:16 fill:#6bffb8/0.90 at:(5.0,0)] -> fadeIn(0.6s, 7.5s) battery_glow: type: morph # Glow expands as voltage rises. The aura around the battery IS the voltage variable made visible. # Seed r:0.0 — non-zero radius renders even with opacity:0, causing an artifact dot at frame zero. shape: circle[r:0.0 fill:#fbbf24/0.0 stroke:#fbbf24/0.0 strokeWidth:1 at:(-5.0,0)] ~> circle[r:0.8 fill:#fbbf24/0.12 stroke:#fbbf24/0.40 strokeWidth:1.2 at:(-5.0,0)] (0.7s, ease.out, 7.5s) ~> circle[r:1.4 fill:#fbbf24/0.08 stroke:#fbbf24/0.25 strokeWidth:1.0 at:(-5.0,0)] (0.7s, ease.out, 5.0s) ~> circle[r:2.2 fill:#fbbf24/0.12 stroke:#fbbf24/0.38 strokeWidth:1.4 at:(-5.0,0)] (0.7s, ease.out, 3.0s) ~> circle[r:1.6 fill:#fbbf24/0.06 stroke:#fbbf24/0.18 strokeWidth:1.0 at:(-5.0,0)] (0.7s, ease.out, 3.5s) eq_ohm: type: morph # The equation lands AFTER the physics. Not before. Confirmation, not instruction. # It blooms from invisible — this is the insight becoming visible, not a label being placed. shape: text[text:"V = I · R" fontSize:30 fill:#ffffff/0.0 fontWeight:bold at:(0,0)] ~> text[text:"V = I · R" fontSize:30 fill:#ffffff/0.90 fontWeight:bold at:(0,0)] (0.9s, ease.out, 8.2s) voltage_label: type: morph # Label updates simultaneously with physics state change. The number changes when the electron changes speed. # This is coordinated — three elements move together: electron duration, label text, battery glow radius. shape: text[text:"V = 1V" fontSize:20 fill:#fbbf24/0.0 fontWeight:bold at:(-5.5,-3.5)] ~> text[text:"V = 1V" fontSize:20 fill:#fbbf24/0.88 fontWeight:bold at:(-5.5,-3.5)] (0.7s, ease.out, 9.0s) ~> text[text:"V = 3V" fontSize:20 fill:#fbbf24/0.88 fontWeight:bold at:(-5.5,-3.5)] (0.5s, ease.out, 3.5s) ~> text[text:"V = 6V" fontSize:20 fill:#fbbf24/0.88 fontWeight:bold at:(-5.5,-3.5)] (0.5s, ease.out, 3.0s) ~> text[text:"V = 6V" fontSize:20 fill:#fbbf24/0.88 fontWeight:bold at:(-5.5,-3.5)] (0.5s, ease.out, 4.5s) resistance_label: type: morph shape: text[text:"R = 1Ω" fontSize:20 fill:#ff9f43/0.0 fontWeight:bold at:(0,-3.5)] ~> text[text:"R = 1Ω" fontSize:20 fill:#ff9f43/0.88 fontWeight:bold at:(0,-3.5)] (0.7s, ease.out, 9.0s) ~> text[text:"R = 1Ω" fontSize:20 fill:#ff9f43/0.88 fontWeight:bold at:(0,-3.5)] (0.5s, ease.out, 3.5s) ~> text[text:"R = 1Ω" fontSize:20 fill:#ff9f43/0.88 fontWeight:bold at:(0,-3.5)] (0.5s, ease.out, 3.0s) ~> text[text:"R = 3Ω" fontSize:20 fill:#ff9f43/0.88 fontWeight:bold at:(0,-3.5)] (0.5s, ease.out, 4.5s) current_label: type: morph shape: text[text:"I = 1A" fontSize:20 fill:#6bffb8/0.0 fontWeight:bold at:(5.5,-3.5)] ~> text[text:"I = 1A" fontSize:20 fill:#6bffb8/0.88 fontWeight:bold at:(5.5,-3.5)] (0.7s, ease.out, 9.0s) ~> text[text:"I = 3A" fontSize:20 fill:#6bffb8/0.88 fontWeight:bold at:(5.5,-3.5)] (0.5s, ease.out, 3.5s) ~> text[text:"I = 6A" fontSize:20 fill:#6bffb8/0.88 fontWeight:bold at:(5.5,-3.5)] (0.5s, ease.out, 3.0s) ~> text[text:"I = 2A" fontSize:20 fill:#6bffb8/0.88 fontWeight:bold at:(5.5,-3.5)] (0.5s, ease.out, 4.5s) electron_1: type: morph # Electron spawns 0.3s after circuit closes (causality gap). Triple-zero hold for 7.5s. # Then traverses circuit corners. Each leg duration = speed of current. # Color progression: cyan → lighter cyan → pale cyan → reduced cyan = energy rising then resistance taking effect. # Three electrons staggered 0.3s apart = continuous flow, not single pulse. shape: circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(-5.0,2.2)] ~> circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(-5.0,2.2)] (7.5s, linear, 0.0s) ~> circle[r:0.20 fill:#4fc3f7/0.95 stroke:#ffffff/0.70 strokeWidth:1.5 at:(-5.0,2.2)] (0.35s, ease.spring, 7.5s) ~> circle[r:0.20 fill:#4fc3f7/0.95 stroke:#ffffff/0.70 strokeWidth:1.5 at:(5.0,2.2)] (3.0s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#4fc3f7/0.95 stroke:#ffffff/0.70 strokeWidth:1.5 at:(5.0,-2.2)] (1.4s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#4fc3f7/0.95 stroke:#ffffff/0.70 strokeWidth:1.5 at:(-5.0,-2.2)] (3.0s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#4fc3f7/0.95 stroke:#ffffff/0.70 strokeWidth:1.5 at:(-5.0,2.2)] (1.4s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#67e8f9/0.97 stroke:#ffffff/0.80 strokeWidth:1.5 at:(5.0,2.2)] (1.6s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#67e8f9/0.97 stroke:#ffffff/0.80 strokeWidth:1.5 at:(5.0,-2.2)] (0.7s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#67e8f9/0.97 stroke:#ffffff/0.80 strokeWidth:1.5 at:(-5.0,-2.2)] (1.6s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#67e8f9/0.97 stroke:#ffffff/0.80 strokeWidth:1.5 at:(-5.0,2.2)] (0.7s, ease.inOut, 0.0s) ~> circle[r:0.24 fill:#b9f3ff/0.99 stroke:#ffffff/0.90 strokeWidth:1.5 at:(5.0,2.2)] (0.9s, ease.inOut, 0.0s) ~> circle[r:0.24 fill:#b9f3ff/0.99 stroke:#ffffff/0.90 strokeWidth:1.5 at:(5.0,-2.2)] (0.4s, ease.inOut, 0.0s) ~> circle[r:0.24 fill:#b9f3ff/0.99 stroke:#ffffff/0.90 strokeWidth:1.5 at:(-5.0,-2.2)] (0.9s, ease.inOut, 0.0s) ~> circle[r:0.24 fill:#b9f3ff/0.99 stroke:#ffffff/0.90 strokeWidth:1.5 at:(-5.0,2.2)] (0.4s, ease.inOut, 0.0s) ~> circle[r:0.19 fill:#4fc3f7/0.65 stroke:#ffffff/0.45 strokeWidth:1.5 at:(5.0,2.2)] (2.2s, ease.inOut, 0.0s) ~> circle[r:0.19 fill:#4fc3f7/0.65 stroke:#ffffff/0.45 strokeWidth:1.5 at:(5.0,-2.2)] (1.0s, ease.inOut, 0.0s) electron_2: type: morph shape: circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(0,2.2)] ~> circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(0,2.2)] (7.8s, linear, 0.0s) ~> circle[r:0.18 fill:#4fc3f7/0.88 stroke:#ffffff/0.60 strokeWidth:1.5 at:(0,2.2)] (0.35s, ease.spring, 7.8s) ~> circle[r:0.18 fill:#4fc3f7/0.88 stroke:#ffffff/0.60 strokeWidth:1.5 at:(5.0,2.2)] (1.5s, ease.inOut, 0.0s) ~> circle[r:0.18 fill:#4fc3f7/0.88 stroke:#ffffff/0.60 strokeWidth:1.5 at:(5.0,-2.2)] (1.4s, ease.inOut, 0.0s) ~> circle[r:0.18 fill:#4fc3f7/0.88 stroke:#ffffff/0.60 strokeWidth:1.5 at:(-5.0,-2.2)] (3.0s, ease.inOut, 0.0s) ~> circle[r:0.18 fill:#4fc3f7/0.88 stroke:#ffffff/0.60 strokeWidth:1.5 at:(-5.0,2.2)] (1.4s, ease.inOut, 0.0s) ~> circle[r:0.18 fill:#4fc3f7/0.88 stroke:#ffffff/0.60 strokeWidth:1.5 at:(0,2.2)] (1.5s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#67e8f9/0.95 stroke:#ffffff/0.75 strokeWidth:1.5 at:(5.0,2.2)] (0.8s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#67e8f9/0.95 stroke:#ffffff/0.75 strokeWidth:1.5 at:(5.0,-2.2)] (0.7s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#67e8f9/0.95 stroke:#ffffff/0.75 strokeWidth:1.5 at:(-5.0,-2.2)] (1.6s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#67e8f9/0.95 stroke:#ffffff/0.75 strokeWidth:1.5 at:(-5.0,2.2)] (0.7s, ease.inOut, 0.0s) ~> circle[r:0.20 fill:#67e8f9/0.95 stroke:#ffffff/0.75 strokeWidth:1.5 at:(0,2.2)] (0.8s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#b9f3ff/0.98 stroke:#ffffff/0.88 strokeWidth:1.5 at:(5.0,2.2)] (0.45s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#b9f3ff/0.98 stroke:#ffffff/0.88 strokeWidth:1.5 at:(5.0,-2.2)] (0.4s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#b9f3ff/0.98 stroke:#ffffff/0.88 strokeWidth:1.5 at:(-5.0,-2.2)] (0.9s, ease.inOut, 0.0s) ~> circle[r:0.22 fill:#b9f3ff/0.98 stroke:#ffffff/0.88 strokeWidth:1.5 at:(-5.0,2.2)] (0.4s, ease.inOut, 0.0s) ~> circle[r:0.17 fill:#4fc3f7/0.62 stroke:#ffffff/0.40 strokeWidth:1.5 at:(0,2.2)] (2.2s, ease.inOut, 0.0s) electron_3: type: morph shape: circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(5.0,-2.2)] ~> circle[r:0.0 fill:#4fc3f7/0.0 stroke:#4fc3f7/0.0 strokeWidth:2 at:(5.0,-2.2)] (8.1s, linear, 0.0s) ~> circle[r:0.16 fill:#4fc3f7/0.82 stroke:#ffffff/0.55 strokeWidth:1.5 at:(5.0,-2.2)] (0.35s, ease.spring, 8.1s) ~> circle[r:0.16 fill:#4fc3f7/0.82 stroke:#ffffff/0.55 strokeWidth:1.5 at:(-5.0,-2.2)] (3.0s, ease.inOut, 0.0s) ~> circle[r:0.16 fill:#4fc3f7/0.82 stroke:#ffffff/0.55 strokeWidth:1.5 at:(-5.0,2.2)] (1.4s, ease.inOut, 0.0s) ~> circle[r:0.16 fill:#4fc3f7/0.82 stroke:#ffffff/0.55 strokeWidth:1.5 at:(5.0,2.2)] (3.0s, ease.inOut, 0.0s) ~> circle[r:0.16 fill:#4fc3f7/0.82 stroke:#ffffff/0.55 strokeWidth:1.5 at:(5.0,-2.2)] (1.4s, ease.inOut, 0.0s) ~> circle[r:0.19 fill:#67e8f9/0.92 stroke:#ffffff/0.70 strokeWidth:1.5 at:(-5.0,-2.2)] (1.6s, ease.inOut, 0.0s) ~> circle[r:0.19 fill:#67e8f9/0.92 stroke:#ffffff/0.70 strokeWidth:1.5 at:(-5.0,2.2)] (0.7s, ease.inOut, 0.0s) ~> circle[r:0.19 fill:#67e8f9/0.92 stroke:#ffffff/0.70 strokeWidth:1.5 at:(5.0,2.2)] (1.6s, ease.inOut, 0.0s) ~> circle[r:0.19 fill:#67e8f9/0.92 stroke:#ffffff/0.70 strokeWidth:1.5 at:(5.0,-2.2)] (0.7s, ease.inOut, 0.0s) ~> circle[r:0.21 fill:#b9f3ff/0.97 stroke:#ffffff/0.85 strokeWidth:1.5 at:(-5.0,-2.2)] (0.9s, ease.inOut, 0.0s) ~> circle[r:0.21 fill:#b9f3ff/0.97 stroke:#ffffff/0.85 strokeWidth:1.5 at:(-5.0,2.2)] (0.4s, ease.inOut, 0.0s) ~> circle[r:0.21 fill:#b9f3ff/0.97 stroke:#ffffff/0.85 strokeWidth:1.5 at:(5.0,2.2)] (0.9s, ease.inOut, 0.0s) ~> circle[r:0.21 fill:#b9f3ff/0.97 stroke:#ffffff/0.85 strokeWidth:1.5 at:(5.0,-2.2)] (0.4s, ease.inOut, 0.0s) ~> circle[r:0.16 fill:#4fc3f7/0.60 stroke:#ffffff/0.38 strokeWidth:1.5 at:(-5.0,-2.2)] (2.2s, ease.inOut, 0.0s) lbl_fast: type: morph # Contextual label appears when physics changes. Confirms what electron speed already shows. # Different label for each state: faster → even faster → slower. Text IS the narrative beat. shape: text[text:"I ↑ faster" fontSize:15 fill:#6bffb8/0.0 at:(2.5,0.8)] ~> text[text:"I ↑ faster" fontSize:15 fill:#6bffb8/0.0 at:(2.5,0.8)] (12.5s, linear, 0.0s) ~> text[text:"I ↑ faster" fontSize:15 fill:#6bffb8/0.80 at:(2.5,0.8)] (0.6s, ease.out, 12.5s) ~> text[text:"I ↑↑ faster" fontSize:15 fill:#6bffb8/0.80 at:(2.5,0.8)] (0.6s, ease.out, 3.5s) ~> text[text:"I ↓ slower" fontSize:15 fill:#ff6b9d/0.80 at:(2.5,0.8)] (0.6s, ease.out, 3.0s) ~> text[text:"I ↓ slower" fontSize:15 fill:#ff6b9d/0.0 at:(2.5,0.8)] (0.5s, ease.in, 4.0s) heat_icon_1: type: morph # Fire icons bloom on resistor when resistance rises. Universal visual = heat. No label needed. shape: icon[icon:"mdi:fire" size:0.7 color:#ff9f43/0.0 at:(0.8,3.2)] ~> icon[icon:"mdi:fire" size:0.7 color:#ff9f43/0.0 at:(0.8,3.2)] (19.0s, linear, 0.0s) ~> icon[icon:"mdi:fire" size:0.7 color:#ff9f43/0.85 at:(0.8,3.2)] (0.5s, ease.spring, 19.0s) heat_icon_2: type: morph shape: icon[icon:"mdi:fire" size:0.6 color:#ff6b9d/0.0 at:(-0.8,3.2)] ~> icon[icon:"mdi:fire" size:0.6 color:#ff6b9d/0.0 at:(-0.8,3.2)] (19.4s, linear, 0.0s) ~> icon[icon:"mdi:fire" size:0.6 color:#ff6b9d/0.80 at:(-0.8,3.2)] (0.5s, ease.spring, 19.4s) world_dim: type: morph # World dims before cinematic moment. Everything steps back. The conclusion is about to land. shape: circle[r:0.0 fill:#000000/0.0 at:(0,0)] ~> circle[r:0.0 fill:#000000/0.0 at:(0,0)] (25.5s, linear, 0.0s) ~> circle[r:22.0 fill:#000000/0.65 at:(0,0)] (0.8s, ease.out, 25.5s) word_ohm: type: morph # Cinematic word. Amber. Appears nowhere before this. Names what the viewer just watched. # Holds 2.5s then shrinks to corner whisper — proof that the argument was made. shape: text[text:"OHM'S LAW" fontSize:52 fill:#ffd54f/0.0 fontWeight:bold at:(0,0.5)] ~> text[text:"OHM'S LAW" fontSize:52 fill:#ffd54f/0.0 fontWeight:bold at:(0,0.5)] (25.8s, linear, 0.0s) ~> text[text:"OHM'S LAW" fontSize:52 fill:#ffd54f/0.97 fontWeight:bold at:(0,0.5)] (0.7s, ease.out, 25.8s) ~> text[text:"OHM'S LAW" fontSize:18 fill:#ffd54f/0.22 fontWeight:bold at:(0,0.5)] (1.0s, ease.in, 2.5s) tagline: type: primitive # Whisper arrives after cinematic word shrinks. One line that names the physics in plain language. shape: text[text:"V drives • R opposes • I flows" fontSize:14 fill:#ffffff/0.45 at:(0,-0.5)] -> fadeIn(0.6s, 26.8s) -> fadeOut(0.4s, 28.8s) --- SCENE 2 — DOPPLER EFFECT (Multi-Beat Science Explainer) The argument: Motion changes the frequency of what you hear. Approaching = compressed waves = higher pitch. Receding = stretched waves = lower pitch. The same source, three different frequencies, one cause: relative motion. The directorial question: How do you show three different states of the same phenomenon simultaneously without losing the viewer? The answer: Color-code absolutely and consistently across every layer. Cyan = approaching = high frequency. Gold = beside = true frequency. Pink = receding = low frequency. These colors appear in the wave rings, the music notes, the function graph at the bottom, and the kid's reaction color change. The viewer reads the state from color without needing to read a single label. Then all three frequencies appear as ghost lines simultaneously at the end — the visual proof that all three came from the same source. Why the rings have displaced centers: This is the entire physics argument made visible. A ring center that stays at the car's current position would show uniform wave expansion — equal in all directions, no Doppler effect. Moving the ring center forward of the car for the approaching phase means each ring was emitted from a position further back in time — the car moved forward while the ring expanded. The compression ahead, expansion behind, IS the Doppler effect. The code makes this happen by slightly advancing the center at: coordinate with each successive ring. Why there are 5–6 rings per cluster not 1–2: One ring expanding and fading is a visual event. Five rings staggered 0.3s apart is a continuous emission — the viewer perceives ongoing sound waves, not a single pulse. The stagger duration approximates the time between wave emissions. This is physics encoded in timing. Why the kid reacts with color not text: At each physics state change, the kid icon (mdi:human, mint green baseline) briefly changes color — brightens to gold at the "beside" state (the true frequency, the correct one), dims slightly pink at the receding state (frequency dropping). The viewer reads the kid's experience from the color. No label needed. The kid IS the observer in the physics scenario. Why three separate function graphs appear at the bottom at the end (ghost_high, ghost_true, ghost_low): After three separate demonstrations, the scene needs one moment that proves the point: same source, three frequencies. Three sine waves simultaneously visible — different frequencies, different colors — is that proof. The tight spacing at the bottom makes them visually comparable. The viewer sees compression and stretching in the waveform itself, not just in the rings. Why the scene stays on one background image throughout: Unlike the Rome scenes which cut between images, Doppler keeps one dark background with the grid, road, and physics apparatus constant. This is correct for this argument — cutting to a new image would reset the spatial context. The viewer needs to maintain their mental model of the scene (kid at center, car approaching from left, receding right) throughout all three states. *(Full Doppler VMD code as provided in the exemplar file — every design decision follows from the reasoning above. The code is proof of the reasoning.)* --- SCENE 3 — CAESAR (Cinematic Documentary / Fourth Wall Moment) The argument: Every creator has a story to tell. VMD gives them the means to tell it. The directorial question: How do you make a product argument feel like a reckoning rather than a sales pitch? The answer: Displacement. For the first 9 seconds the scene is about Caesar — historical, monumental, other. The viewer is in documentary mode, watching history. Then at t=9.36s a shockwave hits the screen and the text says "Now you can." The displacement worked: the viewer spent 9 seconds building up the weight of "every creator has a story to tell" through the lens of history's greatest storyteller, and then that weight lands on them personally. Why iris birth from a crimson seed point: The iris birth says "something important is being revealed." The seed color matches the crimson overlay — they are the same event, the same argument. Crimson means power, blood, historical force. The image of Caesar is not decorating the scene — it IS the argument that the power to tell stories has always existed and now belongs to the viewer. Why the aura rings draw at t=6.0s: Not for decoration. They appear around Caesar during the drama text — "Every creator / has a story to tell." The rings around the subject amplify the sense that this person carries something, that what they had was significant. When the fourth wall break happens at t=9.36s, the rings are still there — the viewer and Caesar momentarily share the same aura. Why the fourth wall shockwave uses white then crimson: The white shockwave is the break itself — sudden, unavoidable, clean. The crimson shockwave that follows is the emotional register of the break — it lands in the scene's established color language. It says: what just happened belongs to the same world as Caesar's power. The viewer has just been given something historic. Why "Now you can." is fontSize:54 at full opacity 0.99 with focusPull 0.6s: Every other text in the scene is deferential — measured, historical, documentary-toned. "Now you can." is none of those things. It is the shortest line, the largest, the most abrupt. The focusPull is 0.6s instead of the usual 1.6–2.2s because this line should not build slowly — it should arrive like a door being opened. The speed is intentional rudeness, the best kind. Why there is no cinematic amber word in this scene: The scene IS the cinematic moment. The fourth wall break at t=9.36s is more powerful than any amber word could be. Adding an amber word after "Now you can." would dilute the pivot. The scene ends in the energy of that moment, not in a summary. *(Full Caesar VMD code as provided in the Rome exemplar file — every design decision follows from the reasoning above.)* --- SCENE 4 — TESLA (Motion Deck / Presentation) The argument: VMD is a precision tool. Write your cinema using .vmd. The directorial question: How do you make a product introduction feel like a precision system booting rather than a slide appearing? The answer: Scanline boot. The image doesn't fade in — it is scanned into existence by a beam of light, like a machine initializing. This says: what you are about to see was built, engineered, loaded. The grayscale-to-color transition after the scan says: it is now live. Then the precision diagram assembles piece by piece — not appearing, assembling. Every element of this scene says the same thing: precision, engineering, deliberate construction. Why scanline over iris birth or blur-to-sharp: Tesla is a scene about a product that is technical and precise. Iris birth is for history and revelation. Blur-to-sharp is for documentary establishing shots. Scanline is for technical initialization. The initialization choice is not aesthetic — it is the first argument the scene makes. Why the image goes grayscale first then color: The scan sweeps a grayscale image into existence. The color transition happens after the scan completes. This is not a filter — it is a narrative: the system booted in black and white (schema, structure, precision) then went live in full color (active, working). The sequence grayscale → color encodes initialization → operation. Why the split panel rises at t=2.73s: The panel rises during the settlement window, after the image has established. It carves out territory for the text before the text arrives. The text never fights the image — it lives in protected space that was prepared for it. The dark panel is not decoration — it is the reason the text is readable against any photographic background without washing the image out. Why the diagram (icons + connecting lines) assembles in a specific order: Problem icon → line → Constraint icon → line → Solution icon. This is not left-to-right animation preference. This is the argument of the diagram: problems create constraints, constraints produce solutions. The order IS the logic. Reversing it would produce a meaningless sequence of icons. The lines draw themselves between icons — each connection forms as the cause produces the effect, 0.45s after the preceding icon appears. Why "VIDEOMARKDOWN" lands at fontSize:34 then shrinks to fontSize:14 whisper: The cinematic word arrives as the peak of the scene — all assembly complete, all text set, the diagram proven. It names what was just demonstrated. Then it shrinks — not to disappear, but to become a ghost at the panel edge, proof that the demonstration happened, before the scene transitions. The shrink IS the scene breathing out. Why world icons are cog, CPU, circuit-chip: This is a technical world. The ambient icons at the canvas edges establish that before anything else appears. The viewer lands in a world of engineering. The drama layer's precision makes sense in this context — it belongs to this world. concept: "Rome" emoji: "🏛️" total_duration: 10 background: "#000000" subtitles: - "" phases: scene_tesla: duration: 10 description: "Tesla scanline boot, grayscale to color, precision diagram assembles, VIDEOMARKDOWN cinematic" elements: world_cog_a: type: primitive # World icons establish: this is a technical, engineering world. Arrive from frame zero. # Rotating cog = mechanical precision = the register of everything that follows. shape: icon[icon:"mdi:cog" size:1.1 color:#4fc3f7/0.08 at:(-6.5,3.5)] -> fadeIn(0.61s, 0s) -> rotate(360deg, 9.1s, linear, 0s) world_cog_b: type: primitive shape: icon[icon:"mdi:cog" size:0.8 color:#4fc3f7/0.06 at:(6.5,-3.5)] -> fadeIn(0.61s, 0.3s) -> rotate(-360deg, 7.58s, linear, 0s) world_cpu_a: type: primitive shape: icon[icon:"lucide:cpu" size:0.9 color:#ffffff/0.06 at:(-6.5,-3.0)] -> fadeIn(0.61s, 0.15s) world_cpu_b: type: primitive shape: icon[icon:"lucide:cpu" size:0.7 color:#ffffff/0.05 at:(6.2,3.8)] -> fadeIn(0.61s, 0.45s) world_circuit: type: primitive shape: icon[icon:"mdi:integrated-circuit-chip" size:1.2 color:#4fc3f7/0.07 at:(0.0,5.2)] -> fadeIn(0.76s, 0s) scanline_sweep: type: morph # The beam IS the initialization. Sweeps top to bottom at linear speed — mechanical, not organic. # Linear easing mandatory: a machine scans at constant rate, not ease.out. shape: rectangle[w:16 h:0.08 fill:#4fc3f7/0.55 stroke:#4fc3f7/0.8 strokeWidth:0 at:(0,6.0)] ~> rectangle[w:16 h:0.08 fill:#4fc3f7/0.55 stroke:#4fc3f7/0.8 strokeWidth:0 at:(0,-6.5)] (1.67s, linear, 0s) scanline_glow: type: morph # Warmth rect: 10× height of beam, same color at /0.08. Creates halo around scan line — the machine warming up. shape: rectangle[w:16 h:0.8 fill:#4fc3f7/0.08 at:(0,6.0)] ~> rectangle[w:16 h:0.8 fill:#4fc3f7/0.08 at:(0,-6.5)] (1.67s, linear, 0s) tesla_image: type: morph # Image starts opacity:0.0 grayscale:1.0 — not yet live. # Rises to visible grayscale during scan (schema, structure). # Then grayscale:0 after scan (now live, in color, operational). shape: image[src:"images/icons/Tesla.jpg" w:7.0 h:10.0 fit:cover radius:0.0 grayscale:1 opacity:0.0 at:(-3.5,0.0)] ~> image[src:"images/icons/Tesla.jpg" w:7.0 h:10.0 fit:cover radius:0.0 grayscale:1 opacity:0.95 at:(-3.5,0.0)] (1.67s, linear, 0s) ~> image[src:"images/icons/Tesla.jpg" w:7.0 h:10.0 fit:cover radius:0.0 grayscale:0 opacity:0.95 at:(-3.5,0.0)] (0.91s, ease.out, 1.97s) tesla_edge_line: type: morph # Vertical line divides image territory from text territory. Rises after image is live (t=2.73s). # This is the edge of the argument's domain. The split panel will rise behind the text side. shape: line[from:(0.0,5.5) to:(0.0,-5.5) stroke:#4fc3f7/0.0 strokeWidth:1.5] ~> line[from:(0.0,5.5) to:(0.0,-5.5) stroke:#4fc3f7/0.55 strokeWidth:1.5] (0.76s, ease.out, 2.73s) tesla_truth: type: primitive # Primary claim. fadeIn used here because in motion deck mode the claim is punchy, direct. # "Write your Cinema." — directive, not explanatory. Active verb. Short. shape: text[text:"Write your Cinema." fontSize:28 fill:#ffffff/0.90 fontWeight:bold at:(4.2,1.8)] -> fadeIn(0.76s, 3.03s) tesla_truth_two: type: primitive # Secondary claim 0.6s after primary — same beat, causally linked. # Cyan color = alive, active — this is what the tool does. shape: text[text:"using .vmd" fontSize:22 fill:#4fc3f7/0.85 at:(4.2,1.1)] -> fadeIn(0.76s, 3.64s) diag_icon_problem: type: morph # Diagram assembles: Problem → Constraint → Solution. This order IS the argument. # Icons as characters: mdi:head-question = the problem state. Pink = something not yet resolved. shape: icon[icon:"mdi:head-question" size:1.0 color:#ff6b9d/0.0 at:(2.2,-1.1)] ~> icon[icon:"mdi:head-question" size:1.0 color:#ff6b9d/0.95 at:(2.2,-1.1)] (0.53s, ease.spring, 4.25s) diag_label_a: type: primitive shape: text[text:"PROBLEM" fontSize:10 fill:#ff6b9d/0.60 at:(2.2,-1.85)] -> fadeIn(0.45s, 4.55s) diag_line_ab: type: morph # Line draws itself between Problem and Constraint — the connection forms because the cause demands the effect. # 0.45s after diag_icon_problem appears. Causality: problem exists → constraint emerges. shape: line[from:(2.72,-1.1) to:(2.72,-1.1) stroke:#4fc3f7/0.35 strokeWidth:1.2] ~> line[from:(2.72,-1.1) to:(3.68,-1.1) stroke:#4fc3f7/0.35 strokeWidth:1.2] (0.45s, ease.out, 4.78s) diag_icon_constraint: type: morph # Constraint icon: mdi:link-variant = connected, bound, limited. Cyan = active system element. # Appears 0.3s after line tip arrives at its position. shape: icon[icon:"mdi:link-variant" size:1.0 color:#4fc3f7/0.0 at:(4.2,-1.1)] ~> icon[icon:"mdi:link-variant" size:1.0 color:#4fc3f7/0.95 at:(4.2,-1.1)] (0.53s, ease.spring, 4.85s) diag_label_b: type: primitive shape: text[text:"CONSTRAINT" fontSize:10 fill:#4fc3f7/0.60 at:(4.2,-1.85)] -> fadeIn(0.45s, 5.16s) diag_line_bc: type: morph shape: line[from:(4.72,-1.1) to:(4.72,-1.1) stroke:#6bffb8/0.35 strokeWidth:1.2] ~> line[from:(4.72,-1.1) to:(5.68,-1.1) stroke:#6bffb8/0.35 strokeWidth:1.2] (0.45s, ease.out, 5.38s) diag_icon_solution: type: morph # Solution icon: mdi:lightbulb-on = insight, resolution. Mint = correct, complete, resolved. # The color progression across the diagram: pink → cyan → mint = problem → process → solution. shape: icon[icon:"mdi:lightbulb-on" size:1.0 color:#6bffb8/0.0 at:(6.2,-1.1)] ~> icon[icon:"mdi:lightbulb-on" size:1.0 color:#6bffb8/0.95 at:(6.2,-1.1)] (0.53s, ease.spring, 5.46s) diag_label_c: type: primitive shape: text[text:"SOLUTION" fontSize:10 fill:#6bffb8/0.60 at:(6.2,-1.85)] -> fadeIn(0.45s, 5.76s) tesla_cinematic: type: morph # Cinematic word: amber. Appears nowhere before this moment. # "VIDEOMARKDOWN" names the tool that just demonstrated itself through the diagram. # Shrinks to whisper — the scene breathes out, ready to transition. shape: text[text:"VIDEOMARKDOWN" fontSize:34 fill:#ffd54f/0.0 fontWeight:bold at:(4.2,0.0)] ~> text[text:"VIDEOMARKDOWN" fontSize:34 fill:#ffd54f/0.95 fontWeight:bold at:(4.2,0.0)] (0.61s, ease.out, 6.37s) ~> text[text:"VIDEOMARKDOWN" fontSize:14 fill:#ffd54f/0.28 fontWeight:bold at:(4.2,0.0)] (0.91s, ease.inOut, 7.89s) tesla_dim_veil: type: morph # Veil dims scene at cinematic moment, then lifts. The universe contracts to the word, then reopens. shape: rectangle[w:16 h:12 fill:#000000/0.0 at:(0,0)] ~> rectangle[w:16 h:12 fill:#000000/0.0 at:(0,0)] (6.37s, linear, 0s) ~> rectangle[w:16 h:12 fill:#000000/0.28 at:(0,0)] (0.45s, ease.out, 6.37s) ~> rectangle[w:16 h:12 fill:#000000/0.0 at:(0,0)] (0.76s, ease.out, 7.89s) --- PART 5 — THE SELF-CHECK After generating, before outputting, answer four questions. Not a checklist. Four questions. 1. Does something actually happen? Watch the scene in your head from t=0 to end. Is there a moment where the viewer feels the concept — not understands it, feels it? Is there a beat that would make someone watching for the first time lean forward? If the scene is just shapes appearing on a schedule, it is a slideshow. Rewrite it. 2. Does every element exist because the story needed it? Go through the element list. For each one: why is this here? If the answer is "it looks good" or "it adds visual interest" — cut it. If the answer is "because at t=7.2s the circuit closes and 0.3s later the current flows and this electron IS that current" — keep it. Nothing earns its place by looking good. Everything earns its place by serving the argument. 3. Could someone watch this with no sound and understand the argument? If the visual layer requires the labels to be readable to make sense, the animation has failed. Color, motion, causality — these must carry the argument. Text confirms. Text never explains. 4. Do the elements change state as the argument changes? Structure appearing is not argument. Nodes appearing is not a network effect. Lines drawing is not complexity. The argument lives in state changes — things that were one way becoming another way because something happened. Electrons speeding up. Resistors heating. Networks brightening. Value counters unable to keep pace. If your elements appear and then stay static, the argument is not on screen. Find the state change that IS the argument and encode it in opacity, scale, color, duration, or glow. If nothing changes state after appearing — rewrite. If any answer is no — not adjust, rewrite. The standard is a YouTube documentary. Anything less is a draft. --- RENDERER LAWS — NON-NEGOTIABLE - shape: always one line. Any linebreak breaks the parser with no error message. - total_duration must equal exact sum of all phase durations. Any discrepancy = silent wrong output. - subtitles: - "" must be present always. - functionGraph always type: primitive. Never morph. - Seed visible at t=0. Delay controls when animation begins, not when element appears. - Stroke-only circles (rings, auras, pulses): omit fill entirely. Triple-zero seed: r:0.0 stroke:#hex/0.0 strokeWidth:0.0. Writing fill:#hex/0.0 breaks interpolation silently. Writing r:0.3 with invisible stroke/opacity still renders an artifact dot. - Fill+stroke circles (electrons, particles): fill:#hex/0.0 seed is correct. Fill is expected. - Delays are absolute from phase start. Not relative to previous step. - Amber #ffd54f appears nowhere before the cinematic moment. Once broken, cannot be recovered. - NO COMMENTS IN VMD CODE. ZERO. The parser has no comment syntax. A # inside any element block breaks or corrupts output silently. Do not write a single comment anywhere in the generated .vmd file. Not one.