A self-hosted, fully customizable Jeopardy app for game nights — built with SvelteKit 5, PostgreSQL, and Discord login. Create custom question boards, run live rounds with friends, and spice things up with the Chaos Category mini-games.
A special category where each question is a different mini-game:
| Type | Description |
|---|---|
| ❓ Free Question | Standard Jeopardy question |
| 🟩 Wordle | Guess the hidden word letter by letter |
| 🪢 Hangman | Classic hangman with a configurable word |
| 🎡 Chaos Wheel | Spin the wheel — random point modifiers |
| 🔍 Spot the Diff | Find the wrong tile in a 10×5 image grid |
| Area | Technology |
|---|---|
| Framework | SvelteKit 2 + Svelte 5 (Runes) |
| Language | TypeScript |
| Database | PostgreSQL · Drizzle ORM |
| Auth | Auth.js v1 (Discord OAuth) |
| Styling | Scoped CSS + Tailwind CSS v4 |
| Deployment | Docker · adapter-node |
git clone https://github.com/AntiPhil/phils-jeopardy-sveltekit.git
cd phils-jeopardy-sveltekit
npm install
cp .env.example .env
| Variable | Description |
|---|---|
AUTH_SECRET |
Random secret, min. 32 chars (openssl rand -base64 32) |
AUTH_DISCORD_ID |
Discord application client ID |
AUTH_DISCORD_SECRET |
Discord application client secret |
AUTH_URL |
Public URL of the app (e.g. https://your-domain.com) |
ORIGIN |
Same as AUTH_URL — required for CSRF protection |
DATABASE_URL |
PostgreSQL connection string |
Discord OAuth setup:
.envhttp://localhost:5173/auth/callback/discordhttps://your-domain.com/auth/callback/discordnpm run db:push
npm run dev
The repository includes a production-ready docker-compose.yml with the app and PostgreSQL. The database schema is pushed automatically on every container start.
.envCopy .env.example to .env and fill in all values including:
POSTGRES_PASSWORD — password for the DB containerDATABASE_URL — use db as the host: postgresql://jeopardy:password@db:5432/phils_jeopardydocker compose up -d --build
The app is now available at http://<your-server-ip>:3000.
git pull
docker compose up -d --build
After first login, grant admin rights via SQL:
UPDATE users SET is_admin = true WHERE email = '[email protected]';
Admins can publish games so they appear for all users in the game selection screen.
Place SVG files in src/lib/assets/spot-diff/ following this naming convention:
SceneName_Original.svg ← the correct image
SceneName_Fehler_1.svg ← same image with one deliberate mistake
SceneName_Fehler_2.svg ← optional second variant
The game automatically loads all scenes at build time via import.meta.glob.
src/
├── lib/
│ ├── assets/
│ │ └── spot-diff/ # SVG scenes for Spot the Diff
│ ├── components/
│ │ ├── admin/ # CategoryEditor, QuestionEditor, GameListItem
│ │ └── comps/ # Navbar, GameSelect, PlayerSetup, QuestionModal,
│ │ # Scoreboard, SpotTheDiff, Toaster, ...
│ ├── server/
│ │ ├── db.ts # Drizzle instance
│ │ ├── schema.ts # DB schema (users, saved_games, game_sessions, game_ratings)
│ │ └── rateLimit.ts # In-memory rate limiter
│ ├── stores/
│ │ ├── game.ts # Live game state + session persistence
│ │ ├── savedGames.ts # API-backed store for saved games
│ │ └── toast.ts # Toast notification store
│ └── sounds.ts # Web Audio API (click, correct, wrong, victory)
└── routes/
├── api/
│ ├── games/ # CRUD for saved games
│ └── sessions/ # CRUD for game sessions
├── game/ # Live game board
├── game-editor/ # Game editor (authenticated)
├── how-to-play/ # Rules & mini-game guide
├── winner/ # Results & rating
├── sitemap.xml/ # Dynamic sitemap
└── +error.svelte # Branded error page
| Command | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Production build |
npm run preview |
Preview production build locally |
npm run check |
TypeScript + Svelte type check |
npm run db:push |
Push schema to database |
npm run db:generate |
Generate Drizzle migration files |
Private project — all rights reserved.