feat(phase4): Complete frontend with themes, replay, and results

Visual Effects:
- Add effects.css with neon glow, CRT scanlines, glitch, sparkles, RGB shift
- Animations work with all 5 themes (DRRR, Retro, Minimal, RGB, Anime)

Game Finished Handler:
- Add gameResult state to gameStore (winner, scores, replay, achievements)
- Handle game_finished WebSocket event in useSocket
- Store achievements unlocked by all players

Results Page:
- Show winner with animation (or tie)
- Display final scores with staggered animation
- List achievements unlocked per player
- Buttons for replay and new game

Replay Player:
- Fetch replay from API by code
- Auto-playback with configurable speed (1x, 2x, 4x)
- Play/Pause and timeline controls
- Events sidebar for navigation
- Animated event transitions

Lobby Updates:
- Load categories from API on mount
- Display available categories with icons
- Backend generates board (no hardcoded data)

TypeScript Fixes:
- Add vite-env.d.ts for import.meta.env types
- Fix ringColor style issues
- Remove unused imports

Build verified: npm run build succeeds

Phase 4 tasks completed:
- F4.1: CSS effects for themes
- F4.2: Results page with achievements
- F4.3: Replay player
- F4.4: game_finished handler
- F4.5: Lobby API integration
- F4.6: Build verification and fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 08:40:36 +00:00
parent 0141153653
commit 3e91305e46
13 changed files with 862 additions and 296 deletions

View File

@@ -1,13 +1,13 @@
import { useEffect, useRef, useCallback } from 'react'
import { io, Socket } from 'socket.io-client'
import { useGameStore } from '../stores/gameStore'
import type { GameRoom, ChatMessage, AnswerResult } from '../types'
import type { GameRoom, ChatMessage, AnswerResult, Achievement } from '../types'
const SOCKET_URL = import.meta.env.VITE_WS_URL || 'http://localhost:8000'
export function useSocket() {
const socketRef = useRef<Socket | null>(null)
const { setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setTimerEnd } =
const { setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setTimerEnd, setGameResult } =
useGameStore()
useEffect(() => {
@@ -95,10 +95,34 @@ export function useSocket() {
console.log(`${data.player_name} reacted with ${data.emoji}`)
})
socket.on('game_finished', (data: {
room: GameRoom
winner: 'A' | 'B' | null
final_scores: { A: number; B: number }
replay_code: string | null
achievements_unlocked: Array<{
player_name: string
team: 'A' | 'B'
achievement: unknown
}>
}) => {
setRoom(data.room)
setGameResult({
winner: data.winner,
finalScores: data.final_scores,
replayCode: data.replay_code,
achievementsUnlocked: data.achievements_unlocked.map(a => ({
player_name: a.player_name,
team: a.team,
achievement: a.achievement as Achievement
}))
})
})
return () => {
socket.disconnect()
}
}, [setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setTimerEnd])
}, [setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setTimerEnd, setGameResult])
// Socket methods
const createRoom = useCallback((playerName: string) => {