This is a submission for Weekend Challenge: Passion Edition
What I Built
It's July 12th, the World Cup is in full swing, and I'm watching a stoppage time winner go in and thinking, not "what a goal," but "that's a data point I want to capture."
That's the itch FanForge scratches. It watches live events, starting with the 2026 FIFA World Cup, and scores how much passion is packed into each moment on a 0–100 scale. Every scored moment becomes instantly searchable, and the fans who show up the most get rewarded with tournament linked "Passion Points."
Here's the twist: I didn't want to build "a World Cup app." Somewhere around hour six I realized the actual interesting problem wasn't football, it was passion itself and passion doesn't care what the fandom is. So the same scoring engine that grades a last-minute winner also grades a Vim-vs-Emacs flame war, and my own 1am "finally fixed the bug" moment. Any fandom is one plugin away. If the challenge's prompt was "any angle is fair game," I took that literally and built the angle-agnostic version.
One-line pitch: A passion-scoring engine that watches what people love, makes it searchable, tells the story out loud, and remembers it forever.
Demo
- 🔗 Live Website: [https://fanforge-pce7.onrender.com]
Want to poke at it yourself instead of trusting a screenshot? Two commands, no API keys, no database to install:
npm install
npm run dev
You'll land on the marketing page; the actual product lives at /app. On first boot the server auto-ingests and scores a seeded dataset 66 moments across 7 World Cup matches plus rivalry and personal samples, so the dashboard isn't staring back at you empty. Worth exploring: /app (live feed), /app/explore (instant search), /app/tournament (hype-off arena), /app/personal (passion tracker).
Code
Soham-0047
/
Ardor
A passion-scoring engine that watches what people love, makes it searchable, tells the story out loud, and remembers it forever.
🔥 FanForge
An open, pluggable passion-intelligence platform — MERN edition
A passion-scoring engine that watches what people love, makes it searchable, tells the story out loud, and remembers it forever.
FanForge measures, narrates, and rewards fandom in real time. It watches live events — starting with the 2026 FIFA World Cup — scores how much passion is in each moment, makes every scored moment instantly searchable, turns the biggest moments into spoken hype clips, and hands out verifiable "Passion Points" badges to the most engaged fans.
It's built as a plugin framework, not a single-sport app. The same engine scores a World Cup match, a two-sided rivalry (Vim vs Emacs, React vs Vue, the GOAT debate), or a solo dev's personal passion-project streak.
⚙️ Stack — MERN + sponsor tech
Built on the MERN stack, as requested (this pivots the original spec's Next.js + Neon Postgres design):
Layer
Tech
fanforge/
├── server/ # Express + Node + MongoDB
│ └── src/
│ ├── plugins/ # FanPlugin interface + worldcup / rivalry / personal + registry
│ ├── services/ # AI scoring + heuristic fallback, search, warehouse aggregation, audio, badges
│ ├── models/ # Mongoose: Event, User, Vote, Tournament
│ └── data/ # seeded World Cup dataset (60 events)
└── client/ # React + Vite + TypeScript + Tailwind dashboard
CI runs typecheck, a client build, and a 15-assertion smoke test — ingest → score → dedupe → search → aggregate → badge → vote-uniqueness — against an in-memory MongoDB on every push. No secrets needed to go green.
How I Built It
The loop, end to end:
Match data & user actions
→ Plugin router (worldcup · rivalry · personal)
→ Passion scoring (Google AI structured extraction → heuristic fallback)
→ MongoDB (store + instant passion-ranked search + passion warehouse)
→ ElevenLabs (spoken hype clips, cached)
→ Solana devnet (proof-of-fandom badges)
→ Dashboard (feed · search · trends · arena · tracker)
1. Ingest. Each plugin's fetchEvents() pulls its own source. The World Cup plugin hits football-data.org — except its free tier is delayed, not live, which I didn't fully appreciate until I was staring at a scoreboard that was 15 minutes behind Twitter. Lesson learned: for a demo that has to work on judging day regardless of what's happening in the world, a seeded 60-event dataset isn't a cop-out, it's the actual reliable path. Rivalry and personal plugins skip the API problem entirely and just take direct user submissions.
2. Score. This is the part I was most nervous about. I send the raw event text to Google AI (Gemini) and ask for a schema-constrained JSON object back — passion_score, sentiment, key_moment, one_line_recap — not a chat reply I then have to parse and hope for the best. Turning "unstructured fan noise" into a number you can sort, filter, and chart is the whole product, honestly. And because a hackathon demo dying on a rate limit is the most avoidable kind of embarrassing, there's a deterministic heuristic scorer sitting behind it — no service token, no problem, the pipeline just keeps moving.
3. Fan out. Every scored moment lands in MongoDB, which does double duty as both the searchable store and the analytics warehouse via aggregation (/api/warehouse answers "show me passion by hour, by team, by sentiment" without a separate data pipeline). The loudest moments get narrated by ElevenLabs and cached to disk — I generate a clip exactly once, never again, both to save quota and because regenerating the same hype clip on every page load is a silly way to burn an API budget. And a Solana devnet mint can fire for a badge, simulated and clearly labelled if no keypair is configured.
4. Surface. A React + Vite + TS + Tailwind dashboard ties it together: a self-refreshing feed, instant search, passion-over-time trends, a fan-vs-fan Hype-Off bracket where votes earn Passion Points and streaks mint badges, and an off-season personal passion tracker for journaling your own project's ups and downs. There's an "Under the hood" panel that just tells you the truth — which integrations are live right now versus quietly running on their fallback. I added that mostly for myself, so I'd stop wondering, but it turned out to be the most-asked-about feature when I showed friends.
The plugin contract, which is really the whole architectural bet of the project:
interface FanPlugin {
id: string;
domain: 'worldcup' | 'rivalry' | 'personal';
displayName: string;
description: string;
emoji: string;
voicePersona: string; // narration style hint
scoringPrompt: string; // what "passion" means in this domain
fetchEvents(): Promise<RawEvent[]>;
normalizeUserAction?(input): RawEvent;
}
Implement that interface, register it in server/src/plugins/registry.ts, and your domain is scored, stored, searchable, and surfaced zero changes to the core. Three ship in the box (World Cup, Rivalry, Personal Passion), but the whole point is that a fourth is an afternoon of work, not a rewrite.
The lesson that actually stuck with me: the single design decision that mattered most wasn't which sponsor tech I used it was deciding, on day one, that the demo must survive with zero keys. Every integration got a real fallback: heuristic scorer, Mongo text search, seeded dataset, simulated badges. That decision felt like over-engineering at 10am on day one. By 1am on day two, when I was debugging on a laptop with spotty wifi and testing whether the app still worked, it was the only reason anything worked at all. Building for the failure case first, instead of bolting it on after, is a genuinely different way of writing software, and I'd do it again even without a deadline forcing my hand. (Fittingly, logging that exact 1am debugging win into FanForge's own personal tracker scored an 88. Ecstatic.)
Prize Categories
- Best use of Solana
- Best use of ElevenLabs
- Best use of Google AI
Top comments (0)