Diorama guides
This page covers Diorama SVG. For textured miniature stories and four live finishes, read the Diorama Worlds guide; for dithered pixel-art miniatures, the Diorama Pixel guide.
Diorama SVG builds lightweight interactive figures in one offline HTML file: a dependency-free renderer, six themes, eighteen examples and browser checks. Worlds and Pixel use separate renderers and their own install commands.
Installation
npx skills add educlopez/diorama --skill diorama -gSelects only Diorama SVG. The CLI lets you choose target agents. Add -a claude-code to target Claude Code alone. Node.js 20 or later runs the scripts; Chrome or Chromium runs the look (Playwright's browser is found, or playwright-core is installed once).
git clone https://github.com/educlopez/diorama.git ~/.diorama && mkdir -p ~/.claude/skills && ln -s ~/.diorama/skills/diorama ~/.claude/skills/dioramaThe same, with git: the clone stays in ~/.diorama, git pull there updates it. Nothing is needed at runtime: a finished page draws itself as SVG and makes no network request.
Prompts
Type /diorama and describe the place. Say where it will live (a hero, a card, a small figure), name the place or the product, and give a colour if you have one. The agent offers two or three concepts, builds the one you pick, runs the validator and the look, and hands you diorama-<name>.html.
/diorama a hero for our logistics landing page: a yard with trucks docking/diorama an empty state for the inbox, small and quiet, line-drawing style/diorama a card for the docs: jobs wait in line, one runner takes them/diorama a harbour at dusk for the top of our README, brand colour #5b4ae0Sizes
The size sets the stage, the budget and the tempo.
| Size | Lives | Stage | Moves | Lines |
|---|---|---|---|---|
| hero | the top of a landing page or a README | 3:2, full width | three or four loops with different periods, one pointer answer | ≤ 420 |
| card | beside text in docs or a post | 4:3, about 500px | one process, one pointer answer | ≤ 240 |
| mini | a small spot: an empty state, a feature list, a sidebar, a 404, an icon-sized hero | 1:1, 160 to 240px | one quiet ambient motion | ≤ 150 |
Themes
A theme is a set of fields the kernel applies to every primitive; a figure never branches on it, so the same file draws five ways. Pick it from where the figure will live.
| Theme | Reads as | Keeps | Use it for |
|---|---|---|---|
| toy | pastel blocks, one warm sun, a cool fill, soft shadows | every colour | product pages, READMEs, anything warm |
| soft | the same on cream, gentler light, desaturated accents | every colour, shifted warm | editorial pages, paper backgrounds |
| mono | tints of ink on white | one accent | docs, product UIs with their own accent, text-heavy pages |
| line | a hairline drawing: white fills, ink edges, silhouettes, no shadows; by hand with wobble | one accent, as a stroke | minimal or technical pages, small quiet figures |
| night | dusk: dark base, cool sky, warm lamps and windows | every colour, darker | dark pages, hero sections on black |
| flat tones per face and ink lines, no thrown shadows: a printed illustration | every colour | editorial pages, docs that want a drawing |
theme: { base: 'mono', accent: '#f2c230', shadows: false }
theme: { base: 'toy', palette: { coral: '#5b4ae0', grass: '#d8ead0' } }
theme: { base: 'line', round: true, strokeWidth: 1.4 }
theme: { base: 'night', glow: 3, lights: { sun: ['#ffd6a0', .3] } }| Field | What it does |
|---|---|
| bg | the stage colour, or 'transparent' |
| palette | overrides of any palette entry: base, ground, grass, water, paper, ink, coral, mustard, teal, plum, … |
| accent | the subject's colour in every theme, and the one mono and line keep |
| lights | { hemi: [sky, ground, i], sun: [c, i], fill: [c, i] | null, ambient: [c, i] } |
| shadows, shadow | true or false, and how dark the thrown shadows are (0..1) |
| tones | [top, lit side, dark side]: three flat tones for every box face instead of the lights (print) |
| tone, tint | { paper, ink } makes any theme monochrome; tint 0..1 lets colour back in |
| edges, outline | ink lines on flat solids, silhouettes on round ones |
| stroke, strokeWidth | the line colour and width in css px |
| round | every box becomes a rounded solid |
| glow | a multiplier on every emissive |
| halo | 0..1, the soft shadow the base throws on the page |
| wobble | css px of hand tremor on the ink: 1.2 a pen, 2.5 a felt tip |
Minis
A mini is a small figure, 160 to 240px square: one object and one quiet motion. Use it wherever a page needs a small picture: an empty state, a feature in a list, a settings section, a 404, a sidebar. Its still frame is the picture and the ambient motion is the bonus. The canvas is transparent, so whatever sits behind it shows through. The words around it, a heading, a line, a button, are yours to write in your own UI.
The figure file
One Diorama.figure call: a declaration, a build that makes the scene once with the toolkit and returns the parts that move, and a tick that poses them from ctx.t. No timers, no new THREE.*, no words inside the scene.
Diorama.figure({
name: 'mailbox',
size: 'mini', // hero | card | mini
theme: 'toy', // or { base: 'line', accent: '#cf6a4c' }
means: 'An open letterbox with nothing inside. The flag lifts now and then.',
title: 'Mailbox',
still: 1, // the second reduced motion shows
build: function (o) {
var D = o.D, pal = o.pal;
D.slab(3, 3, pal.grass, 0, 0, 0, null, { h: .5, r: .45, side: pal.base });
var post = D.group(.15, 0, -.1);
D.cyl(.07, .09, 1.1, pal.wood, 0, .55, 0, post, 8);
var body = D.group(0, 1.15, 0, post);
D.box(.95, .5, .6, pal.coral, 0, .25, 0, body);
var flag = D.group(.1, .5, .32, body);
D.box(.26, .16, .03, pal.mustard, .1, .02, 0, flag);
D.props.tree(1, -.8, null, { r: .42 });
return { flag: flag };
},
tick: function (ctx) {
var D = ctx.D, t = ctx.t, check = D.smooth(D.pulse(t, 7, .36));
ctx.parts.flag.rotation.z = D.lerp(.2, 1.45, check);
}
});The toolkit: D.box, D.rbox, D.cyl, D.cone, D.ball, D.dome, D.wedge, D.slab, D.torus, D.wheel, D.label; the props tree, bush, rock, cloud, person, lamp, crate, pallet, cone, flag; for motion phase, pulse, wave, the eases, spring, apart, path, place, spin, stagger. The index at the top of kit/diorama.js lists them all.
Scripts
node ~/.claude/skills/diorama/scripts/build.mjs mailbox.jsWrites diorama-mailbox.html: the kernel, the figure and the bench page, one file that draws itself as SVG with nothing to load.
node ~/.claude/skills/diorama/scripts/validate.mjs diorama-mailbox.htmlTen checks: the declaration, the length, the clock, self-containment, the kernel, the words, the colours, the base, the pointer and the drawing (no renderer, no three.js in the page).
node ~/.claude/skills/diorama/scripts/look.mjs mailbox.js --theme lineOpens the page in headless Chromium and writes one sheet: the still frame, three moments, the pointer, the tilt, a press, the thumbnail and the page, plus the console, the fill, the motion, the answer and the press as numbers.
Embed
The built page can be served as is or framed. For a product, keep the kernel and the figure and drop the bench. The kernel sizes the canvas to the element’s width, watches resizes, sleeps offscreen and honours prefers-reduced-motion with one still frame. Remount to change the theme.
<div id="hero" style="width:100%"></div>
<script src="/diorama/kit/diorama.js"></script>
<script src="/diorama/island.js"></script>
<script>
var fig = Diorama.figures[Diorama.figures.length - 1];
var ctx = Diorama.mount(fig, { el: document.getElementById('hero'), theme: 'night' });
</script>"use client";
import { useEffect, useRef } from "react";
import Diorama from "./diorama.js";
import "./island.js"; // registers the figure
export function Hero({ theme = "toy" }) {
const el = useRef<HTMLDivElement>(null);
useEffect(() => {
const fig = Diorama.figures[Diorama.figures.length - 1];
const ctx = Diorama.mount(fig, { el: el.current!, theme });
return () => ctx.destroy();
}, [theme]);
return <div ref={el} style={{ width: "100%", aspectRatio: "3 / 2" }} aria-hidden />;
}Craft rules
Ten rules separate a diorama from a pile of cubes: the world stands on a rounded base with its zones inside the corners; the camera is true isometric, subject front-left; the palette is pale with two or three accents and one named accent; materials are matte blocks, no textures; every object is a few primitives with the two or three features that give it away; one sun and soft shadows under everything; all motion comes from the clock in tick; vehicles ride paths on a schedule with stops that have a cause; a few loops with different tempos and one rare thing; and no words inside the scene. The long form, with recipes, is in references/craft.md.
What is in the skill
| File | What it is |
|---|---|
| SKILL.md | the workflow: size and concept, build, check, hand over, adjust |
| kit/diorama.js | the kernel, no dependency: scene graph, isometric camera, the SVG painter (flat tones, thrown shadows, halo, ink), themes, primitives, props, paths, springs, the pointer, the loop |
| bench.html | the page a figure is built into; a theme switcher, accent swatches, a live read-out and a still-frame toggle |
| scripts/build.mjs | writes diorama-<name>.html: the bench, the kernel and the figure, nothing to load |
| scripts/validate.mjs | ten checks: declaration, length, clock, self-contained, kernel, words, colours, base, pointer, drawing |
| scripts/look.mjs | a sheet of nine pictures and the numbers, in headless Chromium; --theme for another theme, --press for what to tap |
| references/ | concepts, the ten craft rules and the themes, motion, the look, React |
| examples/ | eighteen figures across sizes and themes, the format to write |
npx skills add educlopez/diorama --skill diorama -g