Forty games and not one sign-up screen
Play Pixel Games started as a question - how much of a game can you ship if the player is never allowed to create an account, download anything, or wait?

The rule I set on day one was that the game had to be playable within one second of the page loading, with one thumb, and without me knowing anything about the person playing. No login, no download, no install. Everything after that was a consequence of that rule.
It sounds like a marketing line. In practice it's a brutal engineering constraint, and it deleted about half the architecture I would otherwise have reached for.
No account means no server, mostly
The obvious casualty is the backend. If there's no account, there's nothing to load before the first frame - no session check, no profile fetch, no "restoring your progress" spinner. So the whole front end is a static build: Vite and Vue 3, compiled to a folder of HTML, CSS and JS I can drop on any host.
Routing was the one place I refused the easy path. Hash routes would have been simpler, but
/play/tilt-maze is a URL a person can read and a search engine can index, and /#/play/tilt-maze
is not. So it's HTML5 history routing, and every route is pre-rendered to a genuine static HTML file
at build time. Forty-odd games, forty-odd real pages. The build also sets base: './' so it works in
a subfolder as well as at a domain root - which mattered more than I expected.
The daily challenge, without a database
Every game has a Wordle-style daily challenge: everybody gets the same puzzle on the same calendar day. The instinctive way to build that is a server that generates today's puzzle and hands it out. But that's an account system wearing a hat - now I need a backend on the critical path, and my one-second rule is dead.
Instead the puzzle is a pure function of the date. The calendar day seeds a deterministic pseudo-random generator, and the generator produces the level. Two players on opposite sides of the world get identical puzzles because they computed the same thing from the same seed, not because they asked the same server.
const seed = hash(todayISO() + gameSlug)
const level = generate(seed) // same input, same level, everywhere
Streaks live in localStorage. This is a real trade-off and I want to be honest about it: clear your
browser data and your streak is gone. I decided a lost streak was a smaller cost than an account
wall, and I'd make the same call again - but it is a cost, and it's the single thing people write to
me about.

Sound with no audio files
Forty games with sound effects is normally a pile of megabytes and a loading screen. It ships with
zero audio files instead: ZzFX synthesises every blip, thud and jingle procedurally at runtime
from a handful of numbers. The entire sound design weighs less than one .mp3 would.
The fonts got the same treatment - Press Start 2P and Baloo 2, self-hosted as woff2, no Google Fonts request. Partly performance, mostly that I didn't want a third party logging every visitor to a site whose whole promise is that nobody's watching.

The night I filled my production database with junk
There is a backend, just not on the critical path - leaderboards and a lightweight identity live on
Parse and Mongo behind api.playpixelgames.com. And here is the mistake I made, written down so I
don't repeat it.
The front end's API base defaulted to production. Not localhost - production. So the moment I
started the dev server on its own and clicked around, my test scores went straight into the live
database. The leaderboard writes weren't gated on hostname either, so nothing stopped it. I spent an
evening cleaning rows out of a production collection that I had put there myself, one careless
npm run dev at a time.
A default that points at production isn't a default. It's a loaded gun with the safety off.
The fix was unglamorous: never run the front end without a local backend beside it, point the dev build at a same-origin proxy, and write the rule at the top of the repo's guide in language my future self can't misread. Config that's dangerous when forgotten should fail loudly rather than default quietly.
What I'd do differently
- Make the environment explicit from commit one. No implicit production. Refuse to boot without a target.
- Design the streak-loss story earlier. An optional one-tap export would have cost a day and saved most of the complaints.
- Prune sooner. Forty games is a lot of surface for one person. The best eight carry the site.
None of that changes the founding constraint, though. Open the page, and you're playing. That part still works, and it's still the reason the thing exists.