feat: Initial project structure for WebTriviasMulti

- Backend: FastAPI + Python-SocketIO + SQLAlchemy
  - Models for categories, questions, game sessions, events
  - AI services for answer validation and question generation (Claude)
  - Room management with Redis
  - Game logic with stealing mechanics
  - Admin API for question management

- Frontend: React + Vite + TypeScript + Tailwind
  - 5 visual themes (DRRR, Retro, Minimal, RGB, Anime 90s)
  - Real-time game with Socket.IO
  - Achievement system
  - Replay functionality
  - Sound effects per theme

- Docker Compose for deployment
- Design documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 07:50:48 +00:00
commit 43021b9c3c
57 changed files with 5446 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { ThemeName } from '../types'
type SoundEffect =
| 'correct'
| 'incorrect'
| 'steal'
| 'timer_tick'
| 'timer_urgent'
| 'victory'
| 'defeat'
| 'select'
interface SoundState {
volume: number
muted: boolean
setVolume: (volume: number) => void
setMuted: (muted: boolean) => void
toggleMute: () => void
}
export const useSoundStore = create<SoundState>()(
persist(
(set) => ({
volume: 0.7,
muted: false,
setVolume: (volume) => set({ volume }),
setMuted: (muted) => set({ muted }),
toggleMute: () => set((state) => ({ muted: !state.muted })),
}),
{
name: 'trivia-sound',
}
)
)
// Sound file paths per theme
export const soundPaths: Record<ThemeName, Record<SoundEffect, string>> = {
drrr: {
correct: '/sounds/drrr/correct.mp3',
incorrect: '/sounds/drrr/incorrect.mp3',
steal: '/sounds/drrr/steal.mp3',
timer_tick: '/sounds/drrr/tick.mp3',
timer_urgent: '/sounds/drrr/urgent.mp3',
victory: '/sounds/drrr/victory.mp3',
defeat: '/sounds/drrr/defeat.mp3',
select: '/sounds/drrr/select.mp3',
},
retro: {
correct: '/sounds/retro/correct.mp3',
incorrect: '/sounds/retro/incorrect.mp3',
steal: '/sounds/retro/steal.mp3',
timer_tick: '/sounds/retro/tick.mp3',
timer_urgent: '/sounds/retro/urgent.mp3',
victory: '/sounds/retro/victory.mp3',
defeat: '/sounds/retro/defeat.mp3',
select: '/sounds/retro/select.mp3',
},
minimal: {
correct: '/sounds/minimal/correct.mp3',
incorrect: '/sounds/minimal/incorrect.mp3',
steal: '/sounds/minimal/steal.mp3',
timer_tick: '/sounds/minimal/tick.mp3',
timer_urgent: '/sounds/minimal/urgent.mp3',
victory: '/sounds/minimal/victory.mp3',
defeat: '/sounds/minimal/defeat.mp3',
select: '/sounds/minimal/select.mp3',
},
rgb: {
correct: '/sounds/rgb/correct.mp3',
incorrect: '/sounds/rgb/incorrect.mp3',
steal: '/sounds/rgb/steal.mp3',
timer_tick: '/sounds/rgb/tick.mp3',
timer_urgent: '/sounds/rgb/urgent.mp3',
victory: '/sounds/rgb/victory.mp3',
defeat: '/sounds/rgb/defeat.mp3',
select: '/sounds/rgb/select.mp3',
},
anime: {
correct: '/sounds/anime/correct.mp3',
incorrect: '/sounds/anime/incorrect.mp3',
steal: '/sounds/anime/steal.mp3',
timer_tick: '/sounds/anime/tick.mp3',
timer_urgent: '/sounds/anime/urgent.mp3',
victory: '/sounds/anime/victory.mp3',
defeat: '/sounds/anime/defeat.mp3',
select: '/sounds/anime/select.mp3',
},
}