Files
Trivy/frontend/src/stores/soundStore.ts
consultoria-as ab201e113a feat: 5 categorías rotativas por partida + pool de 200 preguntas + mejoras UI
Cambios principales:
- Tablero ahora muestra 5 categorías aleatorias (de 8 disponibles)
- Pool de 200 preguntas (8 cats × 5 diffs × 5 opciones)
- Preguntas rotan aleatoriamente entre partidas
- Diseño mejorado estilo Jeopardy con efectos visuales
- Socket singleton para conexión persistente
- Nuevos sonidos: game_start, player_join, question_reveal, hover, countdown
- Control de volumen vertical
- Barra de progreso del timer en modal de preguntas
- Animaciones mejoradas con Framer Motion

Backend:
- question_service: selección aleatoria de 5 categorías
- room_manager: fix retorno de create_room
- game_events: carga board desde DB, await en enter_room

Frontend:
- Game.tsx: tablero dinámico, efectos hover, mejor scoreboard
- useSocket: singleton service, eventos con sonidos
- SoundControl: slider vertical
- soundStore: 5 nuevos efectos de sonido

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 23:44:55 +00:00

149 lines
5.4 KiB
TypeScript

import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { ThemeName } from '../types'
export type SoundEffect =
| 'correct'
| 'incorrect'
| 'steal'
| 'timer_tick'
| 'timer_urgent'
| 'victory'
| 'defeat'
| 'select'
| 'game_start'
| 'player_join'
| 'question_reveal'
| 'hover'
| 'countdown'
interface SoundState {
volume: number
muted: boolean
soundsLoaded: boolean
currentLoadedTheme: ThemeName | null
setVolume: (volume: number) => void
setMuted: (muted: boolean) => void
toggleMute: () => void
setSoundsLoaded: (loaded: boolean) => void
setCurrentLoadedTheme: (theme: ThemeName | null) => void
}
export const useSoundStore = create<SoundState>()(
persist(
(set) => ({
volume: 0.7,
muted: false,
soundsLoaded: false,
currentLoadedTheme: null,
setVolume: (volume) => set({ volume: Math.max(0, Math.min(1, volume)) }),
setMuted: (muted) => set({ muted }),
toggleMute: () => set((state) => ({ muted: !state.muted })),
setSoundsLoaded: (soundsLoaded) => set({ soundsLoaded }),
setCurrentLoadedTheme: (currentLoadedTheme) => set({ currentLoadedTheme }),
}),
{
name: 'trivia-sound',
partialize: (state) => ({ volume: state.volume, muted: state.muted }),
}
)
)
// Sound file paths per theme
// All themes share the same base sounds but can be customized 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',
game_start: '/sounds/drrr/game_start.mp3',
player_join: '/sounds/drrr/player_join.mp3',
question_reveal: '/sounds/drrr/question_reveal.mp3',
hover: '/sounds/drrr/hover.mp3',
countdown: '/sounds/drrr/countdown.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',
game_start: '/sounds/retro/game_start.mp3',
player_join: '/sounds/retro/player_join.mp3',
question_reveal: '/sounds/retro/question_reveal.mp3',
hover: '/sounds/retro/hover.mp3',
countdown: '/sounds/retro/countdown.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',
game_start: '/sounds/minimal/game_start.mp3',
player_join: '/sounds/minimal/player_join.mp3',
question_reveal: '/sounds/minimal/question_reveal.mp3',
hover: '/sounds/minimal/hover.mp3',
countdown: '/sounds/minimal/countdown.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',
game_start: '/sounds/rgb/game_start.mp3',
player_join: '/sounds/rgb/player_join.mp3',
question_reveal: '/sounds/rgb/question_reveal.mp3',
hover: '/sounds/rgb/hover.mp3',
countdown: '/sounds/rgb/countdown.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',
game_start: '/sounds/anime/game_start.mp3',
player_join: '/sounds/anime/player_join.mp3',
question_reveal: '/sounds/anime/question_reveal.mp3',
hover: '/sounds/anime/hover.mp3',
countdown: '/sounds/anime/countdown.mp3',
},
}
// Fallback sounds using Web Audio API generated tones
// These are used when actual sound files are not available
export const fallbackSoundConfigs: Record<SoundEffect, { frequency: number; duration: number; type: OscillatorType }> = {
correct: { frequency: 880, duration: 0.15, type: 'sine' },
incorrect: { frequency: 220, duration: 0.3, type: 'square' },
steal: { frequency: 660, duration: 0.2, type: 'sawtooth' },
timer_tick: { frequency: 440, duration: 0.05, type: 'sine' },
timer_urgent: { frequency: 880, duration: 0.1, type: 'square' },
victory: { frequency: 523, duration: 0.5, type: 'sine' },
defeat: { frequency: 196, duration: 0.5, type: 'sine' },
select: { frequency: 600, duration: 0.08, type: 'sine' },
game_start: { frequency: 440, duration: 0.4, type: 'sine' },
player_join: { frequency: 520, duration: 0.12, type: 'sine' },
question_reveal: { frequency: 700, duration: 0.2, type: 'triangle' },
hover: { frequency: 400, duration: 0.03, type: 'sine' },
countdown: { frequency: 600, duration: 0.15, type: 'square' },
}