Inspiration

Nobody quits the gym because they're lazy. They quit because nothing notices. No streak breaks, nobody asks where you were on Tuesday.

I watched the same pattern everywhere I've lived and trained: people who train alone quit quietly, and people with a padel group that expects them on Thursday keep showing up for years. The difference isn't willpower — it's that someone (or something) notices.

Fitness trackers record what you did. Duolingo made a language stick by making absence visible. Nobody had done that properly for sport — across every sport, not just running with a GPS watch. So I built Fouz: a tracker wearing a game's clothes, where showing up is the game.

What it does

Log any of 55 sports in about 20 seconds — padel, football, gym, boxing, yoga, no wearable needed. Then the game layer takes over:

  • XP with public math: a workout pays 20 XP, a pickup game 15, a meal 10, a goal 10. Level N costs 100 × (N−1)² XP. No hidden multipliers, no pay-to-win — the formula is right there in the app.
  • Streaks that stay honest: alive only while your last logged day is today or yesterday. No freeze tokens, no streak insurance. That's what makes a 30-day streak mean something.
  • Badges you'd actually screenshot: original artwork of Fouzi (the mascot) for every milestone, across four rarity tiers.
  • A crew: a feed that only rewards doing the work (no influencers, no ads), leaderboards filterable by city, sport, or friends, and pickup games near you — see games starting within two hours, join in one tap, or host one and let the neighbourhood fill it.
  • Share cards: every session becomes a story-ready card with your level and a scannable code that lands friends in the app.
  • Privacy per post: public, followers, or just you — and private activity still earns XP and holds your streak.

Fouz launched global from day one: English, French, and Spanish, with leaderboards live from Casablanca to Kuala Lumpur.

How I built it

Fouz is built by one person — design, code, backend, artwork direction, and the marketing site.

  • App: React Native + Expo (TypeScript), EAS builds.
  • Backend: Supabase — Postgres with row-level security, RPCs for everything the client shouldn't compute, storage for share-card images.
  • Monetization: RevenueCat (react-native-purchases) powering Fouz Pro and the Founder's Pass — more below, because the integration details matter.
  • Web: A Next.js site (fouz.app) where every in-app share becomes a public permalink with a proper OG unfurl, plus a localized marketing/SEO surface, with the install funnel instrumented in PostHog behind a first-party proxy so ad blockers don't blind it.

How RevenueCat is wired in (the part I'm proud of)

Fouz is a game with public leaderboards, so monetization had one hard constraint before it had a price: nothing you can buy may touch XP, levels, streaks, badges, or leaderboard rank. The moment money buys rank, every rank is meaningless and the free players — the community that makes the leaderboard worth topping — leave. The paywall says it out loud, directly under the price: XP and rank stay earned, never bought.

So Pro sells leverage over your own data and reach, never over another athlete: Pro search filters and leaderboards, your season in XP / volume / records, unlimited log templates, who viewed your profile, and every share-card style. Three SKUs across two RevenueCat offerings — Pro Monthly $3.99, Pro Yearly $24.99 with a 7-day free trial (the default; the button reads "Start your free week"), and the Founder's Pass at $49.99 one-time — Fouz Pro for life, plus a Founder mark.

The integration is deliberately paranoid:

  • Purchases bind to the Supabase user id via Purchases.logIn, so an entitlement survives reinstalls and device changes.
  • The webhook is the authority, not the client. A revenuecat-webhook edge function writes profiles.founder_at and the Pro expiry server-side, so the app can never grant itself anything. Its decision layer is a pure, unit-tested module: NON_RENEWING_PURCHASE / INITIAL_PURCHASE / TRANSFER grant, REFUND / EXPIRATION revoke, and every unrecognised event type is ignored — RevenueCat adds event types over time, and an unknown one must never read as a grant. CANCELLATION deliberately does not revoke Pro: auto-renew switched off still means paid until the term ends.
  • Billing fails closed. One module owns the SDK and no call throws into the UI. On Android, on a build with no RevenueCat key, or on a JS bundle that reached a binary predating the native module (react-native-purchases cannot ship over an Expo OTA update), the paywall simply does not render — a purchase button that cannot complete a purchase is worse than no button.
  • Offerings are swappable from the dashboard. Both the pro and founder offerings read RevenueCat's current first, so pricing can move, or the Pass can be withdrawn, without shipping an app update — which is exactly how a founder offer should end.

Challenges I ran into

  • Sharing had to survive real names. Share-page unfurls are composited from the exact PNG the app rendered, because server-side text rendering (Satori) crashes on Arabic letter-joining — and my launch cities include Casablanca and Cairo. Compositing the app's own image sidesteps the text path entirely and means the unfurl matches the card pixel-for-pixel.
  • OTA updates vs. native modules. react-native-purchases can't ship over an Expo OTA update, so a JS bundle can absolutely land on a binary that predates the native module. The billing layer detects that state and hides itself instead of crashing.
  • Streak honesty is a design constraint, not a feature. Every "kind" mechanic (freeze tokens, grace days) makes the streak worth less to the person holding it. Saying no repeatedly was harder than building it.
  • Solo scope discipline: every feature had to earn its place against "does this get someone to log tomorrow?"

Accomplishments I'm proud of

  • Fouz is live on the App Store — submitted, reviewed and released on 28 August 2026, inside the Shipaton window.
  • The entire loop ships: log → XP → level → share → friends react → someone needs a fourth on Thursday → you show up again.
  • 55 sports, 9 categories, three languages, day-one global leaderboards.
  • Original badge and mascot artwork across four rarity tiers — not an asset pack.
  • A monetization model I can defend to every free user in the app, in one sentence, without flinching.

What I learned

  • Consistency is a social problem wearing a fitness costume. The XP is the hook; the crew is the retention.
  • Fail-closed beats feature-flagged: the billing layer that refuses to render is the one that never crashes a paying customer.
  • Shipping alone means the product is your prioritization function, and "no" is the most-used feature.

What's next for Fouz

  • It shipped. Fouz went live on the App Store on 28 August 2026 — listing, review and release all inside Shipaton. The final boss turned out to be App Review, twice.
  • Getting the first athletes to log a second day: the only number that matters before any other number matters.
  • Android next, then richer pickup-game tooling (recurring games, skill matching).
  • RevenueCat paywall experiments on how Pro and the Pass are presented. The no-pay-to-win rule is non-negotiable; how it gets told is the experiment.
  • More badge artwork, more share-card styles, and the boring, compounding work of shipping what the early athletes actually ask for.

Built With

Share this project:

Updates

Submission history