← crafts

Yuzu

a shy mascot that hides on click and peeks back in.

Yuzu is the little mascot on capy.ai. It lives on the right edge of the screen and acts like a small shy animal. It peeks in when the page loads, blinks, leans toward your cursor, squishes when you press it, and bolts when you click.

the slide in

Think about peeking around a corner to check if someone's there. You lean out quick, hold while you scan the room, then step out once it feels safe. Never one speed. Yuzu enters the same way.

Most animations on the web move in one smooth glide: start, slow down, stop. That reads as an object, not a character. So yuzu runs on a custom speed curve:

CustomEase.create("peekIn", "0.165, 0.84, 0.82, 0.78");

gsap.fromTo(
  el,
  { xPercent: 120 },
  { xPercent: 0, duration: 1.4, ease: "peekIn" },
);

The tail of the curve is bent so the speed never reaches zero. Yuzu inches out for most of the 1.4 seconds, then pushes the last bit and arrives still moving.

That leftover motion needs somewhere to go. Cartoons solved this too: soft things don't just stop, they squash and stretch. So the arrival spills into a wobble:

keyframes: [
  { scaleX: 1.15, duration: 0.12 },
  { scaleX: 0.95, duration: 0.1 },
  { scaleX: 1, duration: 0.15, ease: "easeOutQuad" },
];

Stretch past normal, pull back, settle. That wobble is the "hey, i'm here."

peekIn: creeps out, then commits
a normal glide: no hesitation

the blink

A toy blinks on a timer. A pet blinks whenever it feels like it. The difference is randomness, so the wait between blinks is a dice roll:

const scheduleBlink = () => {
  gsap.delayedCall(gsap.utils.random(3, 7), blink);
};

The blink itself is just the eyes squeezed to a line and back, 0.08 seconds each way. Fast enough that you feel it more than see it.

Hover over yuzu and it blinks once and leans toward you a little, like a pet noticing your hand before you pet it:

gsap.to(el, { xPercent: 12, duration: 0.3, ease: "easeOutQuart" });
blink and lean

the squish

Press a gummy bear against a table. It doesn't shrink. It flattens where you push and bulges where you don't. To get that on screen, one decision matters before any animation: which part of yuzu stays pinned when it changes shape.

gsap.set(el, { transformOrigin: "center right" });

Yuzu's right edge is glued to the screen, so that edge stays pinned. Now pressing it squishes it against its own wall:

gsap.to(el, { scaleX: 0.87, scaleY: 1.08, duration: 0.15 });

Narrower and a little taller. Squeezed, not shrunk. The same pinned edge is why the entry wobble stretches out of the wall instead of ballooning in place.

pinned to the wall: squishes against it (slowed down)
pinned at the center: shrinks in place (slowed down)

the hide and come back

Startle a cat and watch the timing. The escape is instant. The hiding takes as long as it takes. The return is slow and careful. Fear is fast, curiosity is slow.

That's the whole design of the click:

returnTl
  .to(el, { scaleX: 1, scaleY: 1, xPercent: 120, duration: 0.3 }) // bolt, fast
  .to(el, { xPercent: 120, duration: 0.8 }) // hide, do nothing
  .to(el, { xPercent: 0, duration: 1.4, ease: "peekIn" }); // creep back out

0.3 seconds to bolt. Then 0.8 seconds of nothing, which is yuzu deciding whether you can be trusted. Then the same slow peek-in from the start, wobble included.

The pause is the part people feel. Animating nothing for 0.8 seconds is still animating.

bolts, hides, creeps back
no pause, straight back

No springs, no physics, nothing fancy. A few speed curves, a few timed steps, and one rule from every cartoon ever made: leave fast, come back slow.