Inspiration
Every sports broadcast has the same energy: two people losing their minds over something that is, objectively, not that important. Every bank statement has the opposite energy: nobody wants to look at it. I wondered what would happen if I crossed them, and then added a Siskel and Ebert style review show on top. The result is a pair of critics who treat your 2am DoorDash order like a game-winning drive and then give it a 2 out of 10.
What it does
Checkout Critics puts two AI critics in a sports broadcast booth to review every purchase you make, live and out loud.
- Big Mike is the play-by-play announcer. He's loud, generous, and easily impressed.
- Linda is the color commentator. She's dry, analytical, and quietly devastated.
Each purchase becomes a "play." Your balance is the score, your bank is the opponent, and both critics give every purchase a score from 0 to 10. They sometimes disagree on air, which earns a SPLIT DECISION graphic. A ticker tape scrolls your stats along the bottom (food delivery streak, biggest play, subscription turnovers, critics' average), and you can hit IMPULSE BUY to make a new purchase and hear them react in real time. When the session ends, they deliver a final review of your whole spending run, complete with a movie poster style pull quote.
Is it useful? No. Two thumbs down? Probably.
How I built it
- Capital One Nessie API supplies the bank data. I create a customer and account, load purchases into it, and read them back as the transactions the critics review. Impulse buys are posted back to Nessie.
- Gemini API writes the commentary and scores each purchase. It returns structured JSON (dialogue lines, an on-screen graphic, a score from each critic, and a final review at the end).
- ElevenLabs gives the two critics distinct voices, with different settings so Mike sounds hyped and Linda sounds deadpan.
- React + Vite + TypeScript powers the frontend with Tailwind for styling, and an Express server keeps all the API keys server-side.
The key design decision was that the model never does math. Balances, totals, streaks, and the critics' average are all computed in code and handed to Gemini, which only writes the jokes and scores around real numbers. That is why the roasts quote the actual amounts.
An hour lost to a 403 that was entirely my fault. Nessie is very well documented; I simply hadn't read it. Creating an account returned 403 Missing Authentication Token, which looks exactly like a rejected key, and I treated it like one: tried every auth header, blamed the account tier, suspected the key. The key was fine. Accounts aren't created with POST /accounts (they hang off their customer), type is a closed enum, rewards is a required field, the date field is purchase_date, and write responses wrap the created object in {objectCreated}, so created._id was silently undefined, and the next call then failed complaining about a missing merchant_id, which read as a validation bug rather than the undefined it actually was. Every one of those is in the docs. Most of them are in the first screen of them.
The same thing happened twice more. Gemini: my dev model 404s for new users, and a model I simply guessed at never existed; both are listed in the models endpoint I hadn't called. ElevenLabs: an exhausted quota returns HTTP 401 with code: "quota_exceeded", not a 429, so I spent time debugging voice IDs that were perfectly fine. And because every service here degrades gracefully, that last one surfaced as a generic "browser voices: unavailable"; an out-of-credits key rendered indistinguishable from a typo.
The pattern wasn't that the APIs were hard. It was that every one of these errors pointed somewhere plausible but wrong, so the instinct was always to debug the integration instead of going back and read. Nothing here needed a clever workaround; it needed reading the docs, and then making sure a fallback failure tells you why instead of smoothing it into something generic.
Browser speech cannot be trusted. My fallback path used speechSynthesis, and it failed in two ways I only understood by reading the engine's actual behaviour. First, calling cancel() while it is paused wedges it permanently: every later utterance is silently dropped for the life of the page, which is why "restart" appeared to kill audio forever. Second, speechSynthesis.volume is global and persistent: two overlapping interrupt ramps each restored the other's partially-lowered value, so every restart ratcheted the master volume a little further down until the show went permanently silent. Both were invisible from the outside. It just looked like "the audio stopped", which is why real TTS (ELEVENLABS_API_KEY) is not a luxury here but the thing that makes audio trustworthy at all.
A screen shake that rebooted the app. To replay the impact animation I keyed the root element on a shake counter. That remounted the entire React tree on every high-intensity play, wiping whatever you had typed into the console, restarting the ticker, and re-popping every verdict card. The animation now runs on a single node via the Web Animations API and never remounts anything.
My best feature was unreachable. IMPULSE BUY appended to the segment queue, so a purchase landed behind every play still ahead of it. In a 90-second show the booth spent the entire run narrating backfill and the button never once fired. It now cuts the line on air, using the same interrupt() path as Halftime and Postgame, and resumes the backlog afterwards. A $28 DoorDash interrupting Mike mid-word is the whole point of the app, and it only worked once I stopped being polite about the queue.
What I learned
- Structured output plus validation makes LLM features much more reliable than trusting free-form text.
- The funniest AI writing comes from giving the model real, specific data instead of letting it invent details.
- Fallbacks are a feature. A demo that degrades gracefully beats one that breaks on stage.
What's next
A raccoon mode that gives terrible, confident investment advice, instant replays of your worst purchases, and a post-game interview where you try to explain yourself to the critics.
Built With
- capital-one-nessie
- eleven-labs
- express.js
- gemini-api
- node.js
- react
- tailwind
- typescript
- vite

Log in or sign up for Devpost to join the conversation.