142 lines
No EOL
5.7 KiB
TypeScript
142 lines
No EOL
5.7 KiB
TypeScript
import React from 'react';
|
|
import { Question, AnswerOption, GameState, GameRole } from '../types';
|
|
import { COLORS, SHAPES } from '../constants';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
interface GameScreenProps {
|
|
question?: Question;
|
|
timeLeft: number;
|
|
totalQuestions: number;
|
|
currentQuestionIndex: number;
|
|
gameState: GameState;
|
|
role: GameRole;
|
|
onAnswer: (isCorrect: boolean) => void;
|
|
hasAnswered: boolean;
|
|
lastPointsEarned: number | null;
|
|
}
|
|
|
|
export const GameScreen: React.FC<GameScreenProps> = ({
|
|
question,
|
|
timeLeft,
|
|
totalQuestions,
|
|
currentQuestionIndex,
|
|
gameState,
|
|
role,
|
|
onAnswer,
|
|
hasAnswered,
|
|
}) => {
|
|
const isClient = role === 'CLIENT';
|
|
const displayOptions = question?.options || [];
|
|
|
|
// Timer styling logic
|
|
const isUrgent = timeLeft < 5 && timeLeft > 0;
|
|
const timerBorderColor = isUrgent ? 'border-red-500' : 'border-white';
|
|
const timerTextColor = isUrgent ? 'text-red-500' : 'text-theme-primary';
|
|
const timerAnimation = isUrgent ? 'animate-ping' : '';
|
|
|
|
return (
|
|
<div className="flex flex-col h-screen max-h-screen overflow-hidden relative">
|
|
{/* Header */}
|
|
<div className="flex justify-between items-center p-4 md:p-6">
|
|
<div className="bg-white/20 backdrop-blur-md px-6 py-2 rounded-2xl font-black text-xl shadow-sm border-2 border-white/10">
|
|
{currentQuestionIndex + 1} / {totalQuestions}
|
|
</div>
|
|
|
|
{/* Whimsical Timer */}
|
|
<div className="relative">
|
|
<div className="absolute inset-0 bg-white/20 rounded-full blur-xl animate-pulse"></div>
|
|
<div className={`bg-white ${timerTextColor} rounded-full w-20 h-20 flex items-center justify-center text-4xl font-black shadow-[0_6px_0_rgba(0,0,0,0.2)] border-4 ${timerBorderColor} ${timerAnimation} relative z-10 transition-colors duration-300`}>
|
|
{timeLeft}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white/20 backdrop-blur-md px-6 py-2 rounded-2xl font-black text-xl shadow-sm border-2 border-white/10">
|
|
{isClient ? 'Controller' : 'Host'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Question Area */}
|
|
<div className="flex-1 flex flex-col items-center justify-center p-4 md:p-8 text-center relative z-10">
|
|
{question && (
|
|
<motion.div
|
|
key={question.id}
|
|
initial={{ y: 20, opacity: 0, scale: 0.95 }}
|
|
animate={{ y: 0, opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
className="bg-white text-black p-8 md:p-12 rounded-[2rem] shadow-[0_12px_0_rgba(0,0,0,0.1)] max-w-5xl w-full border-b-8 border-gray-200"
|
|
>
|
|
<h2 className="text-2xl md:text-5xl font-black text-[#333] font-display leading-tight">
|
|
{question.text}
|
|
</h2>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Answer Grid */}
|
|
<div className="grid grid-cols-2 gap-3 md:gap-6 p-3 md:p-6 h-1/2 md:h-[45vh] pb-8 md:pb-12">
|
|
{displayOptions.map((option, idx) => {
|
|
const ShapeIcon = SHAPES[option.shape];
|
|
const colorClass = COLORS[option.color];
|
|
|
|
let opacityClass = "opacity-100";
|
|
let scaleClass = "scale-100";
|
|
|
|
// If answering phase and user answered, dim everything
|
|
if (hasAnswered) {
|
|
opacityClass = "opacity-50 cursor-not-allowed grayscale";
|
|
scaleClass = "scale-95";
|
|
}
|
|
|
|
return (
|
|
<motion.button
|
|
key={idx}
|
|
initial={{ y: 50, opacity: 0 }}
|
|
animate={{ y: 0, opacity: 1 }}
|
|
transition={{ delay: idx * 0.03, type: 'spring', stiffness: 500, damping: 30 }}
|
|
disabled={hasAnswered}
|
|
onClick={() => onAnswer(option as any)}
|
|
className={`
|
|
${colorClass} ${opacityClass} ${scaleClass}
|
|
rounded-3xl shadow-[0_8px_0_rgba(0,0,0,0.2)]
|
|
flex flex-col md:flex-row items-center justify-center md:justify-start
|
|
p-4 md:p-8
|
|
active:shadow-none active:translate-y-[8px] active:scale-95
|
|
transition-all duration-300 relative group overflow-hidden border-b-8 border-black/10
|
|
`}
|
|
>
|
|
<ShapeIcon className="absolute -right-6 -bottom-6 w-32 h-32 text-black/10 rotate-12 group-hover:rotate-45 transition-transform duration-500" />
|
|
|
|
<div className="flex-shrink-0 mb-2 md:mb-0 md:mr-6 bg-black/20 p-3 md:p-4 rounded-2xl shadow-inner">
|
|
<ShapeIcon className="w-8 h-8 md:w-12 md:h-12 text-white" fill="currentColor" />
|
|
</div>
|
|
|
|
<span className="text-lg md:text-3xl font-black text-white text-center md:text-left drop-shadow-md leading-tight relative z-10">
|
|
{option.text}
|
|
</span>
|
|
</motion.button>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* "Answer Sent" Overlay */}
|
|
<AnimatePresence>
|
|
{isClient && hasAnswered && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
|
className="absolute inset-0 bg-theme-primary/95 flex flex-col items-center justify-center z-50 p-8 text-center"
|
|
>
|
|
<motion.div
|
|
animate={{ scale: [1, 1.2, 1] }}
|
|
transition={{ repeat: Infinity, duration: 1.5 }}
|
|
className="text-6xl mb-6"
|
|
>
|
|
🚀
|
|
</motion.div>
|
|
<h2 className="text-4xl md:text-5xl font-black text-white font-display mb-4">Answer Sent!</h2>
|
|
<p className="text-xl font-bold opacity-80">Cross your fingers...</p>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}; |