Inspiration

Every football fan knows the beautiful game is best experienced together. Watching a World Cup game alone on a laptop doesn't compare to the roar of a room that cares as much as you do. But finding that room is hard — especially if you've just moved, you're traveling, or your team's community is scattered across the city. And even when you find fans of your team, it might not be your crowd: a student who wants a loud late-night bar and a parent who needs a family-friendly afternoon screen are both Japan fans, but they're not looking for the same night.

We wanted to build a bridge between isolated fans and their people — not just supporters of the same team, but the group that actually matches how you watch. CU There was born from a simple idea: no fan should have to celebrate a last-minute winner alone, or in the wrong room.

What it does

CU There puts your city's football life on a live map. Every user pledges a team and fills in a short profile — how they like to watch, their languages, whether kids come along, what they eat. That profile is what the whole app matches on: not just fans of your team, but your kind of fan.

Live fan map. The city is divided into tiles, each carrying a live count of fans per team based on where users are. Pick your team and see exactly where your crowd gathers. Tap any venue to see who's actually attending its watch party — the same match can have a Brazil-dominated grill and a Japanese-dominated sushi restaurant, and the map tells you which room is yours before you leave home.

Smart search. When you search, Gemini reads the query — fixing typos, pulling out teams, days, and tags from the app's own vocabulary. Every venue and event is stored in DigitalOcean Postgres alongside a 768-dimension embedding of its description; your query is embedded the same way, and pgvector computes the similarity in-database. Results are scored by a blend of meaning similarity and tag overlap, then Gemini makes a final relevance judgment on each one.

Explainable matching. Every result carries a match score you can open: how close it is to you in meaning, which of your tags matched, and a live Gemini fit rating with a reason written for that exact pairing.

Profile-based recommendations. When you sign up, Gemini reads your entire profile and writes a matching fingerprint — a short summary plus the tags it implies ("my kids are coming along" becomes family-friendly). That summary is embedded and stored in DigitalOcean next to the venues and events it will be compared against, then run through the same search pipeline: semantic similarity against every venue and event, tag overlap, and a live fit rating. Your team's upcoming fixtures at places that suit you rise to the top, each with a plainly written why.

Request what doesn't exist. If no one's hosting the event you want, post a request. Requests are stored in DigitalOcean, and Gemini clusters them into demand briefs — "11 people in Chinatown wants a party for the Japan match" — with a summary, sample quotes, and suggested tags. Any host can open that brief and post exactly that event, pre-filled, in two taps.

How we built it

Languages

  • TypeScript — the entire codebase: app, server, and scripts
  • SQL — schema and queries
  • JavaScript — embedded WebView canvas renderer for the heatmap

Frontend

  • React Native + React 19 — the app, built with Expo
  • Moti — animations
  • socket.io-client — live updates: RSVPs, new and cancelled events

Backend

  • Node.js 24 + Express — REST API
  • Socket.IO — realtime broadcast
  • tsx — TypeScript runtime

DigitalOcean

A Managed PostgreSQL cluster is the app's single source of truth, provisioned and managed through the DigitalOcean REST API. It stores everything: venues, events, the tag registry, per-tile fan counts, user profile summaries with their embeddings, and watch-party requests.

Two extensions turn it from storage into the matching engine:

  • pgvector — every venue, event, and profile summary row carries a 768-dimension embedding of its description. Vector similarity is computed inside the database: one SQL query returns everything ranked by how close its meaning is to yours.
  • PostGIS — venues carry geography points, so proximity questions like "what's near this user" are one ST_DWithin query away.

Google Gemini

Gemini does every piece of language understanding in the app.

  • gemini-flash-lite-latest — the real-time jobs: reading search queries (typo correction, extracting teams, days, and tags), judging each result's actual relevance, rating venue/event fit against a user with a written reason, and clustering raw watch-party requests into demand briefs.
  • gemini-3.5-flash — profile summaries, a user's matching fingerprint. Generated once per profile version and stored.
  • gemini-embedding-001 (768 dims) — embeds venue descriptions, event docs, and profile summaries, producing the shared vector space that pgvector searches. Query, profile, and venue all speak the same geometry.

APIs (internal)

REST endpoints for app state, hybrid search, recommendations, venue smart-search, requests + AI demand briefs, profile summary lifecycle, and party post/cancel. WebSocket (Socket.IO) events broadcast live changes: RSVPs, new and cancelled parties, pledges.

Data

Fan counts come from users: each signup pledges a team and shares a location, and every pledge updates the map live. For the demo we pre-populated the city with 25 real Ottawa venues and simulated user data so the map is alive on day one. In production the tiles are driven purely by active users.

Challenges we ran into

  • Our first search returned something for every query — which meant garbage for half of them. We added similarity floors and a final Gemini relevance check.
  • gemini-flash-lite sometimes returns truncated or malformed JSON. We wrote a balanced-JSON parser, and learned to never cache a failed response — one cached fallback poisoned every request after it.
  • The native map crashes if overlays mount after load, so every dynamic layer — heat, team filters, markers — had to be mounted once and toggled by opacity only.

Accomplishments that we're proud of

  • Our first mobile app.
  • First time learning and using Digital Ocean.

What we learned

  • Tier your AI by job. A fast model for every search, a stronger one for things generated once and stored — responsive results at a fraction of the cost.
  • Do the matching where the data lives. pgvector inside DigitalOcean Postgres turned similarity search into one SQL query.
  • Never cache a failure. Serve a fallback once, retry next time.

What's next for CU There

Nothing in CU There knows it's football. The same engine — fan map, profile matching, demand-driven hosting — works for any fandom that gathers around live moments: NHL playoffs, the Premier League, NBA finals, F1 race weekends, esports. Next we expand beyond the World Cup to every matchday, add verified venue partners so pubs can claim their team's home ground, and bring in live scores so the app follows the game with you.

Share this project:

Updates