import React, { useState, useEffect } from 'react'; import { Player, PointsBreakdown } from '../types'; import { motion, AnimatePresence, useSpring, useTransform } from 'framer-motion'; import { Loader2, Flame, Rocket, Zap, X } from 'lucide-react'; import { PlayerAvatar } from './PlayerAvatar'; const AnimatedNumber: React.FC<{ value: number; duration?: number }> = ({ value, duration = 600 }) => { const spring = useSpring(0, { duration }); const display = useTransform(spring, (latest) => Math.round(latest)); const [displayValue, setDisplayValue] = useState(0); useEffect(() => { spring.set(value); const unsubscribe = display.on('change', (v) => setDisplayValue(v)); return () => unsubscribe(); }, [value, spring, display]); return {displayValue}; }; interface BonusBadgeProps { points: number; label: string; icon: React.ReactNode; color: string; delay: number; } const BonusBadge: React.FC = ({ points, label, icon, color, delay }) => ( {icon} +{points} {label} ); const PenaltyBadge: React.FC<{ points: number; delay: number }> = ({ points, delay }) => ( -{points} ); interface PlayerRowProps { player: Player & { displayName: string }; index: number; maxScore: number; } const PlayerRow: React.FC = ({ player, index, maxScore }) => { const [phase, setPhase] = useState(0); const breakdown = player.pointsBreakdown; const baseDelay = index * 0.3; useEffect(() => { const timers: ReturnType[] = []; timers.push(setTimeout(() => setPhase(1), (baseDelay + 0.2) * 1000)); if (breakdown) { if (breakdown.streakBonus > 0) timers.push(setTimeout(() => setPhase(2), (baseDelay + 0.8) * 1000)); if (breakdown.comebackBonus > 0) timers.push(setTimeout(() => setPhase(3), (baseDelay + 1.2) * 1000)); if (breakdown.firstCorrectBonus > 0) timers.push(setTimeout(() => setPhase(4), (baseDelay + 1.6) * 1000)); } return () => timers.forEach(clearTimeout); }, [baseDelay, breakdown]); const getDisplayScore = () => { if (!breakdown) return player.previousScore; let score = player.previousScore; if (phase >= 1) score += breakdown.basePoints - breakdown.penalty; if (phase >= 2) score += breakdown.streakBonus; if (phase >= 3) score += breakdown.comebackBonus; if (phase >= 4) score += breakdown.firstCorrectBonus; return Math.max(0, score); }; const barWidth = maxScore > 0 ? (getDisplayScore() / maxScore) * 100 : 0; return (
{player.displayName}
{breakdown === null && ( No answer )} {breakdown && breakdown.penalty > 0 && phase >= 1 && ( )} {breakdown && breakdown.basePoints > 0 && phase >= 1 && ( +{breakdown.basePoints} )} {breakdown && breakdown.streakBonus > 0 && phase >= 2 && ( } color="bg-amber-500" delay={0} /> )} {breakdown && breakdown.comebackBonus > 0 && phase >= 3 && ( } color="bg-blue-500" delay={0} /> )} {breakdown && breakdown.firstCorrectBonus > 0 && phase >= 4 && ( } color="bg-yellow-500" delay={0} /> )}
); }; interface ScoreboardProps { players: Player[]; onNext: () => void; isHost: boolean; currentPlayerId: string | null; } export const Scoreboard: React.FC = ({ players, onNext, isHost, currentPlayerId }) => { const playersWithDisplayName = players.map(p => ({ ...p, displayName: p.id === currentPlayerId ? `${p.name} (You)` : p.name })); const sortedPlayers = [...playersWithDisplayName].sort((a, b) => b.score - a.score); const maxScore = Math.max(...sortedPlayers.map(p => Math.max(p.score, p.previousScore)), 1); return (

Scoreboard

{sortedPlayers.map((player, index) => ( ))}
{isHost ? ( ) : (
Waiting for host...
)}
); };