|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import Link from "next/link"; |
| 4 | +import { useEffect, useState } from "react"; |
| 5 | + |
| 6 | +import { Button } from "@/components/ui/button"; |
| 7 | +import TRIVIA from "@/trivia.json"; |
| 8 | + |
| 9 | +interface Trivia { |
| 10 | + question: string; |
| 11 | + answer: string; |
| 12 | +} |
| 13 | + |
| 14 | +export default function Custom404() { |
| 15 | + const [gameQuestions, setGameQuestions] = useState<Trivia[]>([]); |
| 16 | + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); |
| 17 | + const [score, setScore] = useState(0); |
| 18 | + const [gameActive, setGameActive] = useState(false); |
| 19 | + const [timeLeft, setTimeLeft] = useState(30); |
| 20 | + const [answered, setAnswered] = useState(false); |
| 21 | + const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null); |
| 22 | + const [options, setOptions] = useState<string[]>([]); |
| 23 | + |
| 24 | + const currentTrivia = gameQuestions[currentQuestionIndex]; |
| 25 | + |
| 26 | + const generateQuestions = () => { |
| 27 | + const shuffled = TRIVIA.sort(() => Math.random() - 0.5).slice(0, 10); |
| 28 | + setGameQuestions(shuffled); |
| 29 | + }; |
| 30 | + |
| 31 | + useEffect(() => { |
| 32 | + if (!currentTrivia) return; |
| 33 | + |
| 34 | + const wrongAnswers = TRIVIA.filter((t) => t.answer !== currentTrivia.answer) |
| 35 | + .sort(() => Math.random() - 0.5) |
| 36 | + .slice(0, 3) |
| 37 | + .map((t) => t.answer); |
| 38 | + |
| 39 | + const allOptions = [currentTrivia.answer, ...wrongAnswers].sort( |
| 40 | + () => Math.random() - 0.5, |
| 41 | + ); |
| 42 | + setOptions(allOptions); |
| 43 | + }, [currentQuestionIndex, currentTrivia]); |
| 44 | + |
| 45 | + useEffect(() => { |
| 46 | + if (!gameActive || timeLeft <= 0) return; |
| 47 | + |
| 48 | + const timer = setInterval(() => { |
| 49 | + setTimeLeft((prev) => { |
| 50 | + if (prev <= 1) { |
| 51 | + setGameActive(false); |
| 52 | + return 0; |
| 53 | + } |
| 54 | + return prev - 1; |
| 55 | + }); |
| 56 | + }, 1000); |
| 57 | + |
| 58 | + return () => clearInterval(timer); |
| 59 | + }, [gameActive, timeLeft]); |
| 60 | + |
| 61 | + const startGame = () => { |
| 62 | + generateQuestions(); |
| 63 | + setGameActive(true); |
| 64 | + setCurrentQuestionIndex(0); |
| 65 | + setScore(0); |
| 66 | + setTimeLeft(30); |
| 67 | + setAnswered(false); |
| 68 | + setSelectedAnswer(null); |
| 69 | + }; |
| 70 | + |
| 71 | + const handleAnswer = (answer: string) => { |
| 72 | + if (answered) return; |
| 73 | + |
| 74 | + setSelectedAnswer(answer); |
| 75 | + setAnswered(true); |
| 76 | + |
| 77 | + if (answer === currentTrivia.answer) { |
| 78 | + setScore((prev) => prev + 1); |
| 79 | + } |
| 80 | + |
| 81 | + setTimeout(() => { |
| 82 | + if (currentQuestionIndex < gameQuestions.length - 1) { |
| 83 | + setCurrentQuestionIndex((prev) => prev + 1); |
| 84 | + setAnswered(false); |
| 85 | + setSelectedAnswer(null); |
| 86 | + } else { |
| 87 | + setGameActive(false); |
| 88 | + } |
| 89 | + }, 1000); |
| 90 | + }; |
| 91 | + |
| 92 | + const getButtonClass = (option: string) => { |
| 93 | + const baseClass = |
| 94 | + "w-full rounded border p-2.5 text-left text-sm transition-all cursor-pointer md:p-3 md:text-base"; |
| 95 | + |
| 96 | + if (!answered) { |
| 97 | + return `${baseClass} bg-card border-border text-foreground hover:border-accent`; |
| 98 | + } |
| 99 | + |
| 100 | + if (option === currentTrivia.answer) { |
| 101 | + return `${baseClass} bg-primary border-accent text-accent-foreground font-semibold`; |
| 102 | + } |
| 103 | + |
| 104 | + if (option === selectedAnswer && option !== currentTrivia.answer) { |
| 105 | + return `${baseClass} bg-accent border-secondary text-secondary-foreground`; |
| 106 | + } |
| 107 | + |
| 108 | + return `${baseClass} bg-muted border-border text-muted-foreground`; |
| 109 | + }; |
| 110 | + |
| 111 | + return ( |
| 112 | + <main className="mx-auto flex h-dvh max-w-6xl items-center overflow-hidden px-4 py-4 sm:px-6 sm:py-6 md:px-16 md:py-8 lg:px-20 lg:py-10"> |
| 113 | + <div className="mx-auto w-full max-w-2xl"> |
| 114 | + <h1 className="mb-4 text-center font-jersey10 text-5xl text-primary md:mb-5 md:text-[3.25rem]"> |
| 115 | + 404 |
| 116 | + </h1> |
| 117 | + <h2 className="mb-2 text-center font-jersey10 text-xl text-foreground md:mb-3 md:text-[1.4rem]"> |
| 118 | + Page Not Found |
| 119 | + </h2> |
| 120 | + |
| 121 | + <p className="mb-4 text-center text-sm text-foreground md:mb-5 md:text-base"> |
| 122 | + Test your game knowledge with some rapid-fire trivia instead!!! |
| 123 | + </p> |
| 124 | + |
| 125 | + {!gameActive ? ( |
| 126 | + <div className="mb-4 space-y-3 md:mb-5 md:space-y-3.5"> |
| 127 | + {gameQuestions.length === 0 ? ( |
| 128 | + <> |
| 129 | + <p className="mb-3 text-sm text-muted-foreground md:mb-3 md:text-base"> |
| 130 | + Answer 10 random gaming trivia questions in 30 seconds! |
| 131 | + </p> |
| 132 | + <Button onClick={startGame} className="w-full"> |
| 133 | + Start Trivia |
| 134 | + </Button> |
| 135 | + </> |
| 136 | + ) : ( |
| 137 | + <> |
| 138 | + <p className="mb-3 text-center font-jersey10 text-2xl font-bold text-primary md:mb-3"> |
| 139 | + Game Over! |
| 140 | + </p> |
| 141 | + <p className="mb-3 text-center text-sm text-foreground md:mb-3 md:text-base"> |
| 142 | + Final Score: |
| 143 | + <span className="text-xl font-bold text-primary"> |
| 144 | + {score} |
| 145 | + </span>{" "} |
| 146 | + / 10 |
| 147 | + </p> |
| 148 | + <Button onClick={startGame} className="w-full"> |
| 149 | + Play Again |
| 150 | + </Button> |
| 151 | + </> |
| 152 | + )} |
| 153 | + </div> |
| 154 | + ) : ( |
| 155 | + <div className="md:space-y-4.5 mb-4 space-y-4 md:mb-5"> |
| 156 | + <div className="mb-2 flex items-center justify-between md:mb-3"> |
| 157 | + <p className="text-xs text-muted-foreground md:text-sm"> |
| 158 | + Question {currentQuestionIndex + 1} / 10 |
| 159 | + </p> |
| 160 | + <div |
| 161 | + className={`font-jersey10 text-2xl font-bold md:text-[1.9rem] ${ |
| 162 | + timeLeft <= 10 ? "text-destructive" : "text-primary" |
| 163 | + }`} |
| 164 | + > |
| 165 | + {timeLeft}s |
| 166 | + </div> |
| 167 | + </div> |
| 168 | + |
| 169 | + <div className="md:space-y-4.5 space-y-4 rounded border border-border bg-card p-4 md:p-5"> |
| 170 | + <p className="text-base leading-snug text-foreground md:text-[1.05rem]"> |
| 171 | + {currentTrivia.question} |
| 172 | + </p> |
| 173 | + |
| 174 | + <div className="space-y-2.5 md:space-y-3"> |
| 175 | + {options.map((option, idx) => ( |
| 176 | + <button |
| 177 | + key={idx} |
| 178 | + onClick={() => handleAnswer(option)} |
| 179 | + className={getButtonClass(option)} |
| 180 | + disabled={answered} |
| 181 | + > |
| 182 | + {option} |
| 183 | + </button> |
| 184 | + ))} |
| 185 | + </div> |
| 186 | + |
| 187 | + <p className="text-xs text-foreground md:text-sm"> |
| 188 | + Score:{" "} |
| 189 | + <span className="font-jersey10 font-bold text-primary"> |
| 190 | + {score} / 10 |
| 191 | + </span> |
| 192 | + </p> |
| 193 | + </div> |
| 194 | + </div> |
| 195 | + )} |
| 196 | + |
| 197 | + <Link href="/"> |
| 198 | + <Button variant="outline" className="w-full"> |
| 199 | + Go Home |
| 200 | + </Button> |
| 201 | + </Link> |
| 202 | + </div> |
| 203 | + </main> |
| 204 | + ); |
| 205 | +} |
0 commit comments