▶ Play now → https://proveit.jaydenlin.net
The bluffing game Scattergories wishes it was. You get a category — Football Players, Programming Languages, Famous Mathematicians, Minecraft Mobs, Countries — and instead of just listing answers, you brag about how many you can name. Your opponent doesn't know if you're bluffing. Neither do you, really, until the clock is running and you're sweating out #7.
You trade raises ("I can name 7"… "make it 8") until someone calls "Prove it!" — and the bluffer has to back it up before time runs out. Nail it and you take the round. Choke and they do.
No install, no sign-up, no ads. Click the link, you're in a round in seconds.
Live Multiplayer is a bluffing duel:
- You're shown a category.
- Declare a number — "I can name 6."
- Trade raises back and forth with your opponent.
- Someone calls 🗣️ Prove it! — the claimant must name that many before the timer runs out.
- Back it up → you take the round. Choke → they do. First to the target score wins.
Challenge Race is the same game with the bluffing swapped for a footrace: 2–8 players get one category at once, everyone types at the same time, and the most correct answers takes the round.
Solo and Daily Challenge drop the bluffing — it's just you against the clock, naming as many as you can per category before time runs out.
- 👥 Live Multiplayer — create a room, share the link, and play the bluffing duel head-to-head. Friends can also spectate live.
- 🏁 Challenge Race — 2–8 players, one category, everyone racing at once. Clocks are per player, like chess: you can set a bonus increment for every correct answer, and no clock can grow past twice the base timer, so one player on a hot streak can't leave the room waiting. Play first-to-2, first-to-3 or endless; turn on sudden death and a tie at the top sends just the tied players into a decider; the room can throw a category away by unanimous vote; and when the last clock runs out everyone sees each other's answers and can approve the near-misses the matcher was too strict about. Don't want to trade room codes? Quick Match queues you up and drops you into a race with whoever else is waiting.
- 🕹️ Solo — pick a category, or build a custom multi-round run, and race the clock alone. No opponent needed.
- 🌍 Geography — its own mode, not a category: 27 boards across four kinds. Name the countries on a blank map (11 regions), identify a grid of flags (7), name one highlighted country at a time in a borders quiz (7), or type every capital city (2). Each board runs at its own recommended time, keeps its own leaderboard, and counts toward one all-time geography ranking.
- 📅 Daily Challenge — the same puzzle for everyone each day, with a shared leaderboard. Share your score and challenge friends to beat it.
- The bluff is the game. Most naming games end when you run out of answers. Here, the tension is deciding when to stop raising — pure "chicken" energy, not trivia recall.
- 272 categories, 11,533 verified answers — deep enough that regulars still get surprised, wide enough that non-nerds can hang.
- Live, not laggy. Raises, calls, answers and chat land the instant they're sent, over Socket.IO — nothing to refresh, and nothing to install. (The duel takes turns on purpose — that's where the bluffing lives, and letting your 10s turn expire calls Prove it! for you, so nobody can stall it out. The race doesn't take turns at all: everyone types at once.)
- A few secrets hidden in the category list that we're not going to spoil here.
This is built to be passed around — drop the link in your group chat, Discord, or subreddit and anyone can click and play instantly:
To play a friend specifically: open the site, hit Create a room, and share the room link/code — or copy a ?room=CODE invite link straight from the lobby.
On a phone the share buttons open the OS share sheet, so a challenge goes straight into whichever messaging app you actually use; on desktop the same button copies the link. Every shared link also gets its own generated preview card — the picture Discord, iMessage, Slack and Twitter put in the chat names the challenger, the score to beat and the category, instead of one generic image for every link. Solo runs, the daily, a geography board and a room invite each get their own card.
Install it — the game ships a web app manifest, so phones and desktop Chrome can add it to the home screen. Launched from there it opens without browser chrome. Icons are generated by npm run icons (scripts/make-icons.js), including the 512 maskable Android wants and a monochrome layer for Android 13+ themed icons.
272 categories, 11,533 verified answers across 17 themed groups, 268 of them playable solo — spanning Sports, Geography, History, Entertainment, Food, Animals, Music, Brands, Computer Science, Math, Science, Art, Mythology, Games & Puzzles, American Innovations, and Pop Culture (yes, including memes and Italian brainrot).
Want to add your own? Edit data/categories.js — each entry is "Name" or ["Canonical","alias", …] (aliases all match but count once). The header comment in that file explains the format; no code changes needed.
Next.js (App Router) + React + Tailwind CSS on the front end. Node + Express + Socket.IO for realtime multiplayer, deployed on Fly.io, with persistent game analytics via Turso (libSQL).
One process serves both: server/index.js keeps Express for the JSON API, the owner dashboard and the crawler-facing share stub, runs the Socket.IO layer, and hands every other request to Next.
Every share link gets the same hand-made card, public/og-card.png. A generated card drawn per link (next/og, quoting the challenger's name and score) was built and then switched off — it never looked good enough to keep — and its code has been removed rather than left sitting unreachable. routes/challenge.js still writes a per-link <title> and description, which is the part crawlers show as text.
app/, components/, hooks/ the client (React + Tailwind)
lib/ shared logic, plain CommonJS, covered by node:test
lib/browser/ browser-only client modules (sound, storage, share sheet, the D3 geo board)
lib/referral.js turns a referrer/campaign tag into a channel label
data/ game content + generated data
server/index.js the Express + Socket.IO entrypoint
server/game-engine.js the multiplayer duel
server/race-engine.js the live Challenge Race
server/rooms.js, matchmaking.js lobbies, reconnection, quick match (both in server/)
server/stats.js persistent game analytics (Turso)
routes/ JSON API, owner dashboard
lib/cost-guard.js the Fly spend projection + the two automatic caps
npm install
npm run dev # http://localhost:3000
npm test # node:test
npm run lint
npm run build # the production client bundle (then `npm start`)
npm run test:browser # Playwright — builds first, then drives a real browser
npm run icons # regenerate the home-screen icons in public/Pushing to main deploys to Fly.io on its own (.github/workflows/deploy.yml) once lint, tests
and the client build all pass. You can also trigger it by hand from the repo's Actions tab →
Deploy → Run workflow, which is the easy way to redeploy without a new commit.
It needs one repository secret, set once: fly tokens create deploy, then save the output (the
FlyV1 … string) under Settings → Secrets and variables → Actions as FLY_API_TOKEN.
The running server wants that same token as a Fly secret too — that's what lets its cost guard
scale the machine down; see the note in fly.toml. The dollar thresholds and the projection maths
live in lib/cost-guard.js (FLY_COST) — server/index.js only wires the guard up.
Idle machines autosuspend (fly.toml), so compute is billed only while somebody is actually
using the site. Suspend rather than stop, because a suspend snapshots and restores the machine's
RAM — which is where every live multiplayer room lives, so a sleeping server wakes up with its
games intact instead of dropping them. Waking costs a fraction of a second, not the 1–3s of a cold
boot. The uptime.yml probe pokes the site every five minutes, so in practice it cycles at about
that rate; widen that cron if you want longer idle stretches.
fly deploy # or deploy straight from your machine, bypassing CINothing is required — with no environment at all the game runs, and the parts that need a database just switch themselves off.
| variable | effect when set |
|---|---|
TURSO_URL, TURSO_TOKEN |
turns on persistence: challenge links, the daily, every leaderboard. Without them those endpoints answer "not configured". |
OWNER_KEY |
unlocks the owner-only /admin dashboard: every visitor's IP and location, all chat, the session log, and the tools that rename, merge and delete leaderboard entries. Server-side only — it must never end up in a browser. |
CROWN_KEY |
earns the cosmetic 👑 on your own scores, and nothing else. This is the one the browser holds (localStorage.ownerKey, sent with every result), so it is deliberately a different secret from OWNER_KEY. Unset, the crown falls back to OWNER_KEY — which is how it used to work, so nothing breaks until you set this. Set it, then clear localStorage.ownerKey on any device you have crowned on and re-enter the new value; that is what stops the dashboard key living in a browser. |
FLY_API_TOKEN |
lets the cost guard scale the machine down by itself (see above). |
NEXT_PUBLIC_CF_BEACON_TOKEN |
loads the Cloudflare Web Analytics beacon. Unset, no analytics script is served at all — so a fork reports nothing to anybody. It's read at build time (Next inlines NEXT_PUBLIC_*), so it belongs in the build environment, not in fly secrets. |
PORT |
the port to listen on (default 3000). |
— Built by NewYorkImperialist