pubnyanlive

The Hackers' Pub mascot, in motion

A little curiosity.
A whole universe.

pubnyan is a calm cat with a star for a mouth and a ring around its neck. Every performance is written once, in a small motion language, and exported to animated SVG, Lottie, Rive and video that all agree with each other.

Clips
22
Targets
4
Expressions
5
Definition
one
pubnyan, listening
dist/svg/idle.svg · animated SVG in an <img>

Interactive

Give pubnyan a thought.

One Rive file, one state machine. Pick a sustained expression, then fire a reaction on top of it: ordinary reactions keep the emotion underneath, and a celebration takes over the whole face for a moment, then hands it back.

Loading pubnyan.riv…

Expression sustained

Listening. An ear catches a sound, a glance, a lean.

Reaction one shot

Click the cat for a wink. Number keys work while the stage is on screen.

Try this

Choose shy, then Nod: the nod plays and the shyness stays. Then Celebrate.

One performance, several stages

The same wink, in every format.

Each target is rendered in headless Chrome and diffed against the reference sampler before it ships, so what you embed is what was authored.

wink · 0.95s · 60fps

Animated SVG

Self-contained CSS keyframes. Works in an <img>, a CSS background, or inline. Shapes morph with d: path() (Chromium, Firefox 97+); everything else crossfades and is universal.

<img src="idle.svg" alt="pubnyan" width="406" height="351">

Every clip in every format is in the Storybook galleries and in dist/.

How it works

Written once. Checked everywhere.

  1. 01

    Rig

    The original artwork is a git submodule. parts.map.json names its paths (head, ears, eyes, ring) and rig:extract turns them into a rig with pivots.

  2. 02

    Clip

    A clip is a list of tracks: a part, a property, and keys with easings. Amplitudes stay small. pubnyan is a calm cat.

  3. 03

    Export

    The same intermediate representation is sampled into CSS keyframes, Lottie layers, a Rive artboard with a state machine, and video frames.

  4. 04

    Verify

    Every target is rendered in headless Chrome and pixel-diffed against the reference sampler. The contact sheet is judged by eye as well.

motion/clips/wink.clip.ts
export default clip('wink', { rig: 'pubnyan', duration: 0.95, fps: 60, loop: false }, [
  ...['eye-r.white', 'eye-r.pupil'].map((part) => track(part, 'scale', [
    key(0, [1, 1]), key(0.13, [1, 1]), key(0.21, [1, 0.08], 'easeIn'),
    key(0.39, [1, 0.08]), key(0.57, [1, 1], 'outCubic'), key(0.95, [1, 1]),
  ])),
  track('head', 'rotation', [
    key(0, 0), key(0.1, 0.9, 'inOutCubic'), key(0.25, -4, 'outCubic'),
    key(0.43, -4), key(0.69, 0.55, 'inOutCubic'), key(0.95, 0, 'inOutSine'),
  ]),
  track('ear-r', 'rotation', [
    key(0, 0), key(0.18, 0), key(0.32, -4.5, 'outCubic'),
    key(0.49, 1.2, 'inOutCubic'), key(0.66, -0.4, 'inOutSine'), key(0.95, 0, 'inOutSine'),
  ]),
  ...orbit('rotation', [key(0, 0), key(0.2, 0), key(0.42, 1.5, 'outCubic'), key(0.95, 0, 'inOutSine')]),
]);