Inspiration
Every sports argument I've ever had ends the same way: two people repeating "he's better because of [x reason] louder until someone gives up. As avid sports fans, there's nothing more annoying. Jordan vs. LeBron, Messi vs. Ronaldo, there are objective stats to use within all these arguments, but the debate never actually uses them. We wanted to build something that forces the argument to be an argument that doesn't favor who is louder (a volume contest). We then want it to actually score who won instead of letting whoever talked last win.
What it does
You pick a matchup: a popular one, the debate of the day, a random surprise, or literally any sport and any two players you type in. You pick a side, then go three rounds against an AI opponent, or a friend passing the same device back and forth. The AI streams real career stats and championships back at you as it argues, not vague sports-talk-radio filler. After round three, an AI judge (strict, generous, or purely statistical, your choice) scores both sides and hands down a verdict, calling out the best line of the debate. There's a tournament mode if one debate isn't enough, a dashboard tracking your win/loss record and favorite picks, and the whole thing installs as an app and works offline in demo mode.
How I built it
Next.js 16 with the App Router, React 19, and TypeScript in strict mode as the base. The Groq SDK powers the actual arguing, using llama-3.3-70b-versatile. /api/counsel streams the AI's argument token-by-token so it doesn't feel like waiting on a loading spinner, /api/judge returns a structured, scored verdict as JSON, and /api/odds and /api/assist handle the pre-debate predictions and in-debate coaching. Tailwind v4 drives the theming: light, dark, and high-contrast modes, one gold accent, no gradients, Space Grotesk for display type and IBM Plex Mono for anything stats-related, because stats should look like stats. The roster covers 350+ athletes across seven sports with self-hosted photos, and everything (save/resume, debate history, the dashboard) lives in localStorage, so there's no backend, no accounts, nothing that could leak data.
Challenges I ran into
The hardest part wasn't the streaming or the theming, it was keeping the AI honest. An LLM will happily invent a championship or a stat line if you let it, and the entire premise of GOAT Court falls apart the moment the "real career stats" aren't real enough to give a properly backed argument. That's why demo mode only has fully-scripted, stat-backed arguments for certain matchups. Every other matchup in demo mode gets rhetorical, stat-free scripts on purpose, rather than risk fabricated numbers shipping as if they were fact. The same logic extends to typed-in players outside the database: the AI still won't invent stats for names it doesn't know well (the AI will do the best it can). Getting the judge to actually score rounds consistently, instead of just declaring a winner because one side wrote more, took a fair amount of prompt iteration too. The retry-once fallback on /api/judge exists because structured JSON verdicts don't always come back clean on the first try.
What I learned
Building an AI debate judge taught us more about scoring rubrics than debate itself. Getting a model to weigh factual accuracy against argument strength, consistently, across three different judge personalities was truly difficult. We also came away with a much sharper sense of where local-first, no-backend architecture is actually the right call. GOAT Court doesn't need accounts or cross-device sync to be useful, and skipping that layer entirely kept the whole project honest about what it's actually for: a fast, private, stat-grounded argument platform, not a social one.
Built With
- groq
- groq-sdk
- llama-3.3
- nextjs
- node.js
- pwa
- react
- service-worker
- tailwindcss
- typescript
Log in or sign up for Devpost to join the conversation.