Skip to content
Corey Fedde
← All work

Creative tool · SVG systems

Totem

A character builder where independent SVG parts compose, recolor, share, and export without drifting apart.

Role
Product engineer
Stack
React 19 · TypeScript · Zustand · SVG · Vitest
Totem character builder with a lantern-carrying wooden character on the canvas and body-part controls in the editor
A character builder with no prompt box and no mystery meat: every visible piece is authored, interchangeable, and exportable.
26 parts
validated before they reach the browser
120
undo snapshots, because regret scales
2 formats
exported from one composition
0 servers
needed to build, save, or share

01

The problem

Character builders look easy right up until one arm floats three pixels off a new body, two gradients share an ID, and the exported SVG forgets every color it had in the browser. The usual fix is a growing pile of per-part exceptions. That pile eventually becomes the product.

Totem takes the opposite bet: artwork should enter through a strict contract, then compose predictably at runtime. The goal was a genuinely useful creative tool—parts, palettes, stats, local saves, durable links, and clean exports—without a server quietly doing the difficult bits offstage.

02

Design

The canvas owns most of the screen, because the character is the point. Editing lives in one compact rail, while undo, randomize, share, save, and export stay in reach above the work instead of hiding behind a kebab menu wearing a tiny fake moustache.

Parts, rules, and color all use ordinary tabs, radios, and text inputs. The interface remains fast to scan and fully keyboard-operable, while the decorative grid, glow, and orbit marks give the builder enough occult machinery to feel like a toy rather than an asset-management form.

Totem editor showing six preset palettes and four custom color controls beside a randomized character
Six presets get a character somewhere interesting quickly; four token controls handle the inevitable ‘same, but swampier.’
Totem stats editor showing constrained Might, Grit, Mind, and Spirit choices beside the character canvas
The points budget disables impossible choices in place. The interface says no before the data model has to say absolutely not.

03

Architecture

  1. 01Authored SVG
  2. 02Build-time contract
  3. 03Typed manifest
  4. 04Canvas, URL, and export

Each loose SVG passes through a compiler that rejects hardcoded paint, unsafe features, bad coordinate spaces, missing anchors, and dangling references. It resolves anchor coordinates, strips authoring markers, namespaces every identifier, and emits a typed manifest. The browser receives finished geometry instead of a small archaeology assignment.

Bodies and limbs agree on named attachment points; held items chain through the selected hand. Recoloring uses four CSS custom properties with derived light and dark tokens, and export resolves those same tokens to literal values. One deterministic character configuration therefore drives the canvas, undo history, local gallery, compact share URL, standalone SVG, and transparent PNG.

04

Interesting decisions

  1. 01

    Make bad art fail the build

    An SVG can look correct alone while carrying hardcoded colors, duplicate IDs, or anchors that break only after composition.

    Choice
    Compile every part through a contract that validates, normalizes, namespaces, and resolves its geometry before shipping.
    Tradeoff
    Asset authors follow stricter rules, but the runtime never has to improvise around malformed input.
  2. 02

    Compose by agreement, not exception

    A matrix of body-specific offsets grows combinatorially and makes every new part a regression tour.

    Choice
    Connect parts through named anchors and pure transforms, mirroring the authored right limb for the left side.
    Tradeoff
    The authoring contract is less forgiving; in return, every valid arm fits every valid body without a bespoke nudge.
  3. 03

    Keep shared characters boringly durable

    Encoding array positions makes an old share link silently change character when the part list is reordered.

    Choice
    Use a versioned URL codec and an append-only index registry where removed parts leave tombstones.
    Tradeoff
    The registry never becomes perfectly tidy, which is cheaper than explaining why someone’s wizard became a haunted turnip.
  4. 04

    Pan and zoom outside React

    Pointer moves can arrive faster than a component tree should reasonably re-render.

    Choice
    Write one SVG wrapper transform per animation frame and keep React subscribed only to the scale readout.
    Tradeoff
    Viewport state lives beside the component model, but dragging and pinch zoom stay smooth and character edits remain ordinary React state.

05

Accessibility

  • Every part, palette, and attribute choice is exposed through native tabs, radio groups, buttons, and labelled inputs; the picture is not the only source of truth.
  • The complete editor works by keyboard, focus stays visible against the dark shell, and disabled stat choices communicate the budget before submission.
  • Zoom has named controls and a reset readout, while undo, redo, randomize, sharing, saving, and every export size have explicit accessible names.
  • Reduced-motion preferences collapse nonessential transitions; the character, controls, and feedback do not depend on animation appearing at all.

06

What I'd do differently

The strongest decision was making the part contract a product feature rather than a build chore. Once the assets agree on anchors, paint, IDs, and stable indices, the canvas, sharing, and export paths become pleasantly dull—and pleasantly dull systems are the ones that survive more inventory.

A next pass would add a visual-regression matrix over representative part combinations and turn the tiny part library into a documented contributor workflow. I would also add an export preview for people who want reassurance before downloading four sizes of the same tiny forest bureaucrat.

Have a similar project?

Let’s make the complicated part feel simple.

Start a conversation