A Luzir project

Turn any string
into a living face.

Deterministic SVG avatars, with a motion engine for when they need to move.

Empty seed shows a curated set of shapes below. Type one to see it everywhere on this page.

npm install @luzir/bolota

Determinism

Same seed. Same bytes. Every time.

Three renders. Byte-identical.

Faces

Expressions

17 faces, one held pose at a time.

Wander

Specimens

States

Every state, live, looping, side by side.

Idle
Wander
Thinking
Wink
Wide
Alert
Notify
Exclaim
Snooze
Play
Orbit
Swirl
Burst
Comet

Sequences

Four signature moves, one stage, looping on its own.

Idle

Reference

The whole surface

Six imports. Nothing else to learn.

bolota — static render
import { bolota } from "@luzir/bolota";

// deterministic: same seed, same bytes, always
const svg = bolota("any-string", { size: 64 });
container.innerHTML = svg;
parts — framework adapter
import { parts } from "@luzir/bolota";

// { cls, bg, inner, vars }: build your own element,
// wire `inner` through an innerHTML-style sink
const { cls, inner, vars } = parts("any-string", opts);
mountEngine — bloub engine
import { mountEngine } from "@luzir/bolota/engine";

const handle = mountEngine(svgEl, "any-string");
// starts on "idle"; handle.destroy() unmounts
handle — play / loop / stop / follow / setExpression
handle.play("burst");       // one-shot, auto-returns to idle
handle.loop("orbit");       // sugar for play(id, { loop: true })
handle.stop();               // freezes the current frame
handle.follow("window");    // eyes track the pointer; false disables
handle.setExpression("wander");
expressions
handle.expressions;            // every id, stable order
handle.setExpression("wander"); // held pose, only while idle/wander/swirl
handle.setExpression(null);      // clears back to the playing state
sequences
import { runSequence } from "@luzir/bolota/sequences";

// "entrance" | "burst" | "orbit" | "comet"
runSequence(handle, "entrance");