About Juggle Perfect
Why I built it
I love juggling, and I love getting better at things. I know I'll never be perfect, but I want to keep moving in that direction. Perhaps Juggle Better would have been a perfect better name, but it doesn't sound as good, does it?
The original name for this game was actually Juggle Hero, inspired by Guitar Hero. I know Guitar Hero didn't really teach you to play guitar, but I love the idea of a game where the controller is something from real life rather than a keyboard: something that gets you in the zone and makes you feel like you're getting better.
I loved Guitar Hero when I was a kid. Hopefully Juggle Perfect is almost as fun, but actually does teach you to get better at juggling. Anyway, here's how to play…
How to play
1. Get ready
- Stand in good light, ideally in front of a plain wall.
- Prop your phone or laptop up so the camera can see you from your hands to above your head.
- Brightly coloured balls work best, in a colour that isn't in your clothes or the room.
2. Teach the game your ball (Colour)
Press Start. Hold your ball up and tap it in the camera picture. If your balls have panels in two colours, tap each colour, up to two.
3. Check it can see the ball (Tracking)
Throw a ball a few times. A trail should follow it. If it doesn't, go back and tap the ball again, or find better light. Press Looks good.
4. Place the rings (Confirm)
Drag the two rings to where your throws reach their highest point. In a cascade, your right hand throws to the left ring (L), and your left hand throws to the right ring (R). Set your speed in BPM (beats per minute): 160 is a throw about every third of a second. The game remembers all of this for next time.
5. Play
Pick how long to play (30, 60 or 120 seconds) and press Start. Juggle a three-ball cascade. A metronome ticks to help your rhythm, but you don't have to throw exactly on the tick: every good throw counts.
Score throws in a row to build a streak. The longer your streak, the more each throw is worth: ×2 from 5 in a row, ×3 from 10 and ×4 from 15. If no ball reaches a ring for a couple of beats, the streak ends.
6. See how you did
At the end you get your Perfects, Goods and best streak, plus a snapshot of every scored throw so you can see what happened.
How does it work?
The game has no sensors in the balls. It only has your camera, so it works everything out by looking, a bit like you watching a friend juggle. Every picture goes through the same steps:
Step 1: Shrink the picture
Each camera picture is squashed down to 320 × 240 dots. That's small, but a computer can check every single dot 60 times a second without getting out of breath.
Step 2: Find the balls
The game asks two questions about every dot:
- Is it ball-coloured?
- Did it change since the last picture? If so, something moved there.
Why ask about movement? Your orange T-shirt or an orange cushion could look ball-coloured, but they sit still. Flying balls don't. So something only counts as a ball if it's the right colour and moving.
🪄 Trick: colour by hue, not red-green-blue
Touching ball-coloured dots are gathered into one blob, like the paint-bucket tool in a drawing app. The middle of the blob is where the ball is.
🪄 Trick: the crescent moon
🪄 Trick: gluing blobs back together
Step 3: Give each ball a name tag
The game needs to know which ball is which from picture to picture. In each new picture, every blob gets the name of the nearest ball from last time. A blob with nobody close enough gets a brand-new name.
🪄 Trick: being patient
Step 4: Guess the top of the throw (the physics bit!)
Anything thrown through the air follows the same curve: a parabola, the shape of a rainbow. Gravity pulls on it the same way every time. So while the ball is still going up, the game finds the rainbow that best fits the dots it has seen so far (mathematicians call this a least-squares fit). Then it works out where the top of that rainbow will be.
The game scores your throw just after the ball passes the top, using the rainbow that fits its whole way up. It never needs a sharp picture of the top itself, so blurry pictures at the slow, fuzzy top of a throw don't spoil your score.
🪄 Trick: start the curve when the ball leaves your hand
🪄 Trick: no impossible answers
Step 5: Score it
- Only the top counts. The ball can go into the ring on its way up or on its way down. What matters is where the top of its curve is.
- One throw, one score. When a ring scores, it switches off until it's been empty for a moment.
🪄 Trick: not fooled by a lost ball
Step 6: Keep the beat
Juggling has a rhythm, like music. The beat drives the metronome and the pulsing rings, and it decides when your streak has gone quiet for too long. It never stops a good throw from scoring.
🪄 Trick: one clock, not two
Tricks for phones
- Mirror: the picture is flipped like a mirror, so your right hand is on the right.
- Any camera shape: the maths happens in one fixed-size world, then the rings are stretched to fit your camera, even a tall phone one. Squashing a tall picture into that world makes it shorter than it is wide, so distances are measured the way they look on your camera, and a ring scores exactly where it's drawn.
- One screen: the setup steps fit your phone without scrolling.
How was it made?
A web page
The whole game runs in your browser, so there is nothing to install. It is built with Next.js and React: kits for making pages out of reusable pieces, like Lego.
TypeScript
JavaScript with a spell-checker that spots mistakes before the game runs.
Canvas
A drawing board on the page. Each camera picture is copied onto it, then the rings, trails and “PERFECT!” are drawn on top.
The game loop
The browser taps the game on the shoulder about 60 times a second. Each tap it looks at the newest picture, finds the balls and checks the scores: a flip-book that thinks on every page.
Notebooks (Zustand)
Little stores that remember the score, the streak and your settings.
A browser pocket (localStorage)
Your settings are saved inside your own browser. No server, no account: nothing leaves your computer.
Robot testers
Vitest checks the small pieces, like the maths. Playwright opens a real browser and plays the whole game to check the right numbers come out.
Why build it this way?
There were lots of ready-made ways to build a game that watches you through a camera. Here are the big ones, and why we didn't pick them:
🏭 A game engine (like Unity or Godot)
Great for: A giant toy factory for making big 3D games, with physics, lighting and sound built in.
Why not here: Huge to download and slow to start in a web browser. We only need to draw some rings and flashes, which a web page already does.
🤖 A trained AI (like MediaPipe or TensorFlow)
Great for: A robot brain that has studied millions of photos, so it can spot hands, faces and bodies.
Why not here: Nobody has taught it about juggling balls, so we would have to train it ourselves. It is also heavier to run, which can make an older laptop slow and jumpy.
🕶️ An augmented reality kit (like ARKit, ARCore or WebXR)
Great for: Great at sticking pretend things onto the real world, like a dinosaur standing on your carpet.
Why not here: It is made for placing things in 3D, not for chasing small, fast balls, and mostly needs a phone app.
🧰 A giant vision toolbox (like OpenCV)
Great for: Hundreds of ready-made tools for working with pictures.
Why not here: We only need a handful of them, and carrying the whole toolbox makes the game slower to load.
Instead, we built our own, from the simple ideas on this page: look for the ball's colour, check it's moving, give it a name tag and draw a rainbow through its path. Why?
- Keep it simple. A juggling ball is just a brightly coloured thing that moves. You don't need a robot brain to find it, just a careful look at the colours.
- Keep it fast. Checking a small picture's dots is quick work for a computer, so the game keeps up with your throws, even on an ordinary laptop, and the page opens straight away.
- Learn how it really works. Using something ready-made is like buying a cake. Building it yourself is like baking one: you find out what's inside. Making it by hand meant learning about colours, movement, tracking and the physics of a throw.
- Have a go! Nobody was sure plain JavaScript could do it. The only way to find out was to try.
🪄 Trick: it did work, with some help
The robot clown
Waving balls in front of a webcam every time the code changes gets tiring. So the game can pretend to be a camera: a cartoon clown in a top hat juggles for it. The game can't tell he isn't real, so every part gets tested. Because we know exactly where his balls go, we can check the game gets the right answer. That's how most of its bugs were found.
- 😀 Happy clown throws perfectly, 😢 sad clown always misses and 🤪 crazy clown throws a random mix.
- His colours are all far from orange on the colour wheel, so the game never mistakes him for a ball.
- He can make his balls vanish at the top of a throw, like a real camera losing them, or use shaded, two-colour balls like real ones. A perfect pretend world only finds perfect-world bugs; a slightly messy one found real ones.
Try him yourself: open the game with the clown.