Language Reference

VMD Primitive DSL

The complete reference for Video Markdown — every primitive, command, and pattern. Write your first scene in minutes.

Video Markdown (VMD) — Created by Sohan Dananjaya


What is VMD?

Video Markdown is a language and an animated document type. You write it the way you think — describe what you want to see, when it appears, how it moves — and it renders into a fully animated, frame- accurate presentation. The file is the animation. One plain text file is a complete, living, moving document.

What VMD gives you:

Speed from idea to animation. There is no setup, no asset pipeline, no tool to learn for months. You write what you imagine and it exists. A concept that would take weeks in a production pipeline can be a finished animated scene by the end of the afternoon. VMD is built for the gap between an idea and making others see it.

Full control, always yours. Every frame, every timing, every color, every motion is declared by you in the file. Nothing is generated for you without your knowledge. You can adjust anything in seconds — change a duration, shift a color, add a physics event — because the entire animation is readable text you wrote.

Version controlled by nature. A VMD file is a text file. git diff works on it. Branching works. Collaboration works. The full history of every change you made to an animation lives in your repository exactly as it does for code.

Renders anywhere, no GPU required. The same .vmd file produces identical output on a laptop, a server, a CI pipeline, a Docker container, a cloud deploy. No render farm. No locked environment. The renderer is lightweight and runs wherever you run it.

The full range in one language. A clean labeled diagram. A 3D spinning wormhole with a parametric surface formula. A multi-phase cinematic with atmospheric color overlays, scanline arrivals, shockwave rings, drifting particles, and a data packet flowing through an architecture diagram. An avatar character walking across the scene while talking and smiling. All of it is VMD. All of it is the same syntax. You do not switch tools, learn a new system, or change how you think — you just write more.

Declarative by design. VMD is not procedural. You do not write a render loop, construct objects, or call drawing functions. You declare what exists and what it does — and it exists and does it. A sphere that spins is sphere[r:1 spin3d:(0,30,0)]. A wormhole is one surface3d line with a formula. A character walking across the scene is one gesture("walk") chain. The language is as close to thought as code gets.

Unlimited visual effects potential. Layered color overlays that shift atmosphere mid-scene. Particle systems that rise, drift, orbit, or bloom. Shockwave rings that expand from an event. Aura pulses around a focal element. Ken Burns motion on a full-bleed image. Cinematic veil and crown. Parametric rings drawing themselves outward. Color floods that flash and retreat. Every effect composes freely with every other — stack as many layers as the scene demands. There is no ceiling on visual complexity, and each layer is one element in the file.

AI-native. VMD is text with a logical, learnable grammar. An AI can read it, write it, modify it, and generate complete advanced scenes from a description — more accurately and controllably than any video generation model, because VMD is a language with rules, not a probabilistic output. You stay in control; the AI writes what you direct.

Unlimited visual potential. 11,000+ pre-drawn animated objects(icon libraries), full parametric math surfaces, a cinematic layering system, physics effects, 3D illustrations, avatar characters with gestures and expressions, charts, diagrams, image cinematics — all composable in a single file. If you can describe an effect, VMD has a path to make it exist.

VMD is the right tool when the work is educational, presentational, or visual — and when the answer to "how long will this take?" should be hours, not months.


Part 0 — How to Think in VMD

0.1 The Mental Shift

Let me start with a question that sounds too simple: what is an animation, really? Strip everything away and it's just a set of statements — this thing exists here, it moves there, it takes this long. That's all. And if that's all it is, then here's an interesting thought: why shouldn't you be able to just... write those statements down, and have them play? VMD is what happens when you take that thought seriously.

You want a circle that fades in at half a second, drifts over to the right, and turns into a square. So you write that down. And that's what renders. There's no timeline panel because the timing is written in the sentence. There's no canvas to click on because the positions are written in the sentence. The file isn't a project that points at an animation somewhere — the file IS the animation, all of it, every frame, sitting right there in plain text.

Now, why does this matter? Because of distance. In every other tool, there's a long road between the picture in your head and the picture on the screen — menus, panels, clicks, adjustments. In VMD the road is one sentence long. You think it, you write it, it exists. Once you feel that, everything else in this manual is just learning more words.


0.2 The Only Three Things

Here's a secret that will save you a lot of trouble: this whole language is three ideas. Three! Everything else is vocabulary, and vocabulary you can look up whenever you need it. But these three you have to actually understand.

First: phases. A film is chapters of time, played one after another. Each chapter — each phase — has a duration. Add up all the durations and you must get exactly total_duration. That's the one hard rule in the whole language, and it's not bureaucracy — it's bookkeeping. Time has to add up. Inside each phase, the clock starts over at zero. Simple.

Second: elements. The things inside a phase. A circle, a word, an icon, a sphere, a little cartoon character. Each one has a name and one line describing what it is.

Third: animations. What the things do. And here's the beautiful part — what a thing does is written right on the same line as what it is, chained together with arrows:

title:
type: primitive
shape: text[text:"Energy" fontSize:40 fill:#4fc3f7/0.0 at:(0,1)]
-> fadeIn(0.8s, 0.5s)
-> moveTo((0,2.5), 1.2s, ease.out, 1.5s)

Read it out loud: a piece of text, invisible to start, fades in over 0.8 seconds beginning at half a second, then floats upward starting at a second and a half. You just read an animation. Not a description of one — the actual animation, the thing itself. Everything about this element is on these three lines: what it is, where it starts, what it does, and when.

That's the whole system. Honestly. The rest is details.


0.3 Reading Space

The canvas is 16 units wide and 12 units tall, and the middle is (0, 0). Y points up — the way it does in mathematics, and the way it should.

So the top-left corner is (-8, 6) and the bottom-right is (8, -6), and after a day of this you'll know a handful of addresses by heart the way you know your own street:

  • (0, 5) — up in the top bar, where scene labels live
  • (0, 0) — dead center, where the important thing goes
  • (0, -3.2) — down low, where the quiet captions whisper
  • x: ±9 or ±10 — and now, here's the interesting one: off the canvas entirely

Why would you put something outside the picture? Because a stage has wings! An actor doesn't materialize in the middle of the stage — she walks on, and the walking-on tells you something. Spawn a circle at x:-10, send it to x:0, and it enters, from the left, like it came from somewhere. Suddenly your little 16-by-12 rectangle implies a whole world beyond its edges.

So don't think of the canvas as a piece of paper. Think of it as the lit part of a stage. Your job is deciding what's in the light, what's waiting in the dark, and where the audience's eye goes next.


0.4 The Animation Chain

Now I have to warn you about something, because everybody — everybody — learns this wrong the first time, and then has one confusing afternoon, and then never gets it wrong again. Let's skip you past the confusing afternoon.

Animations chain with ->, and every delay is measured from the start of the phase. Not from when the previous step finished. From zero. Always from zero.

shape: circle[r:1 fill:#4fc3f7/0.0 at:(0,0)]
-> fadeIn(0.6s, 0s)
-> moveTo((3,0), 1.5s, ease.out, 1s)
-> fadeOut(0.5s, 3s)

The moveTo starts at t=1. Not "one second after the fade finishes." Not "one second after the fade starts, sort of." At t=1 on the phase clock, period. The fadeOut fires at t=3 on that same clock.

And once you stop fighting it, you realize this is the right rule. Every number in the file is an absolute time. You can look at any delay, anywhere, and know exactly when it happens — without adding up a chain of durations in your head like some poor accountant. The schedule reads like a train timetable, not like a recipe where step four depends on how long step three took.

One more thing, and this one bites everyone too: every element is visible at t=0 unless you say otherwise. The renderer doesn't know you were planning a dramatic entrance. If you write a fadeIn but the element starts at full opacity, it just sits there, fully visible, and then rudely fades in again. So seed things invisible:

fill:#hex/0.0 → filled shapes start transparent
r:0.0 → circles can start at zero size

And for stroke-only shapes — rings, outlines, auras — the rule is stricter and a little funny: give them no fill property at all. Not fill:#hex/0.0. Nothing. An invisible fill on a stroke-only shape quietly breaks the morphing machinery, and it breaks it silently, which is the worst way for anything to break.


0.5 Morph — The Universal Transformer

If VMD has one genuinely powerful idea, it's this one, so let's take our time.

Morph says: here is a thing in state A. Here is the same thing in state B. Become B.

type: morph
shape: circle[r:0.5 fill:#4fc3f7/0.0 at:(0,0)]
~> circle[r:2.5 fill:#4fc3f7/0.90 at:(0,0)] (1.2s, ease.out, 0.5s)
~> circle[r:1.0 fill:#c77aff/0.70 at:(3,1)] (0.8s, ease.spring, 2s)

Notice the operator is ~>, and I want you to read it as "become," not "animate to." That's not poetry — it's precision. Between state A and state B, the renderer interpolates every property at once: size, color, opacity, position, all of them sliding together, every frame computed for you. You supply the destinations. The language supplies the journey.

And here's what makes it universal: anything numeric can morph. Position, radius, color, opacity, stroke width, font size, blur, tint, crop. Even the shape's identity — a circle can become a square can become a star, smoothly, through all the in-between shapes you'd never bother to draw. Morph a function graph and the curve changes shape. Morph an image and the photograph blurs or tints or crops itself. Morph a 3D surface and the geometry warps from one mathematical object into another.

Multi-step morphs are just more ~> steps, and — same rule as before — every step's delay is on the phase clock, absolute from zero.

There's one idiom you'll use constantly, the hold step, and it looks silly the first time you see it:

A[invisible] ~> A[invisible] (2s, linear, 0s) ~> B[visible] (0.8s, ease.out, 2s)

Become... yourself? For two seconds? Yes! It's a way of waiting. The element holds its state until t=2, and then transforms. You've written silence into the music. Remember this trick — it's how you control when things happen without changing what they are.


0.6 Easing — The Feeling of Motion

Here's an experiment. Take the same object, same start point, same end point, same duration — and change only the easing curve. You get completely different meanings. Not different prettiness. Different meanings.

EasingWhat it says
ease.outConfidence. The thing arrives and settles where it belongs.
ease.inInevitability. The thing gathers itself and leaves — it knew it was leaving.
ease.inOutOrganic travel. No hurry. A ship crossing water.
linearMachinery. Constant rate because physics says so. Data, orbits, time itself.
ease.springAlive! Something reacted. It overshot a little because it cares.
ease.elasticSomething's wrong. The system overshoots and wobbles — unstable.
bounceContact. It hit the floor and the floor pushed back.

Why does this work? Because your eye is a physicist. It's been watching real objects fall and settle and collide for your entire life, and it knows — without you knowing it knows — that heavy things don't stop suddenly, that machines don't hesitate, that living things overshoot. When motion on screen obeys those rules, you believe it. When it doesn't, you feel that something is fake, even if you can't say what.

So don't pick easing by what looks nice. Ask what the motion means, and pick the curve that means it. A data packet is linear — it's machinery. A character getting surprising news is ease.spring — it's alive. A planet settling into orbit is ease.inOut — it's enormous and has nowhere urgent to be. The easing is grammar, not decoration.


0.7 Layering — How Scenes Are Built

A scene is a stack of transparent sheets, painted from the back forward. In VMD the rule is almost embarrassingly simple: elements render in file order. Whatever you write later paints on top of whatever you wrote earlier. That's the whole z-axis.

The stack, back to front:

Background color → Image → Atmosphere wash → Panels → Frame/HUD →
Ghost text → World icons → Ambient motion → Drama content →
Active events → Cinematic crown

The deep layers establish a world — the photograph, the colored atmosphere over it. The middle layers carve out a stage — dark panels where text can live safely. And the top layers make the argument. When a scene looks composed instead of assembled, this stack is why. When it looks like clip-art floating on a background, somebody skipped the middle.

And now the most important timing rule in the language — I'd put it in a frame on the wall. The causality rule. When something happens at time T — a shockwave expands, a packet arrives, a ring pulses — the text that talks about it appears at T plus 0.3 seconds. Always after. Never before, never simultaneous.

Why 0.3? Because that's roughly how long a human eye takes to catch an event and be ready to read about it. The event proves the claim; the text confirms what the viewer just saw with their own eyes. Run it in that order and the scene feels true. Run it backwards — announce first, show second — and the scene feels like a salesman. The viewer won't know why they don't believe you. But they won't.


0.8 Effects from Primitives

Now here's something that sounds like a weakness and is actually the best part. VMD has no particle system. No shockwave button. No effects menu at all.

What it has is: morph, opacity, position, size, and staggered timing. And it turns out that's like saying a piano only has 88 keys.

Watch. A shockwave: a stroke-only circle that grows from r:0.3 to r:8.0 while its stroke fades to nothing. That's it — one morph. Now make three of them, staggered 0.4 seconds apart, and you have the unmistakable signature of an energy release. You've seen it in a hundred films. It's two lines.

Particles: tiny circles, r:0.04 to 0.08, each one rising from below the frame to above it over a couple of seconds, fading as it goes. Make eight, scatter their positions and delays so no two match — a field of drifting lights. There is no particle system. There are eight small circles and your good taste.

A data packet: one small bright circle moving linear from node A to node B. Want a trail? A line whose endpoint morphs along behind it at the same speed.

A flash of atmosphere: one full-canvas rectangle, morphing transparent → 22% blue → transparent, in under a second.

You see the pattern. Decide what change you want the eye to see. Find which properties produce that change. Write the two states. Done. You're not choosing from somebody's effects menu — you're composing from principles, and principles combine without limit. Nobody can list all the effects VMD has, for the same reason nobody can list all the sentences English has.


0.9 3D Thinking

For 3D, a phase stops being a flat canvas and becomes a real perspective scene. X to the right, Y up, and negative Z going into the picture, away from you.

Two things must exist in every 3D phase, no exceptions: a camera and a light. And this isn't the renderer being fussy — it's geometry being honest. A 3D world without a camera is a world seen from nowhere. A world without light is a world you can't see at all. The 2D canvas quietly gave you both for free; 3D makes you say where you're standing and where the sun is.

The camera: at is where you stand, lookAt is what you face — almost always (0,0,0) — and fov is how wide your vision is. Here's a lovely fact from photography: a narrow fov around 40 compresses the scene and makes it dramatic, like a telephoto lens; a wide fov around 70 stretches the space open. Same scene, different lens, different feeling. And orbit:(0,40,0) makes the camera circle the scene at 40 degrees per second, all by itself — the world's cheapest camera crew.

The light: its direction is the way the light travels — so (-0.4,-0.7,0.4) streams downward, meaning the source is up high, like a sun. intensity is how strong the direct light is; ambient is the floor under the shadows — how dark the darkest corner gets. Low ambient is film noir. High ambient is a well-lit classroom. Choose accordingly.

emissive:true makes a shape glow with its own fill color, ignoring the light entirely — stars, energy cores, neon. Things that emit instead of receive.

Two traps, both silent, both worth knowing in advance: in 3D, opacity is its own property — opacity:0.5 — and the slash syntax fill:#hex/0.5 does nothing, quietly. And fadeIn doesn't exist in 3D; you fade with a morph.

And then — parametric surfaces, which are my favorite thing in the language. surface3d takes three formulas, one per coordinate, swept over two parameters u and v. The formula IS the shape. Write the torus equation, get a torus. Write the wormhole equation, get a wormhole. A Möbius strip is one line of text. And because formulas are just properties, you can morph between two surfaces and watch one mathematical object melt into another. If you remember any single fact about VMD's 3D, remember this one: any smooth surface you can describe with mathematics, you can have, in one line.

(Its little sibling surface plots a height field, z = f(x,y), over a flat grid — mathematical terrain. It lies flat in the XZ plane, so stand the camera low and diagonal, or you'll be staring at your beautiful landscape edge-on.)


0.10 The VMD Creative Approach

When you actually sit down to make something, here's the order of operations. Not rules — just the way that works.

  1. See it first. Before you type anything, close your eyes and look at the final frame. Know which things move and which hold still. Know when the big moment lands. If you can't see it yet, you're not ready to write it — and that's fine, seeing is most of the work.

  2. Build back to front. Background first, foreground last, because file order is paint order. Set the stage before you send on the actors.

  3. Seed everything invisible. Anything that shouldn't exist at t=0 starts at zero opacity or zero size. Nothing appears uninvited. Every entrance is a decision you made.

  4. Obey causality. Show the event; confirm it in words 0.3 seconds later. The eye first, then the mind. Proof, then caption. Every single time.

  5. Choose easing by meaning. Ask what the motion is saying, then pick the curve that says it. The eye is a physicist; don't lie to it.

  6. End with the crown. Every scene builds toward one moment, and one color — #ffd54f — exists only for that moment. Spend it early and it's worthless. Save it, and the whole scene has been secretly pointing at it all along.

That's the grammar. Everything after this page is vocabulary — shapes, properties, commands, the words of the language. Vocabulary is easy; you look it up when you need it. But grammar is how you think, and now you have it. The rest will click into place like it was always obvious — which, you'll notice, is the feeling VMD is designed around.


Part I — The Language

1. The Mental Model

Three concepts. Understand these and everything else is detail.

Phases are named time chapters. A VMD file is a sequence of phases that play back to back. Each phase has a duration. The sum of all phase durations must equal total_duration exactly.

Elements are objects inside a phase — shapes, text, icons, images, avatars, graphs. Each element has a name, a type, and a shape line that describes what it is and what it does.

Animations are what elements do over time. They are written as a chain directly on the shape line, using ->. There is no separate timeline — the timing lives right on the element.

phases:
intro:
duration: 6
elements:
title:
type: primitive
shape: text[text:"Hello" fontSize:40 fill:#ffffff at:(0,1)] -> fadeIn(0.8s, 0.5s)

That chain reads: "a text element that fades in over 0.8 seconds, starting at 0.5 seconds into the phase." The entire animation for that element is on one line.


2. File Anatomy

Every VMD file follows this structure:

concept: "Title of this animation"
total_duration: 12
background: "#0f0f0f"
avatars:
hero: figure[name:"hero"]{...}
phases:
phase_name:
duration: 6
description: "What happens here."
elements:
element_name:
type: primitive
shape: shape[...] -> command(...)

Rules that must never be broken:

  • total_duration must equal the exact sum of all phase durations. A discrepancy is a hard error — the file will not render.
  • avatars: comes before phases: if used.
  • The shape: line is always one line. Any line break breaks the parser silently.
  • type: is primitive for shapes and graphs, morph for morphing elements, figure for avatar characters.

3. The Canvas

The 2D canvas is 16 units wide and 12 units tall. Center is (0, 0). Y is up.

(-8, 6) ────────────────── (8, 6) ← top edge
│ │
│ (0, 0) │ ← center
│ │
(-8,-6) ────────────────── (8,-6) ← bottom edge

Named positions you will use constantly:

LocationCoordinates
Canvas center(0, 0)
Top bar(0, 5.0)
Bottom bar(0, -4.6)
Whisper / caption(0, -3.2)
Off-canvas spawnx: ±9.0–10.0

3D scenes have their own world space — covered in Part IX.


4. The Property Syntax

Every shape in VMD uses bracket notation:

shape[property:value property:value ...]
  • No commas between properties
  • Numbers have no quotes: fontSize:36, r:0.5
  • Colors use hex: fill:#4fc3f7
  • Opacity is written with / after the hex: fill:#4fc3f7/0.8
  • Positions and vectors use parentheses: at:(0, 1.5), uRange:(0, 6.28)
  • Strings use double quotes: text:"Hello world"

Part II — 2D Shapes

5. Primitives

circle[r:N fill:#hex/op stroke:#hex/op strokeWidth:N arc:N arcStart:N opacity:N at:(x,y)]
ellipse[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]
rectangle[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]
square[w:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]
line[from:(x,y) to:(x,y) stroke:#hex/op strokeWidth:N at:(x,y)]
triangle[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]
star[points:N outerRadius:N innerRadius:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]
regularPolygon[sides:N r:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]

All shapes share: fill, stroke, strokeWidth, at. The at property is the shape's center.

  • circle supports arc (degrees to sweep, 0–360) and arcStart (starting angle in degrees, 0 = 12 o'clock, increases clockwise). A full circle is the default. A partial circle draws a filled wedge — the foundation of pie and donut charts. arc:144 arcStart:0 draws a 40% wedge starting at the top. Both arc and arcStart are fully morphable. In 3D, torus has the same arc property for partial torus segments.
  • triangle takes w (width) and h (height). Use w:N h:N for explicit dimensions.
  • square is a uniform rectangle — use w for both dimensions. rectangle is used when width and height differ.
  • star takes points (number of points), outerRadius (tip to center), and innerRadius (inner notch to center). A 5-point star: star[points:5 outerRadius:1.2 innerRadius:0.5].
  • regularPolygon draws any n-sided polygon by sides and circumradius r. sides:3 is a triangle, sides:6 is a hexagon.

All of these morph freely — morph a circle to a square to a star and the shape transitions smoothly through each intermediate state.

Stroke-only shapes — rings, auras, arcs, outlines — must have no fill property at all. Not fill:#hex/0.0 — no fill property. A fill:#hex/0.0 on a stroke-only shape causes morph interpolation to break silently.


6. Text

text[text:"Label" fontSize:N fill:#hex/op fontWeight:bold at:(x,y)]

Text cannot wrap automatically. At each font size there is a maximum safe character count before the text overflows the canvas:

fontSize645246403630221613
max chars81012141822304454

For multi-line text, use \n in the string: text:"Line one\nLine two". Lines are spaced at 1.2× the font size and centered vertically on the at point. \\ produces a literal backslash (needed for LaTeX-style notation).

Alternatively, use multiple text elements at different y positions for more control.


7. Function Graphs and Parametric Curves

functionGraph plots y = f(x) over an x range:

functionGraph[fn:"sin(x)" xStart:-6 xEnd:6 segments:80 stroke:#4fc3f7 strokeWidth:2 at:(0,0)]

parametric plots (x(t), y(t)) — arcs, circles, pie segments, spirals:

parametric[paramX:"2*cos(t)" paramY:"2*sin(t)" tStart:0 tEnd:6.28 segments:60 stroke:#4fc3f7 at:(0,0)]

Both can be used as type: morph. Morphing xEnd or tEnd from 0 to their final value draws the curve progressively. Morphing fn or paramX/paramY mutates the curve's shape mid-scene. Morphing stroke opacity and strokeWidth works the same as on any other shape.

curve2d plots a curve that has no f(t). Chaotic attractors like Lorenz and Rössler don't exist as a formula you can evaluate at a point in time — they only exist as the result of numerically integrating a system of derivatives, step by step, where each point depends on the one before it. functionGraph and parametric can't express that; curve2d can:

curve2d[dxFn:"expr(x,y,z,t)" dyFn:"expr" dzFn:"expr" seed:(x,y,z)
steps:8000 dt:0.01 skip:0 project:"xz"
stroke:#4fc3f7 strokeWidth:1 colorMap:"turbo" colorMode:"t"]

You write the system's derivatives (dx/dt, dy/dt, dz/dt) — not positions. The engine integrates them with fixed-step RK4 starting from seed, then draws the resulting path as a line. project picks which two of the three integrated axes become the 2D plane (default "xz"). Just like surface3d, any numeric property that isn't reserved becomes a formula variable — this is how constants like a, b, c, sigma, rho, beta get into the equations.

There's no camera to compensate with. Being a 2D primitive, curve2d can't zoom out for a large-magnitude system the way a 3D camera could. pointScale (default 1) rescales the raw integrated coordinates before they hit the canvas, and autoCenter:true recenters on the trajectory's own bounding box instead of assuming it sits near (0,0). Lorenz's z-axis spans roughly 0–50 in raw units — without both of these it renders far off-screen.

Tracing it in over time — use -> draw(duration, delay) instead of fadeIn. It reveals the path point-by-point from the seed outward, so it looks like it's being plotted rather than fading in as a solid blob:

curve2d[...] -> draw(3.0s, 0.5s)

Ready-to-use systems:

Rössler: dxFn:"-y-z" dyFn:"x+a*y" dzFn:"b+z*(x-c)" a:0.2 b:0.2 c:5.7 seed:(0.1,0,0)
Lorenz: dxFn:"sigma*(y-x)" dyFn:"x*(rho-z)-y" dzFn:"x*y-beta*z" sigma:10 rho:28 beta:2.667 seed:(1,1,1)

No morph between two different curve2d trajectories — there's no cheap way to interpolate between two independently-integrated point sets. Opacity and fade animations work normally.


8. Icons — A Pre-Built Factory of Animated Objects

Icons are not decorations. They are a library of over 11,000 pre-drawn objects — people, animals, machines, tools, buildings, scientific symbols, brand logos, UI elements, nature, space, biology, technology — ready to place, color, scale, and animate with zero design work. You do not draw a rocket, a molecule, a server, a person waving, a fire, a circuit board — you call one by name and animate it exactly like any other VMD element.

icon[icon:"mdi:fire" size:2.0 color:#4fc3f7/0.85 at:(0,0)]

Three libraries:

  • mdi: — 7,600+ filled objects. The primary library. People, animals, objects, systems, nature, science, technology. If you need a concrete thing drawn, it is almost certainly here.
  • lucide: — 1,700+ outline-style icons. Architecture diagrams, data flow, technical concepts.
  • logos: — brand logos and product marks.

The format is always "prefix:name" — both parts required. Wrong format produces a silent placeholder with no error.

Icons animate like anything else. Chain the full animation system onto any icon:

rocket:
type: primitive
shape: icon[icon:"mdi:rocket" size:3.0 color:#ffffff/0.9 at:(-6,-4)]
-> fadeIn(0.5s, 0s)
-> moveTo((0,2), 2.0s, ease.out, 0.5s)
-> rotate(360deg, 3s, linear, 2.5s)
atom:
type: morph
shape: icon[icon:"mdi:atom" size:1.2 color:#4fc3f7/0.0 at:(0,0)]
~> icon[icon:"mdi:atom" size:4.0 color:#4fc3f7/0.9 at:(0,0)] (1.0s, ease.spring, 1s)
~> icon[icon:"mdi:atom" size:3.0 color:#4fc3f7/0.5 at:(0,0)] (0.6s, ease.out, 2s)

Size, color, opacity, position all morph smoothly. The icon name itself swaps at 50% of the morph step — use this to switch which object you are showing mid-animation:

state_icon:
type: morph
shape: icon[icon:"mdi:check-circle" size:2.5 color:#6bffb8/0.9 at:(0,0)]
~> icon[icon:"mdi:alert-circle" size:2.5 color:#ff6b9d/0.9 at:(0,0)] (0.4s, ease.spring, 3s)

What this means in practice: the difference between spending hours drawing a custom asset and writing one line of VMD. A server farm, a DNA strand, a human heart, a satellite, a factory floor, a neural network node — call it, size it, color it with the semantic palette, animate it into the scene. The library is the asset pipeline.

Size guide for composition:

RoleSize
Lead subject (hero element)2.5–4.0
Supporting element1.4–1.8
Background world-building0.8–1.2
Ambient atmosphere0.5–0.9

Images:

image[src:"images/photo.jpg" w:16.5 h:12.5 fit:cover opacity:0.9 at:(0,0)]

Images support visual effects, all of which are morphable: blur (0–5), grayscale (0–1), tint:#hex, radius (rounded corners), crop:(x,y,w,h), opacity.

Full-bleed: w:16.5 h:12.5 at:(0,0). Portrait inset: w:7.5 h:11.0 at:(-3,0).


9. Groups

group{at:(2, 1)} [ circle[r:0.5 fill:#4fc3f7 at:(0,0)], text[text:"Node" fontSize:13 at:(0,-1)] ]

Groups offset all children relative to the group's at position. The entire group expression must be on one line.


Part III — Styling

10. The Color System

Colors are hex values. Opacity is written inline with /:

fill:#4fc3f7 → solid
fill:#4fc3f7/0.660% opacity
stroke:#ffffff/0.3535% opacity stroke

Semantic palette — these colors carry meaning. Use them for what they mean:

ColorHexMeaning
Active / alive#4fc3f7something working, flowing, on
Broken / failed#ff6b9derror, stress, overload
Resolved / correct#6bffb8success, completion, answer
Deep system / AI#c77affunknown, deep, hidden layer
Neutral structure#fffffflabels, scaffolding
Cinematic crown#ffd54fthe key moment — use once, at the end

#ffd54f must appear nowhere in a scene before the cinematic moment at the end. One early use destroys the effect completely.

Opacity by role:

RoleRange
Ambient / world texture/0.06–0.20
Background structure/0.15–0.35
Resting / seeded/0.25–0.45
Active / visible/0.55–0.85
Cinematic / hero/0.90–1.00

11. Layering

Elements render in file order — later elements paint on top of earlier ones. There is no explicit z-index. Order your elements in the phase from background to foreground.

The conceptual layer stack from back to front:

Background color → Image → Color overlay → Panels → Frame/HUD →
Ghost text → World icons → Ambient physics → Drama content →
Active physics → Cinematic crown

Part IV — Animation

12. The Animation Chain

Animations are chained with -> directly on the shape line:

shape: circle[r:1 fill:#4fc3f7 at:(0,0)] -> fadeIn(0.6s, 0s) -> moveTo((3,0), 1.5s, ease.out, 1s)

All delays are absolute from the start of the phase, not relative to the previous step. In the example above, moveTo starts at t=1s from phase start — not 1 second after fadeIn ends.

Every element is visible at t=0 unless you explicitly make it invisible. If an element has a fadeIn but no spawn gate (see Chapter 15), it appears at full opacity at t=0 and then fades in again. Always seed elements invisible before animating them in.


13. Commands

Shape commands:

CommandSignatureEffect
fadeIn(duration, delay)fades from invisible to full
fadeOut(duration, delay)fades to invisible
moveTo((x,y), duration, easing, delay, path)translates to position — path optional
rotate(angle, duration, easing, delay)360deg = full rotation
scale(factor, duration, easing, delay)uniform scale

moveTo has a fifth, optional argument: path. Without it, an object moves in a perfectly straight line — which is often wrong. A thrown ball arcs. A bouncing ball loses height each hit. A released pendulum swings through an arc. A spring settles with a wobble. path:"expr(t)" lets you write that motion directly as a formula instead of settling for a straight line every time.

t runs from 0 to 1 across the move (already eased by whatever easing you gave). The formula describes a perpendicular offset off the straight line to the destination — not a new coordinate system. moveTo still owns the actual start and end point, so the object always lands exactly on (x,y), as long as the formula itself resolves to 0 at t=1. That's the one rule to keep in mind: whatever curve you write, make sure it fades back to zero by the end.

shape: icon[icon:"mdi:rocket-launch" size:0.8 color:#4fc3f7 at:(-6,0)] -> fadeIn(0.3s, 0s) -> moveTo((6,0), 2.5s, linear, 0.3s, path:"8*t*(1-t)")

These aren't named presets — they're real equations, the same math and physics any general-purpose model or textbook already knows:

FormulaMotion
"H*t*(1-t)"A true parabola — the actual shape of a thrown object under gravity
"H*abs(sin(t*N*PI))*(1-t)"Decaying bounces — N = bounce count, height shrinks toward the destination
"H*sin(t*N*PI)*exp(-t*K)"A damped oscillation — the real spring/pendulum-with-friction equation

Only Math.* functions and the constants PI/TAU are available inside the formula — no access to other elements or scene state.

One subtlety worth knowing: the perpendicular direction depends on which way the line is actually pointing. A back-and-forth move (swing right, then swing back left) needs the formula's sign flipped on the return leg, or the bulge will land on the wrong side. See scenes/movepath_test.vmd for a working pendulum built exactly this way.

Physics commands — standalone, no easing argument:

CommandSignatureEffect
spring(stiffness:N damping:N mass:N velocity:N)spring-physics motion
bounce(stiffness:N damping:N mass:N velocity:N)bounce-physics motion
elastic(stiffness:N damping:N mass:N velocity:N)elastic-physics motion

Text commands:

CommandSignatureEffect
typewriter(duration, delay)types left to right
wordReveal(stagger, delay)each word fades in, staggered
focusPull(duration, delay)dramatic arrival with soft scale

14. Easing

EasingWhen to use
ease.outarrivals, settling, things coming to rest
ease.indepartures, collapse, inevitable motion
ease.inOuttravel across the canvas, Ken Burns, organic motion
lineardata flow, orbits, mechanical motion, hold steps
ease.springactivation pop, character reactions, UI-like snap
ease.elasticsurprise, unstable state
bouncephysical landing

15. The Morph System

Morphing transitions an element from one state to another. Use type: morph and the ~> operator:

type: morph
shape: circle[r:0.5 fill:#4fc3f7/0.0 at:(0,0)] ~> circle[r:2.0 fill:#4fc3f7/0.9 at:(0,0)] (1.2s, ease.out, 0.5s)

What morphs smoothly: at, r, w, h, fill, stroke, opacity, strokeWidth, fontSize, blur, grayscale, tint, crop.

What snaps at 50% of the step: icon name, image src, fontWeight.

Multi-step morph: chain multiple ~> steps. Each delay is absolute from phase start.

shape: A[...] ~> B[...] (1s, ease.out, 0s) ~> C[...] (0.8s, ease.out, 2.5s)

B arrives at t=1s. C starts at t=2.5s. Not 2.5s after B — 2.5s from phase start.

Hold step — stay at a state for a while before changing:

shape: A[invisible] ~> A[invisible] (2s, linear, 0s) ~> B[visible] (0.8s, ease.out, 2s)

The spawn gate — make an element invisible at t=0 so it can be animated in:

Stroke-only shapes (rings, auras):

circle[r:0.0 stroke:#4fc3f7/0.0 strokeWidth:0.0 at:(0,0)]

Filled shapes:

circle[r:0.0 fill:#4fc3f7/0.0 at:(0,0)]

The distinction matters: a fill:#hex/0.0 on a stroke-only shape breaks the morph silently. Stroke-only shapes must have no fill property at all.


Part V — Scene Architecture

16. The Phase System

Phases are the chapters of a VMD file. They play back to back. Time resets to t=0 at the start of each phase.

total_duration: 18
phases:
opening:
duration: 8
elements: ...
main:
duration: 7
elements: ...
close:
duration: 3
elements: ...

Element names are local to a phase. The same name in two phases refers to two independent elements. Split into a new phase when you need: a different background, a completely different layout, a meaningful chapter break. For animation sequences on a shared canvas, stay in one phase.


17. The Layer Depth Stack

VMD scenes are built in layers from back to front. Think of it as 10 planes. Knowing which plane each kind of element belongs to gives you the grammar of a well-composed scene:

PlaneRoleWhat lives here
0Canvasbackground: color
1Image worldFull-bleed photo, Ken Burns motion
2AtmosphereFull-canvas color overlay rectangle
3PanelsDark content-protection rectangles
4FrameHUD corners, dividers, parametric rings, scan lines
5Ghost textLarge barely-visible concept word
6World iconsEdge icons at low opacity, present from t=0
7Ambient physicsDrifting particles, background orbits
8DramaText, charts, diagrams — the argument
9Active physicsShockwaves, data packets, causality events
10Cinematic crownAmber word, fourth-wall flood

18. Scene Timing

Every scene follows a structure in time:

WindowWhenRule
Initialization0–2sWorld elements only. No content, no drama.
Settlement2–5sKen Burns starts. Overlay appears. World icons arrive.
Drama5s to end−3sText beats. Physics events. Minimum 3s between beats.
Holdlast 3sNothing new starts. Let the last beat breathe.

The causality rule: physics fires at time T. Confirming text fires at T+0.3s. This is not optional — it is the grammar of VMD. The physics proves the claim; the text confirms what you just saw. Never the other way around.


Part VI — Cinematic Techniques

19. The Overlay System

A full-canvas rectangle w:16.5 h:12.5 with a tinted color wash. One per scene. The overlay color is used nowhere else — it is the scene's atmospheric tone.

Common overlay colors and what they communicate:

ColorHexPeak opacityTone
Crimson#8b1a1a/0.22power, dominance
Amber#92400e/0.30warmth, creative peak
Violet#2e1065/0.27depth, legacy
Indigo#0d0d2b/0.22cosmic scale
Dark base#000000/0.45pure cinema, content protection

Standard behavior: hold transparent → rise to peak ease.inOut 3–4s → retreat to /0.12–0.18. Never retreat to zero. The overlay remains as a permanent tint.


20. The Panel System

Panels are dark rectangles that carve out protected space for content — text and stats sit inside them, readable against any background image.

Common panel types:

Panelatwhopacity
Right(5.0, 0)6.512/0.82
Left(-5.0, 0)6.512/0.82
Bottom bar(0, -4.5)166/0.84
Center card(x, y)5.03.0/0.76

Panels emerge: opacity 0→target in 1.4s ease.out. A split line appears at +0.3s. Content arrives at +0.3–0.6s after the line.

Optional border: stroke:#4fc3f7/0.22 strokeWidth:0.5


21. Frame and HUD

Four corner brackets establish a cinematic frame — each bracket is one horizontal + one vertical short line. Length 0.7–1.0, stroke:#4fc3f7/0.50 strokeWidth:1.2. Stagger: TL at T, TR at T+0.2s, BL at T+0.3s, BR at T+0.4s.

Parametric rings add depth around a focal point:

  • Inner aura: r:2.0 stroke:#4fc3f7/0.30 strokeWidth:1.2
  • Outer aura: r:3.5 stroke:#4fc3f7/0.14 strokeWidth:0.7

Scan line: rectangle w:16 h:0.09 fill:#4fc3f7/0.48 sweeping from y:6.5 to y:-6.5 over 1.8s linear.


22. Ghost Text and World Icons

Ghost text: a concept word at fontSize:64–80 fill:#ffffff/0.15 fontWeight:bold placed at center, arriving with fadeIn 2s delay:0s. It is texture — it is not the argument. The argument lives on top of it in the drama layer.

World icons: 4–8 icons at the canvas edges at size:0.5–1.1 and color:/0.30–0.42, present from the first frame. One or two rotate slowly rotate(360deg, 9s, linear, 0s). They set the thematic world of the scene without competing with content.


23. Scene Transitions

Black cut — hard chapter break. Place at the START of the new scene:

cut: type: morph
shape: rectangle[w:16.5 h:12.5 fill:#000000/1.0 at:(0,0)] ~> rectangle[w:16.5 h:12.5 fill:#000000/0.0 at:(0,0)] (0.7s, ease.out, 0s)

Fade to black — contemplative ending. Place in the current scene's last element:

shape: rectangle[w:16.5 h:12.5 fill:#000000/0.0 at:(0,0)] ~> rectangle[w:16.5 h:12.5 fill:#000000/0.0 at:(0,0)] (13s, linear, 0s) ~> rectangle[w:16.5 h:12.5 fill:#000000/0.90 at:(0,0)] (2.5s, ease.inOut, 13s)

Cinematic hold — nothing new starts in the last 3 seconds. Mandatory in every scene.


24. The Cinematic Crown

The crown is the final moment. Every scene ends with one.

The amber word:

  • A dim veil: rectangle fill:#000000/0→/0.38 in 0.6s ease.out
  • The word: text fontSize:40–64 fill:#ffd54f/0→/0.95 at T+0.3s
  • After holding 2.5–3.5s, the word shrinks to fontSize:13 fill:#ffd54f/0.28 and moves to a bottom corner — carrying forward as a whisper into subsequent scenes

#ffd54f must not appear anywhere in the scene before this moment. The amber word's power comes entirely from contrast. One early use destroys it for the entire film.

The fourth-wall break — once per film only. The flood and the text land simultaneously (not at T+0.3s). The simultaneity IS the break. Requires three or more prior scenes of consistent grammar before it earns the effect.


Part VII — Effects and Physics

25. Building Visual Effects

VMD has no built-in particle system, no shockwave primitive, no pre-made effect library. What it has is morph, stagger, opacity, position, size — and those compose into anything you can imagine. Every visual effect in VMD is built from the same primitives you already know.

The core logic of any effect:

  • A morph that changes size, position, or opacity over time creates motion and presence
  • Staggered delays on multiple identical elements create rhythm and density
  • Easing controls whether the effect feels explosive, organic, mechanical, or gentle
  • Layering several morphs on the same event point builds complexity from simple parts

Drifting particles — staggered small circles moving and fading:

p1: type: morph
shape: circle[r:0.06 fill:#4fc3f7/0.0 at:(-2,-1)] ~> circle[r:0.06 fill:#4fc3f7/0.65 at:(-2,5)] (2.5s, ease.out, 0s) ~> circle[r:0.06 fill:#4fc3f7/0.0 at:(-2,5.5)] (0.5s, ease.out, 2.5s)
p2: type: morph
shape: circle[r:0.05 fill:#4fc3f7/0.0 at:(1,-1)] ~> circle[r:0.05 fill:#4fc3f7/0.55 at:(1,5)] (2.8s, ease.out, 0.4s) ~> circle[r:0.05 fill:#4fc3f7/0.0 at:(1,5.5)] (0.5s, ease.out, 3.2s)

Eight of these with varied x positions, delays, and sizes — you have a particle field. The effect is entirely yours: direction, speed, density, color, fade timing.

An expanding ring — a circle that grows and disappears:

ring: type: morph
shape: circle[r:0.3 stroke:#4fc3f7/0.55 strokeWidth:2.2 at:(0,0)] ~> circle[r:8.0 stroke:#4fc3f7/0.0 strokeWidth:0.6 at:(0,0)] (3.0s, ease.out, 0s)

Three of these staggered 0.4s apart creates the visual signature of an energy release. The stroke-only rule applies: no fill property, or the morph breaks silently.

A full-canvas color flash — atmosphere in a moment:

flood: type: morph
shape: rectangle[w:16.5 h:12.5 fill:#4fc3f7/0.0 at:(0,0)] ~> rectangle[w:16.5 h:12.5 fill:#4fc3f7/0.22 at:(0,0)] (0.6s, ease.out, 0s) ~> rectangle[w:16.5 h:12.5 fill:#4fc3f7/0.0 at:(0,0)] (2.0s, ease.inOut, 0.6s)

A moving object with a trail:

packet: type: morph
shape: circle[r:0.10 fill:#4fc3f7/0.9 at:(-4,0)] ~> circle[r:0.10 fill:#4fc3f7/0.9 at:(4,0)] (1.2s, linear, 0s)
trail: type: morph
shape: line[from:(-4,0) to:(-4,0) stroke:#4fc3f7/0.4 strokeWidth:1 at:(0,0)] ~> line[from:(-4,0) to:(4,0) stroke:#4fc3f7/0.4 strokeWidth:1 at:(0,0)] (1.2s, linear, 0s)

The point is not that these effects exist — it is that you create them. Any visual behaviour you can describe as a change in position, size, color, or opacity over time can be built this way. Stack morphs, stagger delays, combine primitives, layer elements. The effect space is unlimited.


29. Charts and Diagrams

VMD has no built-in chart primitive. Charts are composed from 2D shapes in a fixed build order:

  1. Panel background (rectangle, dark card)
  2. Grid H lines (line, stroke:/0.12 strokeWidth:0.4)
  3. Grid V lines
  4. Axes (line, stroke:/0.55 strokeWidth:1.2)
  5. Axis arrows (triangle size:0.12)
  6. Tick labels (text fontSize:9)
  7. Data (see chart type below)
  8. Tip dot (circle r:0.12 fill:/1.0)
  9. Tip aura (circle stroke:/0.40)
  10. Value label (text fontSize:13 fontWeight:bold)

Chart types from primitives:

ChartData element
Line graphfunctionGraph
Bar chartrectangle per bar, morph h:0→value
Scattercircle per point, staggered fadeIn
Pie / donut (2D)circle[arc:N arcStart:N] per segment, morph arc:0→N
Pie / donut (3D)torus[arc:N rotate3d:(0,-offset,0)] per segment
Arearectangle fill behind functionGraph
Venntwo overlapping parametric at /0.20
Gantthorizontal rectangle per task

Pie and donut charts deserve a direct example since the technique is specific. Each segment is one circle with arc (how many degrees it spans) and arcStart (where it begins). Segments are staggered in and each morphs from arc:0 to its final angle. A donut is the same with a background-colored circle placed on top to cut the center hole:

seg_a: type: morph
shape: circle[r:2.0 arc:0 arcStart:0 fill:#4fc3f7 opacity:0 at:(0,0)] ~> circle[r:2.0 arc:144 arcStart:0 fill:#4fc3f7 opacity:1 at:(0,0)] (1.5s, ease.out, 0.5s)
seg_b: type: morph
shape: circle[r:2.0 arc:0 arcStart:144 fill:#c77aff opacity:0 at:(0,0)] ~> circle[r:2.0 arc:108 arcStart:144 fill:#c77aff opacity:1 at:(0,0)] (1.2s, ease.out, 2.2s)
hole: type: primitive
shape: circle[r:0.72 fill:#0f0f0f at:(0,0)]

The same chart in 3D uses torus[arc:N] segments, each rotated to its start angle with rotate3d:(0,-offset,0) and given spin3d:(0,8,0) for a slow unified rotation.

Diagram types are built the same way: node-edge graphs from circle+line, flow diagrams from rectangle+line+triangle, timelines from a horizontal line + circle nodes.

Animation order for charts: axes first, then grid, then data draws, then the tip dot springs in (ease.spring 0.4s), then value label fades. Stagger multiple bars or segments 0.2–0.4s.


Part VIII — Image Techniques

30. Ken Burns and Arrivals

Ken Burns — slow scale and drift over a still image — is how images become cinematic:

bg: type: morph
shape: image[src:"images/photo.jpg" w:16.5 h:12.5 at:(0,0) opacity:0.9] ~>
image[src:"images/photo.jpg" w:19.0 h:14.4 at:(0.8,-0.4) opacity:0.9] (18s, ease.inOut, 0s)

Always hold at the start state for 3–7s linear before beginning the Ken Burns motion. Counter- drift: if the image drifts right, bring content in from the left. The opposition creates energy.

Image arrival types:

ArrivalTechnique
Iris birthseed tiny circle → image expands from center, ease.in
Scanline bootscan rectangle sweeps top to bottom; image desaturated → full color at sweep end
Blur-to-sharpblur:4 w:22 → blur:0 w:16.5, ease.inOut 3s
Curtain revealcrop:(-8,-6,0.1,12) → crop:(-8,-6,16,12), ease.inOut 4s
Opacity fadeopacity:0 → opacity:0.90, ease.out 2s

Part IX — 3D Scenes

31. Thinking in 3D

A 3D phase renders a full perspective scene instead of a flat canvas. The coordinate system is Y-up: X is right, Y is up, negative Z is into the scene.

Two elements are required in every 3D phase: a camera and a light. Missing either produces a broken render with no error message.

Shape sizes in 3D are in world units, not canvas units. What is visible depends entirely on the camera distance. At a camera distance of 8 units with fov:50, one world unit is approximately 40px on a 600px canvas. A sphere with r:0.1 is 8px — barely visible. Match shape sizes to how far the camera is from the scene.


32. Camera and Light

camera[at:(0,3,-8) lookAt:(0,0,0) fov:50 fog:0 fogColor:#000000 orbit:(rx,ry,rz)]
light[direction:(-1,-1,-1) intensity:0.9 ambient:0.2]

Camera: at is where the camera sits. lookAt is what it faces. fov is field of view in degrees — 40 is telephoto (compressed, dramatic), 70 is wide (spacious, distorted edges). fog adds atmospheric depth haze.

orbit:(rx,ry,rz) rotates the camera position around lookAt at degrees/second per axis. Y-axis orbit (ry) sweeps horizontally; X-axis orbit (rx) arcs vertically. Example: orbit:(0,40,0) spins the camera 40°/s around the scene. Both axes can run simultaneously.

Light: direction is the direction the light travels (e.g. (-0.4,-0.7,0.4) = downward and forward). The renderer flips it internally to compute the "toward light" vector. intensity is the main brightness (0–1). ambient is the floor — how bright the darkest shadow is. specular (0–1) adds a glossy sheen; pair with shininess (higher = tighter highlight).

Common light presets:

  • Sunlight: direction:(-0.6,-0.8,0.5) intensity:0.95 ambient:0.25
  • Dramatic: direction:(0,-1,0.3) intensity:0.4 ambient:0.05
  • Glossy metal: direction:(-0.6,-0.8,0.5) intensity:0.95 ambient:0.25 specular:0.6 shininess:64

33. 3D Shapes

All 3D shapes share these universal properties:

fill:#hex opacity:N at:(x,y,z)
rotate3d:(rx,ry,rz) spin3d:(rx,ry,rz)
scaleX:N scaleY:N scaleZ:N
emissive:true repeat:N offset:(dx,dy,dz)

opacity is always a separate property in 3D. Writing fill:#hex/0.5 in a 3D shape has no effect — the opacity is silently ignored. Always write opacity:0.5 separately.

spin3d values are degrees per second. They accumulate over time: spin3d:(0,30,0) rotates 30° per second around Y. spin3d resets to rotate3d + 0° at the start of each phase.

The 3D shape library:

sphere[r:1 segments:24 stacks:16 fill:#4fc3f7]
cube[size:1 fill:#f472b6] # or w:N h:N d:N explicit
cylinder[r:0.5 h:2 fill:#a3e635] # runs along Y; rotate3d:(90,0,0) for Z
cone[r:0.5 h:2 fill:#ffd54f] # tip at +Y, base at -Y
torus[size:2 r:0.3 arc:360 fill:#4fc3f7] # arc < 360 = partial ring
hemisphere[r:1 flip:false fill:#f472b6] # flip:true = bowl
prism[sides:6 r:1 h:2 fill:#a78bfa] # sides:3=triangle, 6=hexagon
plane[w:6 h:4 subdivisions:8 fill:#1e293b] # flat XZ quad, +Y normal
line3d[from:(0,0,0) to:(3,0,0) r:0.05 fill:#64748b] # tube between points

Rotation quick reference:

WantUse
Cylinder along Zrotate3d:(90,0,0)
Cylinder along Xrotate3d:(0,0,90)
Torus in XY planerotate3d:(90,0,0)
Ring tilted N° forwardrotate3d:(N,0,0)
Two opposite beamsrotate3d:(35,0,0) and rotate3d:(35,180,0) with same spin3d

34. Emissive Materials and Layered Glow

emissive:true makes a shape glow at its full fill color, ignoring the light. Use it for anything that should appear to emit light: stars, energy cores, neon lines, data pulses.

Critical: emissive works on fill color. A dark fill like #040e1c glows black — invisible on a dark background. Any hex channel must be above #30 for the glow to be visible.

Layered glow — stack concentric shapes with decreasing radius and increasing opacity:

glow_outer: sphere[r:0.90 fill:#0a3a6a opacity:0.30 emissive:true at:(0,0,0)]
glow_mid: sphere[r:0.50 fill:#0d5a90 opacity:0.55 emissive:true at:(0,0,0)]
glow_inner: sphere[r:0.25 fill:#1890c8 opacity:0.80 emissive:true at:(0,0,0)]
core: sphere[r:0.12 fill:#4fc3f7 opacity:1.0 emissive:true at:(0,0,0)]

This technique works on any geometry — spheres for stars, surface3d cones for beams.

Transparent shapes in 3D render in a second pass after all opaque geometry. Stack multiple transparent shells freely — they are z-tested but not z-written.


35. Rotation and Repetition

rotate3d:(rx,ry,rz) applies fixed Euler angles in degrees, Rx → Ry → Rz order. spin3d:(rx,ry,rz) adds animated rotation at degrees-per-second over time. They stack: final rotation = [rx + spinX×t, ry + spinY×t, rz + spinZ×t].

Group spin — spinning a composed object (multiple shapes) as one rigid body: give every part the same spin3d, using only Y-offset in at. Any Y-offset rotates identically around the Y axis.

body: sphere[r:1 at:(0,0,0) spin3d:(0,22,0) fill:#e2e8f0]
wings: cube[w:5 h:0.1 d:1.2 at:(0,-0.05,0) spin3d:(0,22,0) fill:#cbd5e1]
fin: cube[w:0.07 h:0.6 d:1 at:(0,0.27,0) spin3d:(0,22,0) fill:#cbd5e1]

Repeat clones a shape N times, each shifted by offset:

sphere[r:0.3 fill:#34d399 at:(0,0,0) repeat:5 offset:(1.4,0,0)]
# creates 5 spheres at x=0, 1.4, 2.8, 4.2, 5.6

36. Parametric Surfaces

surface3d generates a triangle mesh from three math expressions over a 2D parameter space. It is the most powerful primitive in VMD — any smooth 3D surface can be expressed this way.

surface3d[xFn:"expr(u,v)" yFn:"expr(u,v)" zFn:"expr(u,v)"
uRange:[start,end] vRange:[start,end]
segments:32 stacks:32
fill:#4fc3f7 opacity:1
colorMap:"viridis" colorMode:"height"
wireframe:false lineWidth:1]

u and v are the two parameters swept across their ranges. Every point is (x(u,v), y(u,v), z(u,v)). Expressions have access to all Math.* methods, plus PI and TAU.

Any numeric property that is not a reserved name becomes a formula variable:

surface3d[xFn:"(R+r*cos(v))*cos(u)" yFn:"r*sin(v)" zFn:"(R+r*cos(v))*sin(u)" R:2.0 r:0.4 ...]

Reserved (cannot be formula variables): opacity, segments, stacks, scaleX, scaleY, scaleZ, repeat, rotate3d, spin3d, at, uRange, vRange.

Color maps: viridis, plasma, heat, turbo, coolwarm, magma, ocean Color modes: flat (uniform fill), u (gradient by u), v (gradient by v), height (by Y)

Wireframe mode: wireframe:true draws the mesh as edges instead of filled triangles. This bypasses back-face culling — the reason wireframe is essential for one-sided surfaces like the Möbius strip or Klein bottle. One side always faces away from the camera; wireframe shows both.

Double-sided solid: render two copies of the same surface, second with uRange reversed:

side_a: surface3d[... uRange:(0,6.2832) fill:#c0c0c0]
side_b: surface3d[... uRange:(6.2832,0) fill:#c0c0c0]

side_a catches front-faces, side_b catches back-faces. Together they cover the full surface.

Ready-to-use shapes:

Torus: xFn:"(R+r*cos(v))*cos(u)" yFn:"r*sin(v)" zFn:"(R+r*cos(v))*sin(u)" R:2 r:0.4 vRange:[0,6.2832]
Möbius: xFn:"(1+v*0.5*cos(u*0.5))*cos(u)" yFn:"v*0.5*sin(u*0.5)" zFn:"(1+v*0.5*cos(u*0.5))*sin(u)" vRange:[-1,1]
Helix: xFn:"(1+0.15*cos(v))*cos(u)" yFn:"u*0.2+0.15*sin(v)" zFn:"(1+0.15*cos(v))*sin(u)" uRange:[0,18.85] vRange:[0,6.2832]
Saddle: xFn:"u" yFn:"u*u-v*v" zFn:"v" uRange:[-2,2] vRange:[-2,2] colorMap:"coolwarm" colorMode:"height"
Wormhole: xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 vRange:[-4.5,4.5]

Height map — simpler entry point for mathematical landscapes:

surface[fn:"sin(x)*cos(y)" xRange:[-3,3] yRange:[-3,3] resolution:32 colorMap:"viridis" colorMode:"height"]

xRange and yRange use array brackets [start,end], not parentheses. colorMode options: height (default), normal, radial, custom (with colorExpr:"expr").

Camera orientation for surface: the surface lies flat in the XZ planexRange maps to world X, yRange maps to world Z, and fn output is the world Y height. A camera directly overhead sees it nearly edge-on. Always use a low diagonal camera: camera[at:(4,3,-5) lookAt:(0,0,0) fov:65]

Fading in a 3D shapefadeIn does not work on 3D. Use morph:

type: morph
shape: sphere[r:0.0 fill:#4fc3f7 opacity:0.0 at:(0,0,0)] ~> sphere[r:0.8 fill:#4fc3f7 opacity:1.0 at:(0,0,0)] (1.0s, ease.out, 0.5s)

If the shape has spin3d, set it in both the from and to states.


Part X — Avatars

37. The Character System

A VMD avatar is a fully rigged 2D cartoon character defined in plain text, inline in the same file that uses it. No external asset, no design tool.

avatars:
hero: figure[name:"hero"]{root[shape:none] chest[...] neck[...] head[...] ...all 19 points...}
phases:
scene:
duration: 5
elements:
hero:
type: figure
shape: figure[use:"hero" at:(0,-0.45) scale:85] -> gesture("walk", 5s, 0s) -> moveTo((3,-0.45), 5s, 0s)

avatars: comes before phases:. The inline figure[name:"..."]{} definition works everywhere — CLI, website preview, Docker, production. Only gesture and moveTo are valid on a figure element. fadeIn, rotate, scale are silent no-ops.

Built-in charactersnarrator and luna are pre-defined and do not need an entry in avatars:. Use them directly: figure[use:"narrator" at:(0,-0.45) scale:85].


38. The 19 Anchor Points

Every figure must define exactly these 19 named anchor points — missing any one is a hard error:

root, 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

The skeleton is a forward-kinematics chain. Rotating a joint orients all segments distal to it. To swing the whole arm: rotate shoulder_r. To additionally bend the forearm on top of that: rotate elbow_r. Rotating elbow_r does not re-orient the upper arm — only what comes after it.

Reference proportions (head r:0.34 is the anchor):

PartKey value
chestwidth:0.6 widthEnd:0.5
necklength:0.22
headr:0.34 length:0.20
eye_l/rw:0.10 h:0.075
shoulder_l/r circler:0.14 — match chest fill
elbow_l/r capsulewidth:0.20 widthEnd:0.17
hand_l/r capsulewidth:0.14 widthEnd:0.11
knee_l/r capsulewidth:0.26 widthEnd:0.22
foot_l/r capsulewidth:0.20 widthEnd:0.24 — wider end = shoe sole
hip_l/r circler:0.14 — match chest fill

The shoulder_l/r and hip_l/r joint circles must match the chest fill color — they are the torso surface at those joints. Color transitions (shirt to skin, trouser to shoe) happen at hand_l/r and foot_l/r.


39. Shape Kinds and Head Shading

Only five shape kinds exist for anchor points and extras:

ShapeNotes
noneOnly for root
circleSafe on any point; rotation-invariant
ellipseAccepts angle (radians) for a static authored tilt
capsuleChain-points only. On extras it silently renders nothing.
pathpoints:[[-0.3,0.1],[0.1,0.4],...] in local offsets; smoothing:0.55

Head shading adds depth. The shadeFill property enables it:

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]

shadeDx:0.12 shadeDy:-0.14 = upper-left light source. shadeFill should be two shades darker than fill. Works on circle, ellipse, path.


40. Extras

Extras are decorations attached to any anchor point:

extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#2b211c angle:0.08 offset:(-0.15,0.10) role:"eyebrow_l"]
  • Pupils: attachTo:"eye_l" + shape:circle — gaze tracking activates automatically by attachment point alone. No role tag needed.
  • Eyebrows: attachTo:"head" + role:"eyebrow_l" or role:"eyebrow_r". Only these two role values exist. The role makes the eyebrow react to expression gestures.
  • Hair: path on head. Keep fringe points at y > ~0.08 in head-local space — lower than that visually covers the eyes.
  • Fingers: 2–3 small ellipse extras on hand_l and hand_r. A bare capsule hand reads as a mitten.
  • capsule on extras renders nothing. Use circle, ellipse, or path only.

Paint order: all 19 anchor point shapes paint first, then extras in listed order. Extras are always on top — there is no "behind the body" option.


41. Gestures and Expressions

Chain gestures with -> the same way as any animation:

shape: figure[use:"hero" at:(0,-0.45) scale:85]
-> gesture("walk", 5s, 0s)
-> gesture("talk", 5s, 0s)
-> moveTo((3,-0.45), 5s, 0s)

Gestures that run in the same time window merge per-channel. talk drives mouth_open. walk drives legs and arms. Neither overwrites the other. A character can walk, talk, and smile at once.

Gesture library:

GestureWhat it does
idleBreathing loop — tiny chest + head oscillation. Run continuously under other gestures.
walkAlternating legs + counter-arm swing. Pair with moveTo for actual movement.
waveRight arm raises and waves twice, lowers.
talkUneven mouth open/close chatter.
explainBoth forearms lift symmetrically toward chest — presenter gesture.
point-leftLeft arm extends toward left with genuine elbow bend.
point-rightMirror of point-left.
point-upRight arm raises above head.
look-leftPupils shift left, return.
look-rightPupils shift right, return.
look-upPupils shift up, return.
head-tiltSide-to-side head arc.
shrugBoth arms out and up — "I don't know."
happySmile, eyebrows up, eyes wide.
sadFrown, eyebrows in, eyes narrower.
surprisedEyes wide, brows high, mouth opens — snaps in fast.
angryBrows down sharply, eyes narrow, frown.
flinchStartled body snap + arms defensive + surprised expression — one call.
laughChest shake + rapid mouth + happy expression — one call.

Expression gestures (happy, sad, surprised, angry) compose freely with everything else.


42. Multiple Characters

Define multiple named avatars in the avatars: block. Reference each independently in phases with separate type: figure elements. Each character's animations and gestures are fully independent:

avatars:
teacher: figure[name:"teacher"]{...}
student: figure[name:"student"]{...}
phases:
dialogue:
duration: 8
elements:
teacher:
type: figure
shape: figure[use:"teacher" at:(-2.5,-0.45) scale:85] -> gesture("explain", 5s, 0s) -> gesture("talk", 5s, 0s)
student:
type: figure
shape: figure[use:"student" at:(2.5,-0.45) scale:85] -> gesture("idle", 8s, 0s) -> gesture("look-left", 2s, 1s)

Appendices

A. The Renderer Laws

These are non-negotiable rules. Violating any of them produces no error — just silent wrong output.

  • shape: is always one line. Any line break = parser failure.
  • total_duration = exact sum of all phase durations. Any discrepancy = hard error, file will not render.
  • All delays are absolute from phase start, not relative to the previous step.
  • Every element is visible at t=0 unless explicitly spawned invisible.
  • Stroke-only shapes (rings, auras): no fill property at all. fill:#hex/0.0 = silent morph break.
  • icon format is always "prefix:name". Wrong format = silent placeholder.
  • group expression must be on one line.
  • 3D: opacity is always a separate property. fill:#hex/0.5 = silent ignore.
  • 3D: fadeIn is a no-op. Use type: morph with opacity:0.0 → 1.0.
  • 3D: spin3d resets to rotate3d + 0° at the start of each phase.
  • 3D: every phase needs both camera and light.
  • Avatars: all 19 anchor points required. Missing any = silent render failure.
  • Avatars: capsule on extras = silent nothing. Use circle, ellipse, or path.
  • Avatars: fadeIn, rotate, scale on a figure = silent no-ops.
  • #ffd54f appears nowhere before the cinematic crown moment.

B. Common Mistakes

SymptomCauseFix
Element visible before its fadeInNo spawn gateStart with r:0.0 fill:#hex/0.0
Morph ring breaks at startfill:#hex/0.0 on stroke-onlyRemove fill property entirely
Wrong output, no errortotal_duration mismatchAdd up all phase durations
3D shape invisibleDark fill + emissive:trueBrighten fill — one channel above #30
3D opacity ignoredUsing fill:#hex/N syntaxAdd opacity:N as separate property
Avatar missingAnchor point omittedAll 19 points required — hard error if any missing
Icon shows placeholderWrong formatUse "prefix:name" — both parts
Amber word has no impact#ffd54f used earlier in sceneRemove all early uses
Möbius strip half-invisibleBack-face cullingAdd wireframe:true or reversed uRange copy
3D shape stops spinning at phase boundaryspin3d resetSet rotate3d in next phase to match end angle

C. Easing Quick Reference

EasingFeel
ease.outLands softly — the default for arrivals
ease.inBuilds then releases — departures, inevitable motion
ease.inOutSmooth start and end — travel, Ken Burns
linearMechanical, constant — orbits, data flow, hold steps
ease.springOvershoots and settles — UI pop, character snap
ease.elasticMultiple overshoots — surprise, instability
bounceHits the floor and bounces — physical landing

D. Icon Library

11,000+ pre-drawn objects — animate any of them with fadeIn, moveTo, rotate, scale, morph, or any other command. No drawing required.

PrefixCountStyleWhat's inside
mdi:~7,400FilledPeople, animals, machines, tools, science, space, biology, buildings, technology, nature, IoT, medical — the primary library
lucide:~1,745OutlineArchitecture diagrams, data flow, UI elements, technical concepts — strict 24×24 grid design
logos:~1,861ColorBrand marks and product logos — full color, not monotone

Size guide: 2.5–4.0 hero subject, 1.4–1.8 supporting, 0.8–1.2 world background, 0.5–0.9 ambient.

Format always "prefix:name". Both parts required — wrong format = silent placeholder.


E. Surface3d Shape Library

Sphere: xFn:"r*sin(v)*cos(u)" yFn:"r*cos(v)" zFn:"r*sin(v)*sin(u)" r:1 uRange:[0,6.2832] vRange:[0,3.1416]
Torus: xFn:"(R+r*cos(v))*cos(u)" yFn:"r*sin(v)" zFn:"(R+r*cos(v))*sin(u)" R:2 r:0.4 uRange:[0,6.2832] vRange:[0,6.2832]
Möbius: xFn:"(1+v*0.5*cos(u*0.5))*cos(u)" yFn:"v*0.5*sin(u*0.5)" zFn:"(1+v*0.5*cos(u*0.5))*sin(u)" uRange:[0,6.2832] vRange:[-1,1]
Helix: xFn:"(1+0.15*cos(v))*cos(u)" yFn:"u*0.2+0.15*sin(v)" zFn:"(1+0.15*cos(v))*sin(u)" uRange:[0,18.85] vRange:[0,6.2832]
Saddle: xFn:"u" yFn:"u*u-v*v" zFn:"v" uRange:[-2,2] vRange:[-2,2] colorMap:"coolwarm" colorMode:"height"
Wormhole: xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 uRange:[0,6.2832] vRange:[-4.5,4.5]
Trefoil: xFn:"(2+cos(1.5*u))*cos(u)*0.5+0.2*cos(v)" yFn:"sin(1.5*u)*0.5+0.2*sin(v)" zFn:"(2+cos(1.5*u))*sin(u)*0.5" uRange:[0,12.566] vRange:[0,6.2832]
Ellipsoid: xFn:"a*sin(v)*cos(u)" yFn:"b*cos(v)" zFn:"c*sin(v)*sin(u)" a:1.5 b:0.8 c:1.5 uRange:[0,6.2832] vRange:[0,3.1416]
Cone beam: xFn:"r*(1-v)*cos(u)" yFn:"h*v-h*0.5" zFn:"r*(1-v)*sin(u)" r:0.4 h:5 uRange:[0,6.2832] vRange:[0,1]

All accept fill, opacity, emissive, spin3d, rotate3d, at, wireframe.


Part XI — Complete Examples

Three complete, runnable VMD files. Each one is designed to be read as a whole — not as a reference, but as a working scene you can copy, run, and learn from. The code is the teaching.

Important: In these examples, shape: definitions are split across multiple lines for readability. In a real .vmd file, every shape: and its full chain must be one continuous line. VMD has no rule against long lines — only against broken ones.


Example 1 — The Morph Chain

What it shows: type: morph at its simplest. One element, six states, one unbroken chain. Every shape transition is smooth. No code, no loop — just states and the language filling the space between them.

Notice: all delays are absolute from phase start. Each ~> step fires at a clock time, not "after the previous step ends." Read the delays: 0s, 1.5s, 3s, 4.5s, 6s. That's the schedule.

concept: "The Morph Chain"
total_duration: 10
background: "#0a0e1a"
phases:
chain:
duration: 10
elements:
label:
type: primitive
shape: text[text:"type: morph" fontSize:14 fill:#58a6ff/0.0 at:(0,4.5)]
-> fadeIn(0.5s, 0.3s)
shape:
type: morph
shape: circle[r:1.0 fill:#ff4444 at:(0,0)]
~> square[w:2.0 fill:#ff8800 at:(0,0)] (1.5s, ease.inOut)
~> triangle[w:2.5 h:2.5 fill:#ffff00 at:(0,0)] (1.5s, ease.inOut, 1.5s)
~> star[points:5 outerRadius:1.2 innerRadius:0.6 fill:#44ff88 at:(0,0)] (1.5s, ease.inOut, 3.0s)
~> regularPolygon[sides:6 r:1.2 fill:#4488ff at:(0,0)] (1.5s, ease.inOut, 4.5s)
~> circle[r:1.0 fill:#cc44ff at:(0,0)] (1.5s, ease.inOut, 6.0s)

Example 2 — The Cinematic

What it shows: how a real presentational scene is structured from first element to crown. One file, 18 seconds, three phases. Background image with continuous ken burns across all phases. Vintage color overlay. A designed avatar character that walks in, points, talks, then celebrates. Three large icons arriving staggered. A function graph that morphs mid-scene. A three-ring shockwave crown at the end.

Notice: the bg image morph in each phase picks up exactly where the previous phase left off — w:18.0→19.8, then 19.8→21.6, then 21.6→23.4. That is a single 18-second zoom written in three 6-second segments. The vintage overlay is just a semi-transparent rectangle, persistent across all phases. The crown fires at t=0 of the last phase, not t=0 of the whole scene — because every delay is absolute within its phase clock.

concept: "The Laws That Move Everything"
total_duration: 18
background: "#0d0a07"
avatars:
newton: figure[name:"newton"]{root[shape:none] chest[shape:capsule width:0.62 widthEnd:0.52 fill:#5c2a3a] neck[shape:capsule width:0.16 fill:#e8cfa8 length:0.22] head[shape:circle r:0.34 fill:#e8cfa8 shadeFill:#7a4a2e shadeOpacity:0.20 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.12 h:0.026 fill:#9a5f55 offset:(0,-0.16)] shoulder_l[shape:circle r:0.135 fill:#5c2a3a offset:(-0.20,-0.05)] shoulder_r[shape:circle r:0.135 fill:#5c2a3a offset:(0.20,-0.05)] elbow_l[shape:capsule width:0.19 widthEnd:0.16 fill:#5c2a3a] elbow_r[shape:capsule width:0.19 widthEnd:0.16 fill:#5c2a3a] hand_l[shape:capsule width:0.135 widthEnd:0.105 fill:#e8cfa8] hand_r[shape:capsule width:0.135 widthEnd:0.105 fill:#e8cfa8] hip_l[shape:circle r:0.135 fill:#5c2a3a offset:(-0.20,-0.04)] hip_r[shape:circle r:0.135 fill:#5c2a3a offset:(0.20,-0.04)] knee_l[shape:capsule width:0.25 widthEnd:0.21 fill:#2b2420] knee_r[shape:capsule width:0.25 widthEnd:0.21 fill:#2b2420] foot_l[shape:capsule width:0.19 widthEnd:0.23 fill:#1a1410] foot_r[shape:capsule width:0.19 widthEnd:0.23 fill:#1a1410] 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:path fill:#dcd6c8 smoothing:0.55 points:[[-0.34,-0.60],[-0.50,-0.20],[-0.46,0.10],[-0.52,0.30],[-0.34,0.42],[-0.40,0.50],[-0.16,0.50],[0,0.46],[0.16,0.50],[0.40,0.50],[0.34,0.42],[0.52,0.30],[0.46,0.10],[0.50,-0.20],[0.34,-0.60],[0.18,-0.35],[0.16,0.27],[0,0.23],[-0.16,0.27],[-0.18,-0.35]]] extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#cfcabe angle:0.08 offset:(-0.15,0.085) role:"eyebrow_l"] extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#cfcabe angle:-0.08 offset:(0.15,0.085) role:"eyebrow_r"] extra[attachTo:"hand_l" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:0.3 offset:(0.04,-0.06)] extra[attachTo:"hand_l" shape:ellipse w:0.045 h:0.095 fill:#e8cfa8 angle:0.0 offset:(-0.02,-0.105)] extra[attachTo:"hand_l" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:-0.3 offset:(-0.08,-0.075)] extra[attachTo:"hand_r" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:-0.3 offset:(-0.04,-0.06)] extra[attachTo:"hand_r" shape:ellipse w:0.045 h:0.095 fill:#e8cfa8 angle:0.0 offset:(0.02,-0.105)] extra[attachTo:"hand_r" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:0.3 offset:(0.08,-0.075)]}
phases:
arrival:
duration: 6
elements:
bg:
type: morph
shape: image[src:"image_1.jpg" w:18.0 h:13.0 fit:cover opacity:0.70 at:(0,0)]
~> image[src:"image_1.jpg" w:19.8 h:14.3 fit:cover opacity:0.70 at:(0.20,0.12)] (6s, ease.inOut, 0s)
vintage:
type: morph
shape: rectangle[w:18 h:13 fill:#1e0c02/0.0 at:(0,0)]
~> rectangle[w:18 h:13 fill:#1e0c02/0.42 at:(0,0)] (2.5s, ease.out, 0s)
title:
type: primitive
shape: text[text:"The Laws That Move Everything" fontSize:30 fill:#f5e6c8/0.0 at:(0,4.3)]
-> fadeIn(0.8s, 0.5s)
subtitle:
type: primitive
shape: text[text:"Physics. Motion. VMD." fontSize:15 fill:#b8956a/0.0 at:(0,3.4)]
-> fadeIn(0.6s, 1.4s)
newton:
type: figure
shape: figure[use:"newton" at:(-5.5,-1.8) scale:82]
-> gesture("walk", 2.0s, 0.3s)
-> gesture("explain", 3.7s, 2.3s)
shockwave:
type: morph
shape: circle[r:0.15 stroke:#f5e6c8 strokeWidth:3.5 opacity:0.9 at:(-5.5,0.8)]
~> circle[r:4.5 stroke:#f5e6c8 strokeWidth:0.5 opacity:0.0 at:(-5.5,0.8)] (1.4s, ease.out, 1.2s)
content:
duration: 6
elements:
bg:
type: morph
shape: image[src:"image_1.jpg" w:19.8 h:14.3 fit:cover opacity:0.70 at:(0.20,0.12)]
~> image[src:"image_1.jpg" w:21.6 h:15.6 fit:cover opacity:0.70 at:(0.40,0.24)] (6s, ease.inOut, 0s)
vintage:
type: primitive
shape: rectangle[w:18 h:13 fill:#1e0c02/0.42 at:(0,0)]
newton:
type: figure
shape: figure[use:"newton" at:(-5.5,-1.8) scale:82]
-> gesture("point-right", 3.0s, 0s)
-> gesture("talk", 3.0s, 3.0s)
icon_apple:
type: primitive
shape: icon[icon:"mdi:food-apple" size:2.4 color:#d64545/0.0 at:(1.2,0.2)]
-> fadeIn(0.5s, 0.3s)
-> moveTo((1.2,0.9), 0.8s, ease.out, 0.3s)
icon_atom:
type: primitive
shape: icon[icon:"mdi:atom" size:2.4 color:#4fc3f7/0.0 at:(3.8,0.2)]
-> fadeIn(0.5s, 0.9s)
-> moveTo((3.8,0.9), 0.8s, ease.out, 0.9s)
icon_bolt:
type: primitive
shape: icon[icon:"mdi:lightning-bolt" size:2.4 color:#ffd54f/0.0 at:(6.4,0.2)]
-> fadeIn(0.5s, 1.5s)
-> moveTo((6.4,0.9), 0.8s, ease.out, 1.5s)
graph:
type: morph
shape: functionGraph[fn:"sin(x)" xStart:-3 xEnd:3 segments:120 stroke:#81c784 strokeWidth:2.5 opacity:0.0 at:(3.8,-1.8)]
~> functionGraph[fn:"sin(x)" xStart:-3 xEnd:3 segments:120 stroke:#81c784 strokeWidth:2.5 opacity:1.0 at:(3.8,-1.8)] (0.8s, ease.out, 2.0s)
~> functionGraph[fn:"cos(x)" xStart:-3 xEnd:3 segments:120 stroke:#4fc3f7 strokeWidth:2.5 opacity:1.0 at:(3.8,-1.8)] (1.5s, ease.inOut, 3.5s)
graph_label:
type: morph
shape: text[text:"f(x) = sin(x)" fontSize:11 fill:#81c784/0.0 at:(3.8,-2.9)]
~> text[text:"f(x) = sin(x)" fontSize:11 fill:#81c784/0.85 at:(3.8,-2.9)] (0.8s, ease.out, 2.0s)
~> text[text:"f(x) = cos(x)" fontSize:11 fill:#4fc3f7/0.85 at:(3.8,-2.9)] (1.5s, ease.inOut, 3.5s)
crown:
duration: 6
elements:
bg:
type: morph
shape: image[src:"image_1.jpg" w:21.6 h:15.6 fit:cover opacity:0.70 at:(0.40,0.24)]
~> image[src:"image_1.jpg" w:23.4 h:16.9 fit:cover opacity:0.70 at:(0.60,0.36)] (6s, ease.inOut, 0s)
vintage:
type: primitive
shape: rectangle[w:18 h:13 fill:#1e0c02/0.42 at:(0,0)]
newton:
type: figure
shape: figure[use:"newton" at:(-5.5,-1.8) scale:82]
-> gesture("happy", 3.0s, 0s)
-> gesture("wave", 3.0s, 3.0s)
burst:
type: morph
shape: circle[r:0.4 fill:#ffd54f/0.9 at:(0,0)]
~> circle[r:6.5 fill:#ffd54f/0.0 at:(0,0)] (1.8s, ease.out, 0s)
ring1:
type: morph
shape: circle[r:0.2 stroke:#ffd54f strokeWidth:3.5 opacity:1.0 at:(0,0)]
~> circle[r:6.0 stroke:#ffd54f strokeWidth:0.5 opacity:0.0 at:(0,0)] (1.6s, ease.out, 0.2s)
ring2:
type: morph
shape: circle[r:0.2 stroke:#f5e6c8 strokeWidth:2.8 opacity:0.85 at:(0,0)]
~> circle[r:6.0 stroke:#f5e6c8 strokeWidth:0.5 opacity:0.0 at:(0,0)] (1.6s, ease.out, 0.6s)
ring3:
type: morph
shape: circle[r:0.2 stroke:#4fc3f7 strokeWidth:2.2 opacity:0.70 at:(0,0)]
~> circle[r:6.0 stroke:#4fc3f7 strokeWidth:0.5 opacity:0.0 at:(0,0)] (1.6s, ease.out, 1.0s)
crown_text:
type: primitive
shape: text[text:"Every motion has a law.\nEvery law has a shape." fontSize:24 fill:#ffd54f/0.0 at:(0,-0.2)]
-> fadeIn(0.7s, 1.8s)
crown_sub:
type: primitive
shape: text[text:"This is VMD." fontSize:15 fill:#f5e6c8/0.0 at:(0,-1.8)]
-> fadeIn(0.6s, 2.8s)

Example 3 — The Wormhole

What it shows: 3D in VMD from first principles. No flat canvas — a full perspective scene with camera, light, and parametric geometry. Three concentric wormhole surfaces are the same Ellis formula at different throat radii, layered with different opacities to produce depth. Two flat spacetime sheets complete the physics image. Two camera angles, two phases.

Notice: emissive:true on the glow spheres means they ignore the light entirely — they are sources, not receivers. wireframe:true on the surfaces with a low lineWidth produces the mesh look without filling the faces. spin3d:(5,0,0) on every surface rotates them all at the same rate around the same axis — they move as one object. The second phase simply moves the camera inside the throat and slows the spin, and the whole scene changes character with those two edits.

concept: "Wormhole — Einstein-Rosen Bridge"
total_duration: 10
phases:
bridge_view:
duration: 5
elements:
camera:
type: primitive
shape: camera[at:(0,3.5,-7) lookAt:(0,0,0) fov:55]
light:
type: primitive
shape: light[direction:(-0.4,-0.7,0.4) intensity:1.05 ambient:0.28]
wh_outer:
type: primitive
shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0b1240 opacity:0.32 wireframe:true lineWidth:0.62 spin3d:(5,0,0) at:(0,0,0)]
wh_mid:
type: primitive
shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.5 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0f4fd4 opacity:0.62 wireframe:true lineWidth:0.88 spin3d:(5,0,0) at:(0,0,0)]
wh_core:
type: primitive
shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.28 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#1ea8ff opacity:0.92 wireframe:true lineWidth:1.08 spin3d:(5,0,0) at:(0,0,0)]
upper_sheet:
type: primitive
shape: surface3d[xFn:"u" yFn:"4.6" zFn:"v" uRange:[-6,6] vRange:[-6,6] segments:18 stacks:18 fill:#0a2a60 opacity:0.36 wireframe:true lineWidth:0.55 spin3d:(5,0,0) at:(0,0,0)]
lower_sheet:
type: primitive
shape: surface3d[xFn:"u" yFn:"-4.6" zFn:"v" uRange:[-6,6] vRange:[-6,6] segments:18 stacks:18 fill:#0a2a60 opacity:0.36 wireframe:true lineWidth:0.55 spin3d:(5,0,0) at:(0,0,0)]
glow_hot:
type: primitive
shape: sphere[r:0.07 fill:#ffffff opacity:1.0 emissive:true at:(0,0,0)]
glow_warm:
type: primitive
shape: sphere[r:0.24 fill:#80d8ff opacity:0.50 emissive:true at:(0,0,0)]
glow_cool:
type: primitive
shape: sphere[r:0.58 fill:#1060b0 opacity:0.18 emissive:true at:(0,0,0)]
tunnel_view:
duration: 5
elements:
camera:
type: primitive
shape: camera[at:(0.5,-6.5,0.5) lookAt:(0,0,0) fov:62]
light:
type: primitive
shape: light[direction:(-0.4,0.8,0.3) intensity:1.05 ambient:0.28]
wh_outer:
type: primitive
shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0b1240 opacity:0.32 wireframe:true lineWidth:0.62 spin3d:(2,0,2) at:(0,0,0)]
wh_mid:
type: primitive
shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.5 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0f4fd4 opacity:0.62 wireframe:true lineWidth:0.88 spin3d:(2,0,2) at:(0,0,0)]
wh_core:
type: primitive
shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.28 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#1ea8ff opacity:0.92 wireframe:true lineWidth:1.08 spin3d:(2,0,2) at:(0,0,0)]
glow_hot:
type: primitive
shape: sphere[r:0.07 fill:#ffffff opacity:1.0 emissive:true at:(0,0,0)]
glow_warm:
type: primitive
shape: sphere[r:0.24 fill:#80d8ff opacity:0.50 emissive:true at:(0,0,0)]
glow_cool:
type: primitive
shape: sphere[r:0.58 fill:#1060b0 opacity:0.18 emissive:true at:(0,0,0)]

Video Markdown (VMD) — Created by Sohan Dananjaya

Explore and start writing →