Inspiration

"Where should we eat?" is the hardest question in any friend group. Menus are walls of text, review apps rate restaurants instead of the dish you'd actually order, and group chats go in circles until someone gives up and picks the usual spot. We wanted deciding what to eat to feel as quick and fun as swiping, and to settle the group argument with what everyone actually wants.

What it does

Crave is a Tinder-style app for food. Instead of restaurants, you swipe on real dishes near you, each with a real photo from the restaurant's own website.

  • Swipe to discover: right to crave, left to pass, swipe up for details. Cards stay "blind" (no restaurant name) until you like a dish, so you choose with your stomach, not brand loyalty. A like reveals the restaurant, with its logo, directions and the rest of its menu.
  • It learns your taste: every like teaches Crave which types, proteins, flavors and cuisines you love. Each batch mixes picks matched to you with new things to try, so you don't get stuck in a bubble. Surprise me deals a completely random mix.
  • Chef-hat ratings for dishes, not restaurants: rate and review the dish itself, like other reviews, and read an AI summary of what customers say.
  • Friends: see which friends liked a dish right on the card.
  • Groups, to decide where to eat: start a group with a countdown or a deadline, invite friends, and everyone keeps swiping as normal. Crave tallies the group's likes and picks the winning restaurant and its top dishes. You get phone notifications when voting is about to close and when there's a winner, and the host can call it early with "Get results now."
  • Crave Clash: a live multiplayer food quiz ("Where's this from?", "What's this dish?", "Which costs more?") built from real dishes nearby. Each player's profile picture races toward a chef's hat on a live leaderboard. There are three modes: Race to the hat, Rounds and Sprint.
  • Installs like a native app (PWA), with real push notifications on Android and iPhone.

How we built it

  • Frontend: Next.js 16 (App Router) with React 19 and TypeScript, Tailwind CSS v4, and Motion for the swipe physics and animations.
  • Backend: Supabase.
    • Postgres with PostGIS for "dishes within X miles" searches.
    • Row-level security on every table, so each person only ever reaches their own data.
    • Realtime for the live Clash leaderboard.
    • Auth with email and Google sign-in, plus Storage for profile pictures.
    • pg_cron and pg_net to schedule group notifications.
  • Data pipeline:
    • Overpass (OpenStreetMap) finds the restaurants.
    • Firecrawl reads their official menu pages.
    • Google Gemini tags each dish with a fixed vocabulary of types, proteins, flavors and cuisines.
    • Only dishes with a verified photo from the restaurant's own site are published. We never use AI-generated food images.
  • Recommendation engine: confirmed likes score tags (primary type +2, others +1). Each score is scaled down for very common tags, so they don't dominate:

$$w_t = \frac{\text{likes}_t}{\log!\left(1 + \frac{N}{n_t}\right)}$$

Here $N$ is the number of dishes in the catalog and $n_t$ is how many carry tag $t$. Each batch of 15 is split roughly 50/50 between personalized and discovery picks, then shuffled under diversity rules (no repeated restaurant within 3 cards, cuisine spread).

  • Notifications: Web Push with VAPID keys and a service worker.
  • Hosting and testing: deployed on Vercel. We test with Vitest (about 300 unit tests) and Playwright (about 40 phone-sized browser tests, plus live checks against production).

Challenges we ran into

  • Real food photos only: most menus don't have a photo for every dish. We built checks that match images to the right dish and drop anything without one, and the app quietly skips a dish if its photo link ever breaks.
  • Swipe-up animation: morphing a card into the full details page stuttered on slower phones. We traced it to a re-render of the whole page mid-animation and got it to a steady 60 fps, even with the CPU slowed 4×.
  • Realtime and security together: the live leaderboard first got no updates at all. The Realtime connection started before the login session loaded, so our own security rules hid every change. Once the session token was passed to Realtime, updates arrive in under a second.
  • Keeping answers secret in a multiplayer game: Clash answers never reach the phone. The database checks each answer once.
  • Merging three people's work: two of our database changes shared the same migration number. Left alone, one would have been silently skipped.
  • Timezones: the server ran on UTC while phones run local time, which broke some on-screen times until we caught it in testing.

Accomplishments that we're proud of

  • A full, live product with real dishes from 13 restaurants near the University of Houston, real photos and real logos.
  • Group decisions and a real-time multiplayer game, both secured by database-level rules.
  • Real phone notifications delivered end to end.
  • Smooth, native-feeling swipe animations.
  • Hundreds of automated tests, plus live checks of every major flow against production.

What we learned

  • How far a web app can go toward feeling native: PWAs, Web Push, gestures and spring animations.
  • Putting security in the database (row-level security and locked-down database functions) instead of trusting the client.
  • Building recommendations that learn without trapping people in a bubble.
  • Using AI where it helps (tagging dishes, summarizing reviews) and keeping it out where trust matters (no AI food photos, no invented descriptions).
  • Test against the real thing: many of our worst bugs only showed up on the live site.

What's next for Crave

  • More cities and restaurants, with restaurants able to claim and update their own dishes.
  • Reservations and ordering straight from the group results.
  • Smarter recommendations that use time of day, budget and dietary needs.
  • Native iOS and Android apps, and more Crave Clash game modes and leaderboards.

Built With

Share this project:

Updates

Submission history