A community prediction platform for Dota 2 Tier 1 tournaments, inspired by the prediction format from the "We Say Things" podcast hosted by SUNSfan and syndereN.
Before every major, SUNSfan and syndereN make their picks - Top 3 placements, a Dark Horse, and a White Donkey. D2 Predictor lets the entire community do the same, compete on leaderboards, and see how they stack up against the hosts.
Non-commercial fan project. Built with full credit and thanks to SUNSfan and syndereN for the inspiration.
- Predictions - Pick your Top 3 (with placements), Dark Horse, and White Donkey before each tournament
- Automatic Scoring - Save results and all scores calculate in a single step
- Leaderboards - Per-tournament and all-time rankings with scrollable sticky-header tables
- Homepage Polls - Signed-in users can vote on the pinned community poll; results stay visible for 5 days after close
- Host Predictions - SUNSfan & syndereN's picks hidden until lock, then revealed with scores
- Final Standings - Dota 2-style tied placements (5-6th, 9-11th), seed deltas, tie-aware DH/WD logic
- Countdown Timer - Live countdown on upcoming tournaments until predictions close
- Prediction Lock - Picks lock automatically at tournament start, enforced server-side
- Community Picks - Most-picked and second most-picked team per slot after lock
- Public Profiles - Stats, rank, favorite team, and full prediction history per user
- Status Filters - Filter tournaments by All / Upcoming / Active / Completed / Scored
- Auto Transitions - Tournaments auto-activate when the start date passes
- Admin Panel - Full CRUD for teams, tournaments, homepage polls, and site updates; drag-and-drop power rankings; placement format presets
- Tournament & Team Notes - Optional info banners and per-team hover tooltips (hidden when empty)
- Updates & Feedback - Database-backed changelog page and signed-in feedback form with email delivery via Resend
| Pick | Points | Condition |
|---|---|---|
| Top 3 (×3) | 2 pts each | Exact placement match |
| Top 3 (×3) | 1 pt each | Right team, wrong placement (still top 3) |
| Dark Horse | 1 pt | Team finishes 4+ places above seed |
| White Donkey | 1 pt | Team finishes 4+ places below seed |
| Max per tournament | 8 pts |
- Seeds are set by admin via datdota Glicko-2 ratings
- DH/WD picks can overlap with Top 3
- Tied placements: Dark Horse uses best position, White Donkey uses worst (favors user)
- Threshold is centralized in
src/lib/constants.ts
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, TypeScript strict) |
| Database & Auth | Supabase (PostgreSQL + Auth + Storage) |
| UI | Tailwind CSS v4 + shadcn/ui |
| Drag & Drop | @dnd-kit |
| Resend | |
| Hosting | Vercel |
| Auth Providers | Google + Discord OAuth (PKCE) |
- Node.js 18+
- Supabase CLI
- A Supabase project (free tier works)
-
Clone and install
git clone https://github.com/Raffiesaurus/d2-predictor.git cd d2-predictor npm install -
Configure environment
cp .env.example .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key SUPABASE_SECRET_KEY=your-secret-key NEXT_PUBLIC_SITE_URL=http://localhost:3000 RESEND_API_KEY=your-resend-api-key FEEDBACK_FROM_EMAIL="D2 Predictor Feedback <sender@yourdomain.com>"
-
Run migrations and seed
npx supabase db push npx supabase db reset --seed-only
The site updates and the initial homepage poll are seeded by the SQL migrations.
-
Grant admin access (after first sign-in)
UPDATE profiles SET is_admin = true WHERE id = 'your-user-id';
-
Start dev server
npm run dev
src/
├── app/
│ ├── page.tsx # Homepage - tournament list, pinned poll, search + status filters
│ ├── homepage-poll.tsx # Client homepage poll card + post-vote results state
│ ├── auth/ # OAuth (Google + Discord), PKCE flow
│ ├── account/ # Profile settings (name, avatar, favorite team)
│ ├── leaderboards/ # All-time + per-tournament leaderboards
│ ├── rules/ # Scoring rules, FAQ, credits
│ ├── updates/ # Public changelog page
│ ├── tournaments/[slug]/ # Tournament detail (standings, hosts, predictions, scores)
│ ├── user/[id]/ # Public user profiles
│ └── admin/ # Teams, rankings, tournaments, polls, updates, results
├── components/
│ ├── countdown.tsx # Live countdown timer
│ ├── nav.tsx # Navigation bar
│ └── ui/ # shadcn/ui components
├── lib/
│ ├── constants.ts # Types, SEED_DIFF_THRESHOLD, placement presets
│ ├── site-content.ts # Shared site poll/update types and visibility helpers
│ ├── site-content-server.ts # Server-side site poll/update queries
│ ├── tournament-status.ts # Auto upcoming→active transition
│ └── supabase/ # Client, server, admin, middleware helpers
supabase/
├── migrations/ # SQL migration files, including site content tables
└── seed.sql # 16 Dota 2 teams + host setup
- A better name 😭
- Liquipedia API integration (pending access)
- Full hero icons and team logos
- Social sharing / SEO
AGPL-3.0 - © 2026 Raffiesaurus
SUNSfan and syndereN - for the "We Say Things" podcast and the prediction format that inspired this project.