|
|
import React, { useState, useEffect } from 'react';
|
|
|
import { Link } from 'react-router-dom';
|
|
|
import { createPageUrl } from '@/utils';
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
import { Brain, CheckCircle2, XCircle, ArrowLeft, Trophy, RotateCcw, Sparkles, Target } from 'lucide-react';
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
import { Progress } from '@/components/ui/progress';
|
|
|
import confetti from 'canvas-confetti';
|
|
|
|
|
|
const questions = [
|
|
|
{
|
|
|
id: 1,
|
|
|
question: "In YOLO (You Only Look Once), what makes it faster than traditional object detection methods?",
|
|
|
options: [
|
|
|
"It uses smaller images",
|
|
|
"It processes the entire image in a single pass instead of multiple regions",
|
|
|
"It only detects one object at a time",
|
|
|
"It uses black and white images"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Object Detection"
|
|
|
},
|
|
|
{
|
|
|
id: 2,
|
|
|
question: "What does a confidence score of 95% mean in object detection?",
|
|
|
options: [
|
|
|
"The object is 95% visible in the image",
|
|
|
"The AI is 95% certain about its prediction",
|
|
|
"95% of the object is inside the bounding box",
|
|
|
"The detection took 95% of the processing time"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Object Detection"
|
|
|
},
|
|
|
{
|
|
|
id: 3,
|
|
|
question: "How many keypoints does a standard pose estimation model typically track on the human body?",
|
|
|
options: [
|
|
|
"7 keypoints",
|
|
|
"10 keypoints",
|
|
|
"17 keypoints",
|
|
|
"25 keypoints"
|
|
|
],
|
|
|
correct: 2,
|
|
|
topic: "Pose Estimation"
|
|
|
},
|
|
|
{
|
|
|
id: 4,
|
|
|
question: "What are 'skeleton lines' in pose estimation?",
|
|
|
options: [
|
|
|
"Lines that show bones in an X-ray",
|
|
|
"Connections between keypoints showing body structure",
|
|
|
"Errors in the detection algorithm",
|
|
|
"Grid lines dividing the image"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Pose Estimation"
|
|
|
},
|
|
|
{
|
|
|
id: 5,
|
|
|
question: "Which of these is NOT one of the 7 basic emotions in FER (Facial Emotion Recognition)?",
|
|
|
options: [
|
|
|
"Fear",
|
|
|
"Disgust",
|
|
|
"Confused",
|
|
|
"Surprise"
|
|
|
],
|
|
|
correct: 2,
|
|
|
topic: "Emotion Recognition"
|
|
|
},
|
|
|
{
|
|
|
id: 6,
|
|
|
question: "Why do facial emotion recognition models use 48×48 pixel images?",
|
|
|
options: [
|
|
|
"Larger images don't work with AI",
|
|
|
"Smaller size allows faster processing while retaining key facial features",
|
|
|
"48×48 is the size of human faces",
|
|
|
"It's the only size cameras can capture"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Emotion Recognition"
|
|
|
},
|
|
|
{
|
|
|
id: 7,
|
|
|
question: "Which technology would be BEST for analyzing a dancer's movements in real-time?",
|
|
|
options: [
|
|
|
"Object Detection",
|
|
|
"Emotion Recognition",
|
|
|
"Pose Estimation",
|
|
|
"Voice Recognition"
|
|
|
],
|
|
|
correct: 2,
|
|
|
topic: "Pose Estimation"
|
|
|
},
|
|
|
{
|
|
|
id: 8,
|
|
|
question: "In object detection, what happens during 'non-max suppression'?",
|
|
|
options: [
|
|
|
"The AI stops detecting objects",
|
|
|
"Duplicate or overlapping bounding boxes are removed",
|
|
|
"The confidence threshold is lowered",
|
|
|
"The image brightness is reduced"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Object Detection"
|
|
|
},
|
|
|
{
|
|
|
id: 9,
|
|
|
question: "Why is consent important before using emotion recognition technology on someone?",
|
|
|
options: [
|
|
|
"It makes the AI more accurate",
|
|
|
"People have a right to privacy and to know when they're being analyzed",
|
|
|
"It's required by all cameras",
|
|
|
"Consent improves the lighting in photos"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Emotion Recognition"
|
|
|
},
|
|
|
{
|
|
|
id: 10,
|
|
|
question: "What is the main advantage of using grayscale images in emotion recognition instead of color?",
|
|
|
options: [
|
|
|
"Grayscale images look more professional",
|
|
|
"Color doesn't exist in emotions",
|
|
|
"It reduces processing complexity and focuses on facial structure rather than skin tone",
|
|
|
"Grayscale cameras are cheaper"
|
|
|
],
|
|
|
correct: 2,
|
|
|
topic: "Emotion Recognition"
|
|
|
},
|
|
|
{
|
|
|
id: 11,
|
|
|
question: "If pose estimation detects 2 people in a video, how many total keypoints might it track?",
|
|
|
options: [
|
|
|
"17 keypoints total",
|
|
|
"34 keypoints (17 per person)",
|
|
|
"25 keypoints total",
|
|
|
"10 keypoints total"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Pose Estimation"
|
|
|
},
|
|
|
{
|
|
|
id: 12,
|
|
|
question: "What is a potential bias concern with emotion recognition AI?",
|
|
|
options: [
|
|
|
"It works too slowly",
|
|
|
"It might perform differently across different cultures, ages, or demographics",
|
|
|
"It can only detect happy emotions",
|
|
|
"It requires too much computer power"
|
|
|
],
|
|
|
correct: 1,
|
|
|
topic: "Emotion Recognition"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
|
|
|
const COMPLETED_KEY = 'completedChapters_v1';
|
|
|
const TTL_MS = 60 * 60 * 1000;
|
|
|
|
|
|
function checkLessonsCompleted() {
|
|
|
try {
|
|
|
const raw = localStorage.getItem(COMPLETED_KEY);
|
|
|
if (!raw) return false;
|
|
|
|
|
|
const parsed = JSON.parse(raw);
|
|
|
const savedAt = parsed?.savedAt;
|
|
|
const value = parsed?.value;
|
|
|
|
|
|
if (!Array.isArray(value) || typeof savedAt !== 'number') return false;
|
|
|
|
|
|
if (Date.now() - savedAt > TTL_MS) {
|
|
|
localStorage.removeItem(COMPLETED_KEY);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return value.length === 3;
|
|
|
} catch {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export default function Quiz() {
|
|
|
const [started, setStarted] = useState(false);
|
|
|
const [currentQuestion, setCurrentQuestion] = useState(0);
|
|
|
const [selectedAnswer, setSelectedAnswer] = useState(null);
|
|
|
const [showResult, setShowResult] = useState(false);
|
|
|
const [score, setScore] = useState(0);
|
|
|
const [answers, setAnswers] = useState([]);
|
|
|
const [finished, setFinished] = useState(false);
|
|
|
|
|
|
const [allLessonsCompleted, setAllLessonsCompleted] = useState(checkLessonsCompleted);
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (finished && score === questions.length) {
|
|
|
triggerConfetti();
|
|
|
}
|
|
|
}, [finished, score]);
|
|
|
|
|
|
const triggerConfetti = () => {
|
|
|
const duration = 5 * 1000;
|
|
|
const animationEnd = Date.now() + duration;
|
|
|
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
|
|
|
|
|
|
const random = (min, max) => Math.random() * (max - min) + min;
|
|
|
|
|
|
const interval = setInterval(function() {
|
|
|
const timeLeft = animationEnd - Date.now();
|
|
|
|
|
|
if (timeLeft <= 0) {
|
|
|
return clearInterval(interval);
|
|
|
}
|
|
|
|
|
|
const particleCount = 50 * (timeLeft / duration);
|
|
|
|
|
|
|
|
|
confetti({
|
|
|
...defaults,
|
|
|
particleCount,
|
|
|
origin: { x: random(0.1, 0.3), y: Math.random() - 0.2 }
|
|
|
});
|
|
|
confetti({
|
|
|
...defaults,
|
|
|
particleCount,
|
|
|
origin: { x: random(0.7, 0.9), y: Math.random() - 0.2 }
|
|
|
});
|
|
|
}, 250);
|
|
|
};
|
|
|
|
|
|
const handleStart = () => {
|
|
|
setStarted(true);
|
|
|
setCurrentQuestion(0);
|
|
|
setScore(0);
|
|
|
setAnswers([]);
|
|
|
setFinished(false);
|
|
|
};
|
|
|
|
|
|
const handleAnswer = (index) => {
|
|
|
if (showResult) return;
|
|
|
|
|
|
setSelectedAnswer(index);
|
|
|
setShowResult(true);
|
|
|
|
|
|
const isCorrect = index === questions[currentQuestion].correct;
|
|
|
if (isCorrect) {
|
|
|
setScore(score + 1);
|
|
|
}
|
|
|
|
|
|
setAnswers([...answers, { questionId: currentQuestion, selected: index, correct: isCorrect }]);
|
|
|
};
|
|
|
|
|
|
const handleNext = () => {
|
|
|
if (currentQuestion < questions.length - 1) {
|
|
|
setCurrentQuestion(currentQuestion + 1);
|
|
|
setSelectedAnswer(null);
|
|
|
setShowResult(false);
|
|
|
} else {
|
|
|
setFinished(true);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const getScoreMessage = () => {
|
|
|
const percentage = (score / questions.length) * 100;
|
|
|
if (percentage === 100) return { emoji: "🏆", message: "PERFECT SCORE! You're an AI Vision Master!" };
|
|
|
if (percentage >= 80) return { emoji: "🌟", message: "Amazing! You really know your AI stuff!" };
|
|
|
if (percentage >= 60) return { emoji: "👍", message: "Good job! Keep learning and you'll be an expert!" };
|
|
|
if (percentage >= 40) return { emoji: "📚", message: "Nice try! Review the lessons and try again!" };
|
|
|
return { emoji: "💪", message: "Don't give up! Go through the lessons and come back stronger!" };
|
|
|
};
|
|
|
|
|
|
|
|
|
if (!started) {
|
|
|
return (
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 pt-24 pb-12 px-6">
|
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
|
|
<div className="absolute top-20 left-10 w-72 h-72 bg-yellow-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
<div className="absolute bottom-20 right-10 w-96 h-96 bg-orange-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
</div>
|
|
|
|
|
|
<div className="max-w-2xl mx-auto relative text-center">
|
|
|
<Link to={createPageUrl('Home')}>
|
|
|
<Button className="text-white/70 hover:text-white mb-8 bg-transparent border-0 hover:bg-white/5">
|
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
|
Back to Home
|
|
|
</Button>
|
|
|
</Link>
|
|
|
|
|
|
<motion.div
|
|
|
initial={{ scale: 0 }}
|
|
|
animate={{ scale: 1 }}
|
|
|
transition={{ type: "spring" }}
|
|
|
className="w-32 h-32 mx-auto rounded-3xl bg-gradient-to-br from-yellow-500 to-orange-500 flex items-center justify-center shadow-2xl shadow-yellow-500/30 mb-8"
|
|
|
>
|
|
|
<Target className="w-16 h-16 text-white" />
|
|
|
</motion.div>
|
|
|
|
|
|
<motion.h1
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
className="text-4xl md:text-5xl font-black text-white mb-4"
|
|
|
>
|
|
|
AI Vision Quiz
|
|
|
</motion.h1>
|
|
|
|
|
|
<motion.p
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
transition={{ delay: 0.1 }}
|
|
|
className="text-xl text-white/70 mb-8"
|
|
|
>
|
|
|
Test your knowledge with {questions.length} questions about Object Detection, Pose Estimation, and Emotion Recognition!
|
|
|
</motion.p>
|
|
|
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
transition={{ delay: 0.2 }}
|
|
|
className="bg-white/5 backdrop-blur-xl rounded-3xl p-8 border border-white/10 mb-8"
|
|
|
>
|
|
|
<div className="grid grid-cols-3 gap-4 text-center">
|
|
|
<div>
|
|
|
<div className="text-3xl font-bold text-cyan-400">{questions.length}</div>
|
|
|
<div className="text-white/60">Questions</div>
|
|
|
</div>
|
|
|
<div>
|
|
|
<div className="text-3xl font-bold text-purple-400">3</div>
|
|
|
<div className="text-white/60">Topics</div>
|
|
|
</div>
|
|
|
<div>
|
|
|
<div className="text-3xl font-bold text-emerald-400">∞</div>
|
|
|
<div className="text-white/60">Retries</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</motion.div>
|
|
|
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
transition={{ delay: 0.3 }}
|
|
|
>
|
|
|
{allLessonsCompleted ? (
|
|
|
<Button
|
|
|
onClick={handleStart}
|
|
|
size="lg"
|
|
|
className="text-xl px-12 py-8 bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600 rounded-2xl shadow-2xl shadow-orange-500/30 text-white font-bold border-0 transition-all hover:scale-105"
|
|
|
>
|
|
|
<Sparkles className="w-6 h-6 mr-2" />
|
|
|
Start Quiz
|
|
|
</Button>
|
|
|
) : (
|
|
|
<div>
|
|
|
<Button
|
|
|
disabled
|
|
|
size="lg"
|
|
|
className="text-xl px-12 py-8 bg-slate-700 text-slate-400 rounded-2xl font-bold border-0 cursor-not-allowed opacity-60 mb-4"
|
|
|
>
|
|
|
🔒 Quiz Locked
|
|
|
</Button>
|
|
|
<p className="text-white/60">Complete all 3 lessons to unlock the quiz!</p>
|
|
|
<Link to={createPageUrl('Lessons')} className="inline-block mt-4">
|
|
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white border-0 rounded-xl px-6 py-3">
|
|
|
Go to Lessons
|
|
|
</Button>
|
|
|
</Link>
|
|
|
</div>
|
|
|
)}
|
|
|
</motion.div>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (finished) {
|
|
|
const { emoji, message } = getScoreMessage();
|
|
|
const percentage = Math.round((score / questions.length) * 100);
|
|
|
|
|
|
return (
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 pt-24 pb-12 px-6">
|
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
|
|
<div className="absolute top-20 left-10 w-72 h-72 bg-yellow-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
<div className="absolute bottom-20 right-10 w-96 h-96 bg-orange-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
</div>
|
|
|
|
|
|
<div className="max-w-2xl mx-auto relative">
|
|
|
<motion.div
|
|
|
initial={{ scale: 0 }}
|
|
|
animate={{ scale: 1 }}
|
|
|
transition={{ type: "spring" }}
|
|
|
className="text-center"
|
|
|
>
|
|
|
<div className="text-8xl mb-6">{emoji}</div>
|
|
|
|
|
|
<h1 className="text-4xl md:text-5xl font-black text-white mb-4">
|
|
|
Quiz Complete!
|
|
|
</h1>
|
|
|
|
|
|
<div className="bg-white/5 backdrop-blur-xl rounded-3xl p-8 border border-white/10 mb-8">
|
|
|
<div className="text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-yellow-400 to-orange-400 mb-2">
|
|
|
{score}/{questions.length}
|
|
|
</div>
|
|
|
<div className="text-xl text-white/70 mb-6">
|
|
|
{percentage}% Correct
|
|
|
</div>
|
|
|
<Progress value={percentage} className="h-4 bg-white/10" />
|
|
|
<p className="text-white/80 mt-6 text-lg">{message}</p>
|
|
|
</div>
|
|
|
|
|
|
{/* Answer Summary */}
|
|
|
<div className="bg-white/5 backdrop-blur-xl rounded-3xl p-6 border border-white/10 mb-8">
|
|
|
<h3 className="text-lg font-bold text-white mb-4">Your Answers</h3>
|
|
|
<div className="grid grid-cols-5 gap-2">
|
|
|
{answers.map((answer, i) => (
|
|
|
<motion.div
|
|
|
key={i}
|
|
|
initial={{ scale: 0 }}
|
|
|
animate={{ scale: 1 }}
|
|
|
transition={{ delay: i * 0.05 }}
|
|
|
className={`w-12 h-12 rounded-xl flex items-center justify-center font-bold text-white ${
|
|
|
answer.correct
|
|
|
? 'bg-gradient-to-br from-emerald-500 to-teal-500'
|
|
|
: 'bg-gradient-to-br from-red-500 to-rose-500'
|
|
|
}`}
|
|
|
>
|
|
|
{i + 1}
|
|
|
</motion.div>
|
|
|
))}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
|
|
<Button
|
|
|
onClick={handleStart}
|
|
|
size="lg"
|
|
|
className="bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600 rounded-xl text-white font-bold border-0 px-8 py-6 text-lg shadow-xl shadow-orange-500/25 transition-all hover:scale-105"
|
|
|
>
|
|
|
<RotateCcw className="w-5 h-5 mr-2" />
|
|
|
Try Again
|
|
|
</Button>
|
|
|
<Link to={createPageUrl('Lessons')}>
|
|
|
<Button
|
|
|
size="lg"
|
|
|
className="border-2 border-white/30 text-white bg-white/5 hover:bg-white/10 rounded-xl w-full px-8 py-6 text-lg font-bold transition-all"
|
|
|
>
|
|
|
Review Lessons
|
|
|
</Button>
|
|
|
</Link>
|
|
|
</div>
|
|
|
|
|
|
{/* Test It Yourself Section */}
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
transition={{ delay: 0.3 }}
|
|
|
className="bg-gradient-to-br from-blue-500/10 to-purple-500/10 rounded-3xl p-10 border border-blue-500/20"
|
|
|
>
|
|
|
<div className="text-center mb-6">
|
|
|
<div className="text-5xl mb-4">🚀</div>
|
|
|
<h2 className="text-3xl font-black text-white mb-3">Ready to Test It Yourself?</h2>
|
|
|
<p className="text-white/70 text-lg mb-8">
|
|
|
Now that you've mastered the concepts, try building your own AI vision projects!
|
|
|
</p>
|
|
|
</div>
|
|
|
|
|
|
<div className="grid md:grid-cols-3 gap-4 mb-8">
|
|
|
<div className="bg-white/5 rounded-2xl p-6 border border-white/10">
|
|
|
<div className="text-3xl mb-3">👁️</div>
|
|
|
<h3 className="text-white font-bold mb-2">Object Detection</h3>
|
|
|
<p className="text-white/60 text-sm">Build your own detector</p>
|
|
|
</div>
|
|
|
<div className="bg-white/5 rounded-2xl p-6 border border-white/10">
|
|
|
<div className="text-3xl mb-3">🏃</div>
|
|
|
<h3 className="text-white font-bold mb-2">Pose Tracking</h3>
|
|
|
<p className="text-white/60 text-sm">Create motion games</p>
|
|
|
</div>
|
|
|
<div className="bg-white/5 rounded-2xl p-6 border border-white/10">
|
|
|
<div className="text-3xl mb-3">😊</div>
|
|
|
<h3 className="text-white font-bold mb-2">Emotion AI</h3>
|
|
|
<p className="text-white/60 text-sm">Analyze expressions</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<Button
|
|
|
size="lg"
|
|
|
onClick={() => {
|
|
|
window.location.href = "/Lab-Selection.html";
|
|
|
}}
|
|
|
className="w-full bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 hover:from-blue-600 hover:via-purple-600 hover:to-pink-600 text-white font-bold text-xl py-8 rounded-2xl border-0 shadow-2xl transition-all hover:scale-105"
|
|
|
>
|
|
|
<Sparkles className="w-6 h-6 mr-3" />
|
|
|
Launch AI Playground
|
|
|
</Button>
|
|
|
</motion.div>
|
|
|
</motion.div>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
const question = questions[currentQuestion];
|
|
|
|
|
|
return (
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 pt-24 pb-12 px-6">
|
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
|
|
<div className="absolute top-20 left-10 w-72 h-72 bg-yellow-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
<div className="absolute bottom-20 right-10 w-96 h-96 bg-orange-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
</div>
|
|
|
|
|
|
<div className="max-w-2xl mx-auto relative">
|
|
|
{/* Progress */}
|
|
|
<div className="mb-8">
|
|
|
<div className="flex items-center justify-between text-white/60 mb-2">
|
|
|
<span>Question {currentQuestion + 1} of {questions.length}</span>
|
|
|
<span className="px-3 py-1 rounded-full bg-white/10 text-sm">{question.topic}</span>
|
|
|
</div>
|
|
|
<Progress
|
|
|
value={((currentQuestion + 1) / questions.length) * 100}
|
|
|
className="h-2 bg-white/10"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
{/* Question */}
|
|
|
<AnimatePresence mode="wait">
|
|
|
<motion.div
|
|
|
key={currentQuestion}
|
|
|
initial={{ opacity: 0, x: 50 }}
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
exit={{ opacity: 0, x: -50 }}
|
|
|
>
|
|
|
<div className="bg-white/5 backdrop-blur-xl rounded-3xl p-8 border border-white/10 mb-6">
|
|
|
<div className="flex items-start gap-4 mb-6">
|
|
|
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-yellow-500 to-orange-500 flex items-center justify-center flex-shrink-0">
|
|
|
<Brain className="w-6 h-6 text-white" />
|
|
|
</div>
|
|
|
<h2 className="text-2xl font-bold text-white leading-relaxed">
|
|
|
{question.question}
|
|
|
</h2>
|
|
|
</div>
|
|
|
|
|
|
{/* Options */}
|
|
|
<div className="space-y-3">
|
|
|
{question.options.map((option, index) => {
|
|
|
const isSelected = selectedAnswer === index;
|
|
|
const isCorrect = index === question.correct;
|
|
|
const showCorrect = showResult && isCorrect;
|
|
|
const showWrong = showResult && isSelected && !isCorrect;
|
|
|
|
|
|
return (
|
|
|
<motion.button
|
|
|
key={index}
|
|
|
onClick={() => handleAnswer(index)}
|
|
|
disabled={showResult}
|
|
|
className={`w-full p-4 rounded-2xl text-left transition-all border-2 ${
|
|
|
showCorrect
|
|
|
? 'bg-emerald-500/20 border-emerald-500 text-white'
|
|
|
: showWrong
|
|
|
? 'bg-red-500/20 border-red-500 text-white'
|
|
|
: isSelected
|
|
|
? 'bg-white/20 border-white/40 text-white'
|
|
|
: 'bg-white/5 border-white/10 text-white/80 hover:bg-white/10 hover:border-white/20'
|
|
|
}`}
|
|
|
whileHover={!showResult ? { scale: 1.02 } : {}}
|
|
|
whileTap={!showResult ? { scale: 0.98 } : {}}
|
|
|
>
|
|
|
<div className="flex items-center gap-4">
|
|
|
<div className={`w-10 h-10 rounded-xl flex items-center justify-center font-bold ${
|
|
|
showCorrect
|
|
|
? 'bg-emerald-500 text-white'
|
|
|
: showWrong
|
|
|
? 'bg-red-500 text-white'
|
|
|
: 'bg-white/10 text-white/60'
|
|
|
}`}>
|
|
|
{showCorrect ? (
|
|
|
<CheckCircle2 className="w-5 h-5" />
|
|
|
) : showWrong ? (
|
|
|
<XCircle className="w-5 h-5" />
|
|
|
) : (
|
|
|
String.fromCharCode(65 + index)
|
|
|
)}
|
|
|
</div>
|
|
|
<span className="text-lg">{option}</span>
|
|
|
</div>
|
|
|
</motion.button>
|
|
|
);
|
|
|
})}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
{/* Feedback */}
|
|
|
<AnimatePresence>
|
|
|
{showResult && (
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
exit={{ opacity: 0, y: -20 }}
|
|
|
className={`rounded-2xl p-6 mb-6 ${
|
|
|
selectedAnswer === question.correct
|
|
|
? 'bg-gradient-to-br from-emerald-500/20 to-teal-500/20 border border-emerald-500/30'
|
|
|
: 'bg-gradient-to-br from-red-500/20 to-rose-500/20 border border-red-500/30'
|
|
|
}`}
|
|
|
>
|
|
|
<div className="flex items-center gap-3">
|
|
|
{selectedAnswer === question.correct ? (
|
|
|
<>
|
|
|
<CheckCircle2 className="w-8 h-8 text-emerald-400" />
|
|
|
<div>
|
|
|
<h3 className="text-xl font-bold text-white">Correct! 🎉</h3>
|
|
|
<p className="text-white/70">Great job! You got this one right.</p>
|
|
|
</div>
|
|
|
</>
|
|
|
) : (
|
|
|
<>
|
|
|
<XCircle className="w-8 h-8 text-red-400" />
|
|
|
<div>
|
|
|
<h3 className="text-xl font-bold text-white">Not quite! 🤔</h3>
|
|
|
<p className="text-white/70">
|
|
|
The correct answer was: {question.options[question.correct]}
|
|
|
</p>
|
|
|
</div>
|
|
|
</>
|
|
|
)}
|
|
|
</div>
|
|
|
</motion.div>
|
|
|
)}
|
|
|
</AnimatePresence>
|
|
|
|
|
|
{/* Next Button */}
|
|
|
{showResult && (
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0 }}
|
|
|
animate={{ opacity: 1 }}
|
|
|
className="text-center"
|
|
|
>
|
|
|
<Button
|
|
|
onClick={handleNext}
|
|
|
size="lg"
|
|
|
className="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 rounded-2xl px-12 py-6 text-lg font-bold text-white border-0 shadow-xl shadow-purple-500/30 transition-all hover:scale-105"
|
|
|
>
|
|
|
{currentQuestion < questions.length - 1 ? 'Next Question →' : '✨ See Results'}
|
|
|
</Button>
|
|
|
</motion.div>
|
|
|
)}
|
|
|
</motion.div>
|
|
|
</AnimatePresence>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|