From Text to Canvas: The Generative CSS Animation Pipeline
How MooduTools turns the raw text emitted by an on-device LLM into living, interactive HTML5/CSS animations — without a canvas API, without WebGL, and without trusting anything the model says.
1. Overview
The companion architecture document described where MooduTools runs. This document describes what it makes. When a user types a mood, an on-device model returns a block of raw text. That text is meant to be a self-contained fragment of HTML and CSS: a little stage of divs, gradients, and @keyframes that produces a moving, expressive picture. The interesting engineering lives in how that text is produced responsibly, parsed safely, parameterized by emotion, and rendered at 60 frames per second — all inside a single origin with no external assets.
Two constraints shape everything. First, the output must be safe: we are injecting code that originated from a generative model, so it is treated as untrusted until proven inert. Second, the output must be expressive: because we aim for emotional resonance rather than data visualization, the pipeline speaks in motion, easing, light, and rhythm rather than axes and labels.
2. Constraining the Model Before It Speaks
The safest code is code the model is asked to produce under a strict contract. The system prompt is a deterministic specification, not a wish. It tells the model that it is a frontend developer and storyteller, that it must return only a raw HTML fragment with no markdown fences, no explanation, and no commentary, and that the fragment must be a single self-contained scene with inline CSS. Tightening the prompt at the source reduces the burden on the sanitizer downstream.
const SYSTEM_PROMPT =
'You are an expert creative frontend developer and storyteller... ' +
'Output ONLY the raw executable HTML/CSS. ' +
'No markdown, no code fences, no explanations, no commentary.';
// The user mood is appended at request time:
`User's mood: "${mood}". Generate the art for this mood.`
A secondary, equally important part of the contract is determinism of shape. The model is expected to return meaningful text to author a micro-story, but the visual result must always be expressible as plain CSS keyframes — no canvas rasterization, no WebGL shaders, no external fonts or images. This constraint is what keeps the whole pipeline offline and dependency-free, and it is what lets a single static page render the model's imagination using only the compositor thread.
3. Parsing & Sanitization
Once the text arrives, the pipeline performs a defensive parse. A naive approach would inject the string straight into innerHTML; a competent one treats the model as an attacker.
function cleanOutput(raw) {
if (typeof raw !== 'string') return '';
let out = raw;
// 1) Strip fenced code blocks and stray backticks.
out = out.replace(/```[\s\S]*?```/g, '').replace(/```/gi, '');
// 2) Hard-remove any script element, no exceptions.
out = out.replace(/