Web component, zero dependencies

A tiny friend that watches your page.

peekpal follows the cursor, reacts when you poke it, naps when nobody is around and looks away when you type a password. One tag, in plain HTML, React, Vue or Svelte.

$ npm i peekpal

Move your mouse around. Poke Mochi the cat. Then leave it alone for 15 seconds.

Playground

Every button calls the real API. Set the nap timer to 3 seconds and keep still to watch it doze off.

Click it four times, fast.

Moods
Nap after
Gaze

// poke it, or press a button

  • events show up here

Make your own

Any character works, from your own drawings. Read the drawing guide or start from the image prompt.

  1. 1

    Draw 18 frames

    Nine directions and nine moods, same canvas, same body, transparent background. Only the head and face change. Hand-drawn or generated, both work.

  2. 2

    Build the sheets

    One Python script with Pillow. It checks every frame, crops them all to one shared box so nothing jumps, and tells you if the feet drift.

  3. 3

    Point the tag at them

    Two webp files, one tag. No canvas, no WebGL, no API key.

The 18 frames of Mochi the cat: nine looking directions on top, nine moods below
Top row: up-left to down-right. Bottom row: blink, happy, love, surprised, wink, shy, sleepy, dizzy, celebrate.
terminal
# a generated sheet? slice it into frames first
python tools/slice-sheet.py art/mochi --composite sheet.png

# 18 PNGs in, two sprite sheets out
python tools/build-sheets.py art/mochi --out public/pals
cat-poses.webp sprite sheet
cat-poses.webp
cat-moods.webp sprite sheet
cat-moods.webp

API

AttributeDefaultWhat it does
posesrequiredURL of the poses sheet
moodsrequiredURL of the moods sheet
size140Width and height in px
label"mascot"Used in the accessible name, "Poke the {label}"
sleep-after15000ms without activity before it naps. 0 never naps
watch-fieldsonSet to "off" to stop it looking at focused inputs
celebrate-on-submitonSet to "off" to stop it celebrating form submits
methods and events
const pal = document.querySelector('peek-pal')

pal.react('love')          // any mood, 600 ms by default
pal.lookAt(x, y)           // page coordinates
pal.lookAt(null)           // back to the cursor

pal.addEventListener('peekpal:poke', (e) => e.detail.count)
pal.addEventListener('peekpal:sleep', nap)
pal.addEventListener('peekpal:wake', hello)
react / next.js
import { PeekPal } from 'peekpal/react'

<PeekPal
  poses="/pals/cat-poses.webp"
  moods="/pals/cat-moods.webp"
  size={140}
/>

On its own it also watches the page: it looks at the field you are typing in, turns away from password fields and celebrates when a form is submitted. Touch screens get a still, centred pal that still answers taps, and reduced motion turns off the bounce and the breathing.