Authoring an animal¶
So you want to make a companion animal — one of the little creatures that lives on your map and follows the robot — to keep, or to share in the animal gallery. This page is about the descriptor format and making the result good.
Using animals — picking a companion per vacuum, the Rainbow Bridge group, the battery-state eyes — is covered in the user guide. This page is about building a new one.
For what a good animal looks like — a readable silhouette, the six distinct poses, a consistent stroke hierarchy, a warning pose that actually warns — see the craft standards in Mascot authoring. They're written against the bundled animals but apply to descriptor animals too.
Two rules to know up front¶
- It's declarative — never code. An animal you submit is a JSON
descriptor: colour tokens plus SVG path data. There is no JavaScript and
no procedural renderer (
type: "custom"is maintainer-only). The intake sanitises every SVG part, then generates the runtime module from your validated data — so a submission can never run code on anyone's dashboard. - Baked colours ⟹ a tribute. If you bake your colours in as literal
hsl(...)values (so the animal always looks exactly like one specific creature — a real pet, say — and only the eye stays dynamic), it must set"memorial": trueand it joins the 🌈 Rainbow Bridge group. Otherwise, expose a themeable--animal-*colour block so it recolours in the theme editor like a regular companion. (A baked animal with no memorial flag is rejected — "make it themeable, or tell us whose it is.")
The descriptor¶
A complete worked example is the Fox, at gallery/animals/fox.json. The shape:
{
"id": "fennec",
"name": "Fennec Fox",
"type": "quadruped",
"license": "CC-BY-4.0",
"colors": { "--animal-eye": "40 30% 20%", "--animal-fur": "30 60% 70%" },
"parts": { "body": "<path .../>", "head": "...", "...": "..." },
"memorial": false,
"author": "Your name",
"author_url": "https://...",
"submitted_by": "Your name",
"description": "A desert fox companion.",
"tags": ["fox", "desert"]
}
id—^[a-z][a-z0-9-]{1,30}$, unique, not one of the built-ins.type—quadrupedorparrot.license— required:CC0-1.0,CC-BY-4.0,MIT, orApache-2.0.author_url— optional, a directhttp(s)link only (no shorteners).description— optional, ≤ 280 characters.
You can submit the bare animal object (above) or wrap it in an envelope
({ "version": 1, "kind": "animal", "animal": { ... } }) — the intake accepts both.
Already have a .js animal?¶
If you wrote one as a module (the old format — const colour aliases and
backtick parts with ${...} interpolation, like the bundled
raccoon.js), don't hand-translate it. The converter runs your module to
capture the definition with every ${FUR} interpolation already resolved,
tidies the SVG (and switches attributes to single quotes), and writes the
descriptor for you:
Then add author/description if you like and validate it like any other
descriptor (below). Procedural (type: "custom") modules can't be converted —
they're code, not data.
Prefer to draw one SVG?¶
You can author the whole animal as a single SVG and let the tool split it — no hand-writing JSON-with-embedded-SVG. The contract (this is what makes it work, so follow it):
- Tag each part with
data-slot—<g data-slot="head">…</g>,<g data-slot="frontLeftLeg">…</g>, etc. (slot names are under Parts). The tool splits on these; the wrapper is dropped. - You place the leg-animation class on the lower-leg subgroup yourself —
<g data-slot="frontLeftLeg"><g class="cat-fl-lower" style="transform-origin: 170px 236px">…</g></g>(the tool won't add it — see Make the legs animate). - Use
hsl(var(--animal-X))for any colour you want themeable.
Then split + sanitise it into a descriptor (real-browser parse; needs npm ci):
node scripts/svg-to-descriptor.mjs your-animal.svg --id fox2 --name "Fox 2" --license CC0-1.0 -o fox2.json
It hands back the safe version — every slot run through the same sanitiser
the intake uses, plus a report of anything stripped — and scaffolds a colors
block from the tokens it found (fill in the real default HSL values; the SVG
carries the token name, not its default). Validate with build-animal.mjs as
above. An untagged SVG is refused — the tool can't guess which path is a leg.
Colours¶
Every key you declare in colors becomes a themeable token: the framework
wraps it so a theme can override it per-animal or globally, and the theme editor
lists exactly the tokens you declared. Values are bare HSL triples —
"H S% L%" (e.g. "142 71% 45%"), no hsl(...) wrapper, no ;.
- In your
parts, reference a token asfill="hsl(var(--animal-fur))". - Always declare
--animal-eye. The eye is recoloured by battery state (green when full, red when low, blue while charging), so it must stay on the token. Forquadrupedandparrotanimals the framework auto-tags the eye group withclass="animal-eyes"(so the charging pulse works automatically) — you don't add that class yourself, and the Fox doesn't. - A baked animal declares only
--animal-eyeand hard-codes the rest as literalhsl(...)in the parts (the Mittens pattern) — and so must bememorial.
Parts (the anatomy)¶
parts is one SVG string per anatomical slot. The drawing space is a
viewBox="-10 -10 500 340" — the easiest start is to trace over an existing
quadruped (cat.js or the Fox), keeping the same coordinate anchors so the
poses line up.
Quadruped (cat / dog / fox): body, frontLeftLeg, frontRightLeg,
backLeftLeg, backRightLeg, tail, head, eyes, face — all required —
plus optional warning (a spooked overlay) and extra.
Parrot: body, frontLeftLeg, frontRightLeg, tail, head, eyes,
face (no hind legs) plus optional warning / extra.
From your drawing to the JSON¶
Once you have the SVG, turning it into the descriptor is mechanical:
- Split it into the slots. Each anatomical group above becomes one entry in
parts— just the markup that draws that piece (a<g>, some<path>s). Leave out the outer<svg>wrapper; the framework supplies it. - Use single quotes for attributes. JSON strings are double-quoted, and so
is SVG by convention — so pasting
<path d="…" fill="…"/>into JSON means escaping every quote as\". Instead write your SVG with single-quoted attributes —<path d='…' fill='…'/>— and the JSON needs no escaping at all. SVG accepts single quotes, and the sanitiser normalises everything back to double quotes on the way out. (The Fox's source is written exactly this way.) - Swap literal colours for tokens. Replace each
fill='#e0742a'withfill='hsl(var(--animal-fur))'and declare--animal-furincolors— now it's themeable. (Or keep it a literalhsl(...)and make the animal a memorial — see the rule up top.)
So one slot ends up looking like:
Check it before you submit¶
Compile your descriptor locally — it validates, sanitises, and generates the
module — then render every pose to eyeball it (both use a headless browser, so
run npm ci first):
node scripts/build-animal.mjs path/to/your-animal.json # validate + sanitise + codegen
node scripts/preview-animal.mjs <id> # 6-pose contact sheet
build-animal refuses anything the contract gate or sanitiser rejects and tells
you exactly what to fix — the same checks the intake runs.
Make the legs animate¶
The framework's knee-bend animations (for the curled, walking, and
animating poses) are wired to a fixed set of class names. So wrap each
lower-leg group in the matching class and give it a transform-origin at the
knee:
Use cat-fl-lower / cat-fr-lower / cat-bl-lower / cat-br-lower (front/back,
left/right). Reusing the cat- namespace is correct and intentional — only one
animal renders per element, so there's no collision, and the knee-fold / walk
cycle fire for free with no framework change. (The Fox and Mittens both do this.)
What the sanitiser allows¶
Your SVG is run through DOMPurify in a real browser at intake. Allowed:
geometry (path, circle, ellipse, rect, line, polyline, polygon,
g), gradients/clips (defs, linearGradient, radialGradient, stop,
clipPath, use), accessibility (title, desc), and presentation attributes
(fill, stroke, opacity, transform, etc.). style is clamped to transform-origin (plus a few). href
must be an internal #fragment (e.g. fill="url(#grad)"). Stripped or
rejected: <script>, <foreignObject>, <image>, event handlers
(onload=...), external references, javascript: / data: URIs, and any
non-allowlisted class. Keep your art to those primitives and nothing is removed.
Licence¶
An SVG drawing is copyrightable art, so a license is required. Only submit
art you have the right to share under the licence you name.
Submitting¶
Two paths, both end in a maintainer-reviewed pull request:
- Issue form (easiest): open a new animal submission (Issues, "Animal submission" template) and paste your descriptor. A bot validates it, sanitises the SVG, renders a 6-pose preview, and opens the PR for you.
- Fork PR: add
gallery/animals/<id>.jsonand runnode scripts/build-animal.mjs gallery/animals/<id>.jsonto generate the module, then open a PR. Theanimal PR checkgate validates it the same way.
Either way, once merged your animal appears in the gallery and the in-card companion picker — themeable, in every pose, with no edit to the framework.