Animos
A browser motion editor: 3D templates, real keyframes, direct manipulation, and video export — with no backend and nothing leaving the machine.
Overview
Animos turns a screenshot or screen recording into a looping product showcase: pick one of twelve 3D templates, drop media into it, adjust a small set of opinionated controls or manipulate the scene directly, then export an MP4 or WebM. It runs entirely in the tab. There is no account, no database, and no upload — projects and media live in IndexedDB and never leave the machine.
Problem
Motion tools split into two unhelpful halves: heavyweight native apps with a week-long learning curve, and CSS keyframe generators that can't produce a 3D device mockup. The gap is a tool where direct manipulation and keyframes are the same system — where dragging a gizmo and typing a number and setting a keyframe all write to one animation model instead of three parallel ones.
One document, three projections
Project document
Preview stage
Timeline
Export canvas
evaluateSceneAtTime clones the base scene and writes evaluated track values onto dotted property paths. Anything without a track keeps its static value, so a template only authors what actually moves.
Technical decisions
MediaRecorder + canvas.captureStream for export
Export reuses the exact render pipeline the preview uses, parameterised to a fixed resolution with device-pixel-ratio pinned to 1 for deterministic frame size. What you previewed is what gets recorded, and progress moves through an explicit rendering → encoding → finalizing state machine with a working cancel path.
WebCodecs or ffmpeg.wasm, which buy offline-speed encoding at the cost of a second rendering path that can drift from the preview. The tradeoff is real: an N-second clip takes about N seconds to export.
One entry point for every property edit
applyPropertyEdit(project, {targetId, property, value, autoKey, currentTime})is what the numeric fields, the opacity slider, the on-canvas gizmo, and arrow-key nudging all call. With Auto Key off it writes the static base value; with Auto Key on it upserts a keyframe at the playhead. There is no second animation system to keep in sync.A three-call gesture protocol for drags
beginLiveEdit()snapshots,liveMutate()updates without touching history,commitLiveEdit()pushes exactly one undo entry. A slider drag across 200 pixels is one Undo, not 200. Scrubbing the timeline bypasses history entirely.Separate IndexedDB databases, not separate stores
idb-keyvalonly creates an object store during that database's upgrade event, so reusing one database name for several stores silently keeps the first and loses the rest. Projects, asset metadata, and asset blobs each get their own database name. This surfaced as aNotFoundErrorduring browser-driven testing and is now written down in the architecture doc.Store asset blobs as ArrayBuffer, not Blob
Structured-cloning a
Blobinto IndexedDB rejects with a barenullin WebKit — no message, no stack. Found during cross-browser testing; the fix is to persist{ buffer, type }and reconstruct.
Things that turned out to be harder than they look
- Aspect-ratio duplication is additive, not a constraint solver: adaptation is baseline(newRatio) + (current − baseline(oldRatio)) per property, with baselines always re-derived from the template rather than stored
- Templates author against placeholder layer ids resolved at instantiation, because real ids are generated per project — this keeps template definitions declarative instead of imperative id-plumbing
- The transform gizmo and React-controlled props would fight, so
onObjectChangediffs per leaf property and writes only what actually moved - Audio is decoded at import, so an undecodable file fails when you add it rather than when you press play
- The AI editing layer emits ordinary editor state — real keyframes, real easing, real automation — so a single Undo reverses an entire natural-language request and there is no hidden AI state to reconcile
Results
- 33
- 59
- 12
- 0
Lesson
An editor is a state-management problem wearing a UI costume. Getting the document model right — one store, one evaluator, one edit entry point — made every feature after it cheaper, and made cross-browser bugs findable instead of mysterious.