Work About Contact

Ballrs

Product Design · Game Design · Brand iOS & Android Live on App Store Dec 2025 — Present
[ 01 Overview ]

A daily sports trivia app I designed, built, and shipped.

Ballrs is a multi-sport player guessing game live on iOS and Android. Seven game modes, four sports, twenty-eight daily puzzles.

Game Modes
Sports
Sessions Per Device
Total Build Cost
Product Design Game Design Brand Growth

Built with React Native using AI-assisted development. I'm a designer, not a developer. AI closed the gap.

[ 02 The Opportunity ]

The gap between Poeltl and NYT Games.

Poeltl proved sports fans love daily puzzle games. NYT Games proved the multi-game collection model works on mobile. I hadn't seen anyone combine the two. The daily puzzle space was scattered. Single sport, single game, mostly web-only. Ballrs puts it all in one app.

FeaturePoeltlNYT GamesBallrs
Native App
Multi-Sport
Multi-Mode
Mobile-First

The original concept was a Poeltl clone. A stat comparison grid. But that format rewards process-of-elimination thinking, not sports knowledge. Switching to text-based clues made the game accessible to casual fans, not just stat nerds. Six clues, one mystery player. You either know it or you don't.

First Prototype
First prototype — stat grid
The Pivot
From "Stat Grid" to "Clue Drop"
Shipped Product
[ Design Decision ]
R
W
N

"Why not Wordle with player names?" Player names aren't stored in your brain the way English words are. You know Jaylen Brown plays for Boston, not that his name is five letters ending in OWN. When you see _R_WN on a Wordle board, your brain can't do the passive scan it does with regular words. You'd have to consciously go through every team and roster. That's work, not a puzzle. That constraint shaped the entire game format.

[ 03 Game Design ]

Seven modes. Shipped one at a time.

Launched with the core game, validated it, then added new modes every few weeks. Each release included a feedback form. Users told me what worked and what didn't, and I shipped fixes the same day.

V1.0
Clue Drop
Core daily puzzle. 6 clues, 1 mystery player.
V2.0
Career Path
Added Career Path and Over Under. Redesigned home screen.
V3.0
Brackets
Added Brackets and Oddball. Five game modes total.
V4.0
Zero-In
Added Zero-In and Ranked. Seven total.
[ Design Decisions ]
Clue Drop gameplay
Feedback modal
Over Under with contextual hints
Duel Results
Client vs Server Scoring Architecture
01

"Clue Drop scoring: 6 points for 1 clue, down to 1 for 6." Simple and intuitive. Rewards knowledge depth. No complex multipliers, no bonus rounds. You either knew it early or you didn't.

02

"Feedback form on the 5th play, not the 1st." First play is too early. By the 5th play, users have formed a real opinion. Every new game mode launched with a feedback form built in.

03

"Server-side game logic." Originally client-side. Moved everything to Supabase Edge Functions after realising client-side scoring was exploitable. Security is an architecture decision, not a feature.

04

"Over Under: two problems in one game." League position questions were counterintuitive. 43 out of 1,041 questions had users selecting UNDER when the answer was OVER. Added contextual hints like "Lower number = higher finish." Then changed the buttons from red and green to neutral colours. Red for "higher" felt wrong to users.

05

"Duels: killed live, went async." Real-time meant Player 1 sits waiting if Player 2 doesn't show. A 48-hour async window fixed it. If both players are online, it still feels live.

★★★★★
"It's sick. And most importantly very addictive."
Ballrs User, WhatsApp
★★★★★
"Absolutely love this app."
Randy216, In-App Feedback
[ 04 Home Screen ]

The home screen grew up.

The home screen changed three times. Not because it was broken, but because the product outgrew it. One game mode became seven. A simple grid wasn't enough anymore.

Phase 1 / The Grid
Phase 1 — The Grid
Four sports, three actions each.
Phase 2 / The Dashboard
Phase 2 — The Dashboard
Sport tabs, still one game.
Phase 3 / The Game List
Phase 3 — The Game List
All games, all sports, one scroll.
1
2
3
4
[ Design Decision ]

"The dashboard was the right call for one game. It gave each sport its own space with leaderboards, streaks, and duels. But it couldn't hold seven game modes per sport. The vertical list solved that. Every game mode is visible immediately. Sport tabs filter if you want, but the default shows everything. The layout scales to any number of games without redesigning the screen."

1Profile name and icon visible without opening settings.
2Date reinforces the daily puzzle habit.
3Duel section breaks up the list and drives social play.
4Ad slot built in from launch so adding monetisation later won't disrupt the layout.
[ 05 Onboarding ]

Show, don't explain.

The first version of onboarding was static instruction cards. Text explaining how each game mode worked. Nobody reads instructions for a trivia game. Replaced them with animated demonstrations that simulate actual gameplay.

Static Instructions (Before)
Static onboarding instructions
Animated Demo (After)

For existing users who had already passed the onboarding, these screens didn't exist. But new game modes still needed explaining. Built a separate set of introduction screens that appear the first time an existing user opens a new game mode. Same animated approach, just contextual.

New Game Mode Intro Screen
New game mode bottom sheet
Contextual sheets introduce new modes to existing users via a single-action bottom sheet.
[ Design Decision ]

"No email, no friction." Account creation asks for a username (with a random generator if you can't think of one), a flag to represent where you're from, and nothing else. No email required. At level 5, the app asks if you want to link an email to protect your progress. By then you've got streaks, XP, and unlocks worth keeping. Users link their email because they want to, not because they had to.

[ Design Decision ]

"Profanity filter from day one." Random name generator and custom usernames both run through a profanity filter. Learned this the hard way when a leaderboard username had to be manually corrected in the database.

[ 06 Design System ]

Neubrutalist. On purpose.

Bold, tactile, high contrast. Thick black borders, offset shadows, and sport-specific accent colours. Every element follows one rule: if it's filled with colour, you can tap it. If it's white, it's information.

Typography
DM Sans (Regular, Bold, Black)
Background
Cream #F5F2EB
Text / Borders
Black #1A1A1A
Primary Action
Teal #1ABC9C
Secondary Action
Yellow #F2C94C
Sport Accents
NBA   EPL   NFL   MLB
Components
2px black borders, 4px offset shadow, 16px corner radius on buttons, 8px on cards
Play Now
Pill Button
border: 2px · radius: 16px · shadow: 4px
Interactive Grammar: Coloured fill = Tappable. Press state physically removes the 4px offset shadow.
7.44
Sessions
Stat Card
border: 2px · radius: 8px · shadow: 2px offset
Context vs. Action: Sport colours provide context. Primary actions remain Teal.
Tab Bar
height: 52px · radius: 100px · active: filled circle
Custom Over System: Sport accent on bar. Active icon filled, inactive at 40%.
App Icon Evolution
App icon V1 App icon V2 App icon V3
The Evolution
V1 — Original

The icon went through three versions. The final one uses the neubrutalist pill shape from the app's button system.

Profile Icons
Custom Over System: 0 system emojis. Every asset is a custom neubrutalist illustration for cross-device consistency.

Custom icons generated with AI (Nano Banana) in the neubrutalist style. Bold outlines, solid fills, no gradients. Unlocked as players level up.

[ 07 Engagement Design ]

Twenty-eight reasons to come back tomorrow.

7.44
Sessions per active device

Every day, every sport, every game mode resets. 28 puzzles daily. The engagement system ties it all together. Streaks, XP, levels, duels, and leagues give players reasons to keep playing beyond the puzzles themselves.

Daily Loop
28 puzzles reset daily (7 modes x 4 sports). Always something new.
Two Streak Types
Play streak counts attempts. Win streak counts correct guesses. Separate per game, per sport. There's always a streak to protect.
Unified XP, Separate Streaks
All game modes feed one XP bar and one level. But streaks are tracked per mode per sport. Your level reflects total engagement. Your streaks reflect commitment to specific games.
Home Screen With Streaks
Home
Level-Up Modal
Level Up
Duel Screen
Duel Results
League Leaderboard
Leagues
[ Design Decision ]

"The level-up moment is intentionally dramatic." Full-screen modal, confetti animation, spring bounce, haptic feedback. In an app with no sound effects and minimal animation elsewhere, this moment stands out on purpose. It tells the player: this matters.

[ Design Decision ]

"Review prompt on the 5th win, not the 5th play." Asking happy players, not frustrated ones. Limited to 3 prompts total (games 5, 25, 50) with 60-day cooldowns.

[ Business Decision ]

"Ad slots from day one, not day one hundred." The app ships with Ballrs-branded banners in every ad slot — home screen, leaderboard, leagues, profile, duel results. No paid ads yet. But when a sponsor fills the slot, nothing changes for the user. The experience stays the same. "I want to start with ads because I don't want the experience to suddenly change for people."

[ Design Decision ]

"Archive replay exploit." Users could give up on a puzzle and replay it later for full first-completion rewards. Failed attempts weren't creating completion records, so the system treated every retry as the first attempt. Fix: a completion record is created the moment a user finishes a puzzle — win, lose, or give up. First finish counts. After that, it's a replay. Full XP on the first replay, nothing after that.

[ 08 Everything That Went Wrong ]

Everything that went wrong.

Shipping a live product means things break. These are the biggest problems I hit and how I fixed them.

01
Fixed
Client-side scoring was exploitable.

All game logic ran client-side. XP, points, streaks, duel winners. Anyone with basic tools could cheat. Fix: rewrote everything as server-side Supabase Edge Functions. The client sends what happened, the server decides what it's worth. "If users can gain an advantage by lying to your server, they will."

// Supabase Edge Function — /submit-answer
const { userId, gameId, answer } = await req.json();

// Server fetches the correct answer — never trust the client
const { data: game } = await supabase
  .from('daily_puzzles').select('answer, clue_count')
  .eq('id', gameId).single();

const correct = game.answer === answer;
const xp = correct ? 7 - game.clue_count : 0;

// Write result server-side — client never controls XP
await supabase.from('completions').insert({
  user_id: userId, game_id: gameId, correct, xp
});
02
Resolved
Supabase went down on Google Play launch day.

Backend went down the same day the app launched on Google Play. App wouldn't load past the splash screen. Turned out to be a regional AWS outage in US East, not a capacity issue. Lasted about 90 minutes. Paused all promotion until it was back.

The Google Play launch video. Hours later, Supabase went down.
03
Stabilized
Android build crashed with no errors.

The app opened, splash screen appeared, then died. No JavaScript errors. Nothing in the console. Had to learn ADB logcat to find it. The error was native code corruption in a library. Fix: cleared the cache and rebuilt from scratch.

04
Iterated
Over Under questions confused users.

43 out of 1,041 questions had counterintuitive answers because "higher" and "lower" mean different things in different sports contexts. Users were getting the right answer marked wrong. Fix: contextual hints on every affected question.

Before
Over Under — before
After
Over Under — fixed
05
Verified
A YouTube viewer found a factual error.

I built a pipeline to produce trivia videos for YouTube and TikTok (detailed in Growth). One of them had a mistake.

Carmelo Anthony was listed as having played for the Nets. He didn't. A viewer caught it on a published video. Fixed the database, left the video up to preserve engagement, and established a verification rule for all future content.

Takeaway
"Bad architecture is expensive to fix later."
If I'd built it server-side from the start, I wouldn't have had to rewrite it later. Security isn't something you bolt on. It's an architecture decision.
[ 09 Growth ]

Discovery is the bottleneck.

44%
Page-view-to-download conversion
$2.84
Cost per install (Apple Search Ads)

The product works. 7.44 sessions per device proves retention. 44% store conversion proves the listing works. The challenge is getting the app in front of new people.

Paid acquisition

Apple Search Ads drove installs at $2.84 each with a 40% tap-to-install rate. Best keywords: "sports trivia" at 64% conversion and "trivia" at 69% conversion. Meta Ads got 107 clicks and zero attributed installs. Paused it. Apple targets people searching for apps. Meta targets people scrolling their feed. Different intent entirely.

Apple Search Ads
40%
Tap-to-Install
$2.84
Cost Per Install
Meta Ads
107
Clicks
0
Attributed Installs

Video ads I designed

I made the ads too. Two approaches: a fake group chat to mimic organic sharing, and a gameplay video showing the UI.

"Group Chat" — mimics organic sharing
"Meta Ad" — gameplay video showing the UI
[ Design Decision ]

"The ads set the expectation for the habit." These aren't just install drivers. They show the daily loop — puzzles resetting, streaks building, duels firing. Users who download after watching already understand the rhythm.

Real-world marketing

Designed QR code stickers linking to the App Store. Took them to sports bars showing live games. One outing: about 10 people scanned and downloaded. People watching sports in a bar, you show them a trivia app about sports. The context sells itself.

QR Sticker Design

ballrs.net

Landing page for app store links, privacy policy, and social media bios. Hosted on Vercel. Redesigned once with all seven game modes, scroll animations, and a stats ticker. After someone on Reddit flagged missing security headers, added them the same day via vercel.json.

ballrs.net

I built the tools to make the content.

Two tools I built to produce marketing content faster:

Screenytime
Screenytime output

Converts screenshots and screen recordings into device mockups. Built frames for iPhone 16 Pro and Google Pixel 7a.

Content Pipeline
Content pipeline

Pick a sport and questions, pull player photos from Wikipedia, generate voiceover via ElevenLabs, paste one render command. Idea to finished video in under 5 minutes.

[ Design Decision ]

"The product converts. The problem is getting people to the listing." 44% of everyone who sees the App Store page downloads the app. Discovery, not conversion, is the bottleneck. Everything in this section — ads, stickers, content tools, the website — is about solving that one problem.