A real-time scoring app for watching Eurovision Song Contest with friends.
Create a room, share the name, and rate every contestant across three categories β Music, Performance, and Vibes. Scores sync live across devices so you can see exactly how your friends voted and who crowned the winner.
Live at lagomeurovision.com
The first iteration of the application was done in 2024 to test the capabilities of building applications just relying on AI agents to do the job while incorporating new tech (outside of it's training data). It used Reflect to livesync rooms and was mostly built with Cursor. 2024 Github
The second iteration uses convex and is mostly the same as this third version. That version was built with OpenCode using mostly Claude Sonnet (3.5 I think would be correct for the time).
And now for 2026 I've just cleaned up the repo with GPT 5.5 and Kimi 2.6. Also redesigned it and added haptics and the ability to just add new contestants to allow this to be easily maintainable.
- Live rooms β Join a room with a nickname and vote in real-time. No accounts, no passwords.
- Three scoring categories β Music, Performance, and Vibes on a 1β12 scale. Totals update automatically.
- Room + global scoreboards β See your room's average alongside the global average across all rooms.
- Per-contestant breakdown β View how every individual in your room voted, anonymised with emoji avatars.
- Swipe navigation β Mobile-first design with swipeable contestant cards.
- Multi-year support β Browse and score past contests alongside the current year.
- Haptic feedback β Subtle vibration on taps and interactions when supported.
- Responsive & dark by default β Built for phones during the show, looks good on desktop too.
| Layer | Tech |
|---|---|
| Frontend | React 19 Β· Vite Β· TypeScript Β· Tailwind CSS v4 Β· shadcn/ui |
| Router | react-router v7 |
| Backend | Convex (real-time database + server functions) |
| Deployment | SST v3 β AWS StaticSite (eu-north-1) |
| Icons | Lucide React |
| Package Manager | Bun |
- Bun installed
- A Convex account and the CLI authenticated (
npx convex login)
bun install
bun run devThis starts the Vite frontend and Convex backend in parallel, then opens the Convex dashboard.
| Script | Purpose |
|---|---|
bun run dev |
Frontend + backend concurrently |
bun run dev:frontend |
Vite dev server only |
bun run dev:backend |
Convex dev server only |
bun run build |
Type-check and production build |
bun run preview |
Preview the production build locally |
bun run lint |
TypeScript + ESLint |
VITE_CONVEX_URL is required at build/dev time. It is already set in .env.local (committed for this repo).
- Landing page β Enter a room name and a nickname. The app creates the room on Convex if it doesn't exist, or joins it if it does.
- Contestant list β Browse all entries for the selected year. Tap any to open the scoring view.
- Scoring view β Rate the current act in Music, Performance, and Vibes. Scores save instantly and sync to everyone in the room.
- Overview β A sortable table showing every contestant's average scores and overall total for the room.
roomsβ Stores room name, creation time, and connected users.ratingsβ One document per user + contestant + room combination. Stores the three category scores.
The app defaults to the latest contest year (currently 2026). To view or score a previous year, prefix the URL with /{year}/:
| Page | 2026 (default) | 2025 |
|---|---|---|
| Contestant List | /room/:roomName/contestants |
/2025/room/:roomName/contestants |
| Rate Contestant | /room/:roomName/contestant/:id |
/2025/room/:roomName/contestant/:id |
| Overview | /room/:roomName/overview |
/2025/room/:roomName/overview |
Once on a year-prefixed route, all navigation preserves the year automatically.
Update src/lib/contestants.ts:
- Add a new
contestants{YYYY}array. - Change
DEFAULT_YEAR = {YYYY}. - Add
{YYYY}toVALID_YEARS.
No other files need to change β routing, lookups, and navigation adapt automatically.
The app deploys automatically via SST v3 to an AWS StaticSite in eu-north-1, served at lagomeurovision.com.
# Build locally for production
bun run build
# Deploy with SST
npx sst deploysrc/
main.tsx # Convex client init + BrowserRouter
App.tsx # Route definitions (year-prefixed and default)
pages/ # LandingPage, ContestantListPage, ContestantRatingPage, OverviewPage
lib/ # Contestant data, year parsing, user hooks, haptics
components/ui/ # shadcn/ui components
convex/
schema.ts # Database schema with indexes
rooms.ts # Room join/create logic
ratings.ts # Submit, query, and aggregate ratings
_generated/ # Auto-generated Convex types (ignored by ESLint)
- No test runner is currently configured.
- Contestant data is hardcoded in
src/lib/contestants.ts, not fetched from an API. - User identity is tied to
localStorage(eurovisionUserId,eurovisionNickname,eurovisionRoomId). Returning to the same room with the same nickname restores your previous votes. - Convex
_generatedis auto-generated by the dev server. Do not edit manually.
MIT