
At a glance
Overview
TimeWarp Trivia is a Jackbox-style party trivia game: one shared screen for the room, everyone else's phone as the buzzer. Built solo, product through full-stack, in about 27 hours across two days, then kept growing well past that first sprint. It's live and playable in-browser at timewarptrivia.com, with 1,106 questions across six decades, three standalone non-decade topic packs (West Wing, Fallout, The Simpsons) adding 434 more, and 36 shipped issues tracked in Linear as of this writing.
Party trivia games like Jackbox split the experience across two kinds of screens: one shared display everyone in the room looks at, and private controllers everyone holds. That split is the whole game: the shared screen can't show private information (like which answer you picked), and the private screen can't show the room's full state (like who's winning). Getting that split right, over a real network, for up to ten players at once, was the actual engineering problem. The trivia content is the theme; the sync model is the product.
The decade angle, 80s through 2010s, gave the content a natural filter and, later, a natural hook for visual theming.
How It Works
- Host opens the shared screen and gets a 4-character room code.
- Up to 10 players join from their phones with the code and a name. No app, no download.
- Host picks a decade filter (or all six) or switches to Deep Cuts, a separate non-decade topic mode, and starts the game.
- 3 rounds of 5 questions each, speed-based scoring (1000 points decaying to 500 over the time limit).
- Before the final round, whoever's in last place picks one question to answer alone. Everyone else just watches.
- Final round pays double points.
- Ranked leaderboard and podium at the end.



Architecture
Realtime sync
One Supabase Realtime channel per room. The TV is the authoritative host: it's the only client that ever advances round state, writes the current question, or ends the game. Phones subscribe to that same row for question/timer state and write their own answers directly to an answers table. Scoring happens client-side on the phone that answered, which is a deliberate scope cut, documented as a known limitation, rather than an oversight. A fully server-validated version would need an edge function or RPC in front of every score write.
Data-driven content
Decades and categories are rows in their own tables, not hardcoded enums. A category like "Internet Memes" gates itself to 2000s-and-later decades via a nullable min_decade_id foreign key. Adding a new decade or category is a data change, not a deploy.
Design system reuse
Every color, type scale, and spacing value is mirrored from an existing personal design system rather than invented fresh for this project, to prove the same token system holds up across a third, very different surface: a 10-foot TV UI and a touch-first phone UI, instead of a typical web app. An internal /designsystem reference page later made that claim checkable rather than asserted: it renders the real theme and mixin values plus several actual shipped components (avatar, countdown ring, decade filter, help/feedback/share) pulled straight from the live code, not recreations, so the docs can't visually drift from the app. A clearly separated section below it sketches grounded-but-speculative components not yet built, each tied to a documented gap.
Technical Highlights
A few problems along the way were worth solving properly rather than papering over.
A CSS animation was quietly burning CPU on every screen, forever
A user reported their fan spinning up just from having the site open. Chrome performance traces showed roughly 2.2% sustained main-thread cost on an otherwise idle page: not JavaScript, paint. The ambient scanline overlay, a decorative CRT-style texture on every screen, animated background-position in an infinite loop, which forces a full repaint every frame indefinitely. Switching the same visual effect to animate transform instead, a compositor-only property, dropped that cost to roughly 0.09%, measured before and after with the same trace. Same look, no repaint.
Decade-based visual theming, built to cascade without touching components
The spec called for the visual theme to shift once a decade's picked, without a rewrite of every screen. The trick: SCSS variables can hold a var() CSS function as their value. Changing $marigold: #ffb238; to $marigold: var(--marigold, #ffb238); means every existing component that already referenced $marigold compiles to var(--marigold, #ffb238) automatically. No component file touched. A single override block per decade, e.g. [data-decade-theme="80s"] { --marigold: #ff3fa4; ... }, driven by a small hook then re-themes the entire app the moment a decade's selected, TV and phone both, session-wide. The same override system now covers all six decades, not just the one it was proven on first.


Diagnosing a silent analytics gap
Vercel Analytics showed roughly 20 pageviews; PostHog showed 1. Both were correctly configured with valid tokens in the right environments. The actual bug was a malformed POSTHOG_HOST value that wasn't a valid absolute URL, so the browser resolved every PostHog request as relative to the app's own origin instead of PostHog's servers, 404ing silently in the background. Confirmed via the dev server's own request logs before touching any config.
Small-screen gating to prevent orphaned game state
The host screen is a 10-foot UI meant for a TV or laptop. Without a check, a phone visitor landing on /host would silently create a real, unusable game room. A matchMedia-based hook gates the real game behind a simple "use a bigger screen" screen below a 768px breakpoint, so a stray visit never spins up state nobody can use.
A second game mode reused the entire engine, zero new game-loop code
Adding Deep Cuts, a non-decade topic mode (West Wing, Fallout, more to come from player suggestions), could have meant forking the game loop. Reading the existing code first paid off: round-advancement, scoring, and the block mechanic were already keyed off generic state like status and question_index, never off decade_filter. Deep Cuts ships as its own route and lobby screen but runs through the exact same LiveTvFlow/LivePlayFlow engine as decade mode via a mode prop, and /play needed no changes at all since joining and rendering were already fully status-driven.
Accessibility gaps that only showed up after real people played
Two post-launch fixes came from actual play sessions rather than an audit: the decade filter's focus ring and its selected state used the same visual treatment, making keyboard navigation ambiguous about what was actually chosen; and the question countdown's speed-decay scoring had no non-visual signal, so a screen-reader user had no way to know time was running out. Both are the kind of gap that a solo build, moving fast, tends to miss on the first pass and that only surfaces once someone who isn't the builder tries to use it.
What Shipped
- Full realtime multiplayer game: lobby, 3-round structure, speed scoring, the block mechanic, podium.
- 1,106 original trivia questions across six decades (60s through 2010s), plus three standalone Deep Cuts topic packs (West Wing, Fallout, The Simpsons) adding 434 more, all sourced and fact-checked, written in a consistent dry/deadpan voice.
- Deep Cuts: a second, non-decade game mode reusing the entire realtime engine with zero new game-loop code.
- Same-room Play Again rematch flow with question-repeat avoidance across games, plus a host Cancel Game escape hatch.
- A browser-playable web version (no app install required), the primary way to play today. The Fire TV app is live on the Amazon Appstore; the same Android TV wrapper is built and working, waiting on Google Play Console's TV app testing before public listing.
- Full observability stack: Vercel Analytics + PostHog for product analytics, Sentry for error/performance tracking, Vercel Speed Insights for Core Web Vitals.
- All six decade visual themes (60s through 2010s), each a single override block on the same token-override system, proved out first on 80s.
- An internal /designsystem reference page rendering real tokens and shipped components live from the app's own code, alongside a documented backlog of grounded-but-speculative components.
What's Next
- Clear Google Play Console's TV app testing to publicly list Android TV (Fire TV, built on the same wrapper, is already live on the Amazon Appstore).
- Server-side answer validation.
- Roku and Apple TV ports (stretch goals, after Android TV).
- Grow the Deep Cuts topic library from player-suggested categories submitted through the feedback form.
Cue it up.
Tell me about the gig. I reply within a day, usually with questions.