Inspiration

What it does# ReFramed

Inspiration

A stupid bet with a friend about who could nail the Vitruvian Man pose. We spent 20 minutes taking bad photos of each other, and I thought — why isn't this a game? Claude can actually look at two photos and tell you whose arms are at the wrong angle. That's the whole idea.

How I Built It

Next.js + Supabase + Claude. Players share a 4-letter room code, get 5 seconds to study a reference pose, 15 seconds to recreate it on camera, then Claude scores both attempts and roasts whoever flopped.

Every game state (lobby, preview, capture, scoring, results) maps to a single column in the database. When the host advances the state, Supabase Realtime pushes the change to both phones and they redirect themselves — no peer-to-peer, no sockets to manage manually.

Countdown timers are synced from a server timestamp so both phones show the same number regardless of when they loaded:

$$t_{\text{remaining}} = \max\left(0,\ T - \left\lfloor \frac{t_{\text{now}} - t_{\text{start}}}{1000} \right\rfloor\right)$$

The scoring prompt weights body geometry only — silhouette (20 pts), arms (25 pts), legs (25 pts), torso (20 pts), head/neck (10 pts). Clothing, background, and lighting are explicitly ignored.

Challenges

Mobile WebSockets die. iOS Safari kills long-lived connections when the screen locks. Added a polling fallback alongside Realtime — inelegant but it works.

Claude returns malformed JSON. Under tight token budgets it occasionally wraps output in markdown fences or truncates. Fixed with a fence-stripping parser and two automatic retries before a neutral fallback.

The mirror problem. Front cameras show a mirrored preview by convention, but the actual captured image isn't mirrored. The canvas capture re-applies scale-x: -1 before encoding so the saved photo matches what the user saw — otherwise left and right arms are swapped in the comparison.

What I Learned

The prompt is the product. "Compare these poses" gives inconsistent garbage. Explicit criteria with score bands and a JSON schema gives something that actually feels fair. The model is capable — you have to tell it exactly what to measure.

Built With

Share this project:

Updates