Fix client answer validation bug and improve scoreboard UX
Bug Fix: - Fix stale closure bug in client answer validation where correct answers were sometimes marked incorrect. The issue occurred when players answered quickly after a question started - React's async state updates meant currentCorrectShape could hold the previous question's value. Added currentCorrectShapeRef to ensure the latest value is always used. Scoreboard Improvements: - Unified desktop/mobile layout: avatar, name, and points on first line; progress bar on second line; bonus pills on third line - Removed 5-player limit to show all players - Added vertical scrolling when player list exceeds viewport - Fixed layout to prevent content overflow issues
This commit is contained in:
parent
3d6081823c
commit
62281e1124
2 changed files with 66 additions and 31 deletions
|
|
@ -93,39 +93,29 @@ const PlayerRow: React.FC<PlayerRowProps> = ({ player, index, maxScore }) => {
|
|||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: baseDelay, duration: 0.4 }}
|
||||
className="flex flex-col md:flex-row md:items-center gap-2 md:gap-4 py-3"
|
||||
className="flex flex-col gap-2 py-3"
|
||||
>
|
||||
<div className="flex items-center justify-between md:justify-start gap-3 w-full md:w-48 md:shrink-0">
|
||||
<div className="flex items-center justify-between gap-3 w-full">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<PlayerAvatar seed={player.avatarSeed} size={32} />
|
||||
<span className="font-black text-lg font-display truncate">{player.displayName}</span>
|
||||
</div>
|
||||
<div className="md:hidden">
|
||||
<span className="font-black text-2xl font-display">
|
||||
<AnimatedNumber value={getDisplayScore()} />
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-black text-2xl font-display">
|
||||
<AnimatedNumber value={getDisplayScore()} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full md:flex-1 flex items-center gap-4">
|
||||
<div className="flex-1 h-12 bg-gray-100 rounded-full overflow-hidden relative">
|
||||
<motion.div
|
||||
className="h-full rounded-full"
|
||||
style={{ backgroundColor: player.color }}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${Math.max(barWidth, 2)}%` }}
|
||||
transition={{ duration: 0.6, delay: baseDelay + 0.1 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:block w-20 text-right">
|
||||
<span className="font-black text-2xl font-display">
|
||||
<AnimatedNumber value={getDisplayScore()} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-10 md:h-12 bg-gray-100 rounded-full overflow-hidden relative">
|
||||
<motion.div
|
||||
className="h-full rounded-full"
|
||||
style={{ backgroundColor: player.color }}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${Math.max(barWidth, 2)}%` }}
|
||||
transition={{ duration: 0.6, delay: baseDelay + 0.1 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap md:flex-nowrap items-center gap-2 w-full md:w-auto md:min-w-[280px] justify-start">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full justify-start">
|
||||
<AnimatePresence>
|
||||
{breakdown === null && (
|
||||
<motion.span
|
||||
|
|
@ -200,16 +190,16 @@ export const Scoreboard: React.FC<ScoreboardProps> = ({ players, onNext, isHost,
|
|||
...p,
|
||||
displayName: p.id === currentPlayerId ? `${p.name} (You)` : p.name
|
||||
}));
|
||||
const sortedPlayers = [...playersWithDisplayName].sort((a, b) => b.score - a.score).slice(0, 5);
|
||||
const sortedPlayers = [...playersWithDisplayName].sort((a, b) => b.score - a.score);
|
||||
const maxScore = Math.max(...sortedPlayers.map(p => p.score), 1);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen p-4 md:p-8">
|
||||
<header className="text-center mb-4 md:mb-8">
|
||||
<div className="flex flex-col h-screen p-4 md:p-8 overflow-hidden">
|
||||
<header className="text-center mb-4 md:mb-8 shrink-0">
|
||||
<h1 className="text-3xl md:text-5xl font-black text-white font-display drop-shadow-md">Scoreboard</h1>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 bg-white rounded-2xl md:rounded-[3rem] shadow-[0_20px_0_rgba(0,0,0,0.2)] p-4 md:p-12 text-gray-900 max-w-5xl w-full mx-auto relative z-10 border-4 md:border-8 border-white/50 overflow-hidden">
|
||||
<div className="flex-1 min-h-0 bg-white rounded-2xl md:rounded-[3rem] shadow-[0_20px_0_rgba(0,0,0,0.2)] p-4 md:p-12 text-gray-900 max-w-5xl w-full mx-auto relative z-10 border-4 md:border-8 border-white/50 overflow-y-auto">
|
||||
<div className="space-y-2">
|
||||
{sortedPlayers.map((player, index) => (
|
||||
<PlayerRow key={player.id} player={player} index={index} maxScore={maxScore} />
|
||||
|
|
@ -217,7 +207,7 @@ export const Scoreboard: React.FC<ScoreboardProps> = ({ players, onNext, isHost,
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 md:mt-8 flex justify-center md:justify-end max-w-5xl w-full mx-auto">
|
||||
<div className="mt-4 md:mt-8 flex justify-center md:justify-end max-w-5xl w-full mx-auto shrink-0">
|
||||
{isHost ? (
|
||||
<button
|
||||
onClick={onNext}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue