Initial commit

This commit is contained in:
Joey Yakimowich-Payne 2026-01-13 07:23:30 -07:00
commit c87ebf0a74
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
22 changed files with 4973 additions and 0 deletions

142
components/GameScreen.tsx Normal file
View file

@ -0,0 +1,142 @@
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-[#46178f]';
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-[#46178f]/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>
);
};