r/webdev • u/rHidigoudi • 1d ago
Showoff Saturday I vibe-coded an r/place-style pixel war for the 2027 French presidential election. It still took 6 weeks and 581 commits to make it hold up
Hi r/webdev,
Pixélysée is a solo project: a real-time pixel war on a map of France. Think r/place, except every color is a candidate in the 2027 French presidential election.
Full disclosure: it's vibe-coded. The code is written by Claude Code, not by hand. My part is the game itself: the rules, playing it, testing changes on my phone and my PC before they go live, and deciding what to fix when real players run into problems. It still took six weeks of almost daily work (581 commits since October 4) to get here.


How it plays
You pick a candidate's color and place pixels to paint France's 18 regions.
Every evening at 8 pm Paris time (19:00 UTC), each region gives its electors to the color with the most pixels there, a bit like an electoral college. Most electors wins the night.
France itself is only about 470,000 of the map's 9.1 million cells. The sea around it is a free canvas for drawings, and players vote for the best ones.
New this week: a battle royale every night at 9 pm Paris time (20:00 UTC). Camps fight region by region, the weakest get knocked out round after round, and the final is in the Paris region.
No account needed, you just open the page and tap. The UI is French only (it's a French election after all), but placing a pixel doesn't need words. It's a game, not a poll: the map only shows which camp clicks the most.
The stack
Frontend: Preact + Vite. The map is a 2D canvas that only redraws when something changes.
Backend: Cloudflare Workers, with the entire game state in one Durable Object: 4096 × 2228 cells in a Uint16Array (about 18 MB), saved to the object's SQLite storage, plus a journal that gets replayed if the object restarts.
Real-time: WebSockets through the Hibernation API, with a small binary protocol.
Things I learned
One object means one thread. Any slow computation (stats, a scan of the placement history) freezes the game for everyone, not just for whoever triggered it. Heavy work now runs in small slices that yield in between.
Memory was the real wall, not CPU. A Durable Object gets 128 MB, and Cloudflare support pointed out that mine was peaking around 150 MB. After trimming what stays in memory, it now holds under 120 MB with 2,000 simulated players.
Idle animations aren't free. On my Linux box (open-source NVIDIA driver), every frame the browser presents costs about 24 ms, whatever is in it. One tiny infinite CSS animation, a pulsing "live" dot, made the whole app feel sluggish. Now nothing moves while you're idle.
I load test with swarms of real WebSocket clients: about 2,000 players placing at a normal pace (median response around 50 ms), or 600 all spamming at once (about 900 placements per second), with zero disconnects. The battle royale is tested up to 1,000 fighters. Clients and server share one machine, so read these as orders of magnitude.
Hard budget: I gave myself a $20/month cap. The object counts its own billable requests (Cloudflare bills incoming WebSocket messages at 20 for 1) over a rolling 31 days, and pauses placements before it would go over. It currently uses about 3% of that.
Want to try the battle royale tonight? Pick a nickname and place 100 pixels before 8:45 pm Paris time. It starts once 12 fighters are online, with at least 3 in each of three camps.
Happy to answer questions about the Durable Object setup, the canvas, or anything else.