diff --git a/src/data/content.js b/src/data/content.js index baca279..2660737 100644 --- a/src/data/content.js +++ b/src/data/content.js @@ -10,6 +10,7 @@ import { RandomMeme } from "../pages/activities/RandomMemes"; import { RandomJoke } from "../pages/activities/RandomJoke"; import { RandomAnimeQuote } from "../pages/activities/RandomAnimeQuote"; import { SimonSays } from "../pages/games/SimonSays"; +import { ReactionTime } from "../pages/games/ReactionTime"; import MemeCaptionMaker from "../pages/games/MemeCaptionMaker"; import meme from "../assets/activities/meme.jpg"; import dog from "../assets/activities/dogimage.jpeg"; @@ -130,6 +131,13 @@ export const games = [ urlTerm: "simon-says", element: , }, + { + title: "Reaction Time Test", + description: "Test your reflexes - click as fast as you can!", + icon: "https://cdn-icons-png.flaticon.com/512/2972/2972554.png", + urlTerm: "reaction-time", + element: , + }, { title: "Meme Caption Maker", description: "Create hilarious memes with custom captions", diff --git a/src/pages/games/ReactionTime.js b/src/pages/games/ReactionTime.js new file mode 100644 index 0000000..fd38726 --- /dev/null +++ b/src/pages/games/ReactionTime.js @@ -0,0 +1,205 @@ +import React, { useState, useEffect, useRef } from 'react'; +import "../../styles/pages/games/ReactionTime.css"; + +export const ReactionTime = () => { + const [gameState, setGameState] = useState('idle'); // idle, waiting, ready, result + const [reactionTimes, setReactionTimes] = useState([]); + const [currentAttempt, setCurrentAttempt] = useState(null); + const [message, setMessage] = useState('Click to Start'); + const [stats, setStats] = useState({ average: 0, fastest: 0, slowest: 0 }); + const [tooEarly, setTooEarly] = useState(false); + + const startTimeRef = useRef(null); + const timeoutRef = useRef(null); + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + useEffect(() => { + if (reactionTimes.length > 0) { + const avg = reactionTimes.reduce((a, b) => a + b, 0) / reactionTimes.length; + const fastest = Math.min(...reactionTimes); + const slowest = Math.max(...reactionTimes); + setStats({ + average: Math.round(avg), + fastest: fastest, + slowest: slowest + }); + } + }, [reactionTimes]); + + const startGame = () => { + if (gameState === 'waiting' || gameState === 'ready') return; + + setTooEarly(false); + setGameState('waiting'); + setMessage('Wait for green...'); + setCurrentAttempt(null); + + // Random delay between 1000ms and 5000ms + const randomDelay = Math.floor(Math.random() * 4000) + 1000; + + timeoutRef.current = setTimeout(() => { + setGameState('ready'); + setMessage('CLICK NOW!'); + startTimeRef.current = Date.now(); + }, randomDelay); + }; + + const handleClick = () => { + if (gameState === 'idle' || gameState === 'result') { + startGame(); + } else if (gameState === 'waiting') { + // Clicked too early + clearTimeout(timeoutRef.current); + setGameState('result'); + setMessage('Too early! Click to try again'); + setTooEarly(true); + setCurrentAttempt(null); + } else if (gameState === 'ready') { + // Calculate reaction time + const reactionTime = Date.now() - startTimeRef.current; + setCurrentAttempt(reactionTime); + setReactionTimes([...reactionTimes, reactionTime]); + setGameState('result'); + setMessage(`${reactionTime}ms - Click to try again`); + setTooEarly(false); + } + }; + + const resetStats = () => { + setReactionTimes([]); + setStats({ average: 0, fastest: 0, slowest: 0 }); + setGameState('idle'); + setMessage('Click to Start'); + setCurrentAttempt(null); + setTooEarly(false); + }; + + const getPerformanceRating = (time) => { + if (time < 200) return { text: 'Lightning Fast!', color: '#FFD700' }; + if (time < 250) return { text: 'Excellent!', color: '#00ff41' }; + if (time < 300) return { text: 'Great!', color: '#00d4ff' }; + if (time < 400) return { text: 'Good!', color: '#7fff00' }; + if (time < 500) return {text: 'Not bad!', color: '#ffaa00' }; + return { text: 'Keep trying!', color: '#ff6b6b' }; + }; + + const getStateClass = () => { + if (gameState === 'waiting') return 'waiting'; + if (gameState === 'ready') return 'ready'; + if (tooEarly) return 'too-early'; + return ''; + }; + + return ( +
+
+

⚡ Reaction Time Test

+

Test how fast your reflexes are!

+
+ +
+
+ {message} + {currentAttempt && !tooEarly && ( +
+ {getPerformanceRating(currentAttempt).emoji} + {getPerformanceRating(currentAttempt).text} +
+ )} + {tooEarly && ( +
+ + Wait for the green screen! +
+ )} +
+ + {gameState === 'waiting' && ( +
+
+
+
+
+ )} +
+ +
+

+ {gameState === 'idle' && '👆 Click the box above to start'} + {gameState === 'waiting' && '⏳ Wait for the screen to turn green'} + {gameState === 'ready' && '🎯 Click as fast as you can!'} + {gameState === 'result' && '🔄 Click to try again'} +

+
+ + {reactionTimes.length > 0 && ( +
+

📊 Your Statistics

+
+
+
📈
+
Attempts
+
{reactionTimes.length}
+
+
+
+
Average
+
{stats.average}ms
+
+
+
🏆
+
Fastest
+
{stats.fastest}ms
+
+
+
🐢
+
Slowest
+
{stats.slowest}ms
+
+
+ +
+

Recent Attempts

+
+ {reactionTimes.slice().reverse().map((time, index) => { + const rating = getPerformanceRating(time); + return ( +
+ #{reactionTimes.length - index} + {time}ms + + {rating.emoji} {rating.text} + +
+ ); + })} +
+
+ + +
+ )} + +
+

💡 How It Works

+
    +
  • Click the box to start the test
  • +
  • Wait for the screen to turn green
  • +
  • Click as fast as you can when it turns green
  • +
  • Try multiple times to improve your average!
  • +
  • Don't click too early or you'll have to start over
  • +
+
+
+ ); +}; + diff --git a/src/styles/pages/games/ReactionTime.css b/src/styles/pages/games/ReactionTime.css new file mode 100644 index 0000000..c74ff82 --- /dev/null +++ b/src/styles/pages/games/ReactionTime.css @@ -0,0 +1,513 @@ +.reaction-time-container { + min-height: 100vh; + padding: 2rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +.reaction-header { + text-align: center; + margin-bottom: 2rem; + animation: fadeInDown 0.6s ease-out; +} + +.reaction-title { + font-size: 3rem; + color: white; + margin: 0; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + font-weight: 700; +} + +.reaction-subtitle { + font-size: 1.2rem; + color: rgba(255, 255, 255, 0.9); + margin: 0.5rem 0 0 0; +} + +.reaction-game-area { + max-width: 800px; + margin: 0 auto 2rem auto; + min-height: 400px; + background-color: #3498db; + border-radius: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + position: relative; + overflow: hidden; + animation: fadeInUp 0.6s ease-out; +} + +.reaction-game-area:hover { + transform: translateY(-5px); + box-shadow: 0 15px 50px rgba(0, 0, 0, 0.4); +} + +.reaction-game-area.waiting { + background-color: #e74c3c; + animation: pulse 1.5s ease-in-out infinite; +} + +.reaction-game-area.ready { + background-color: #2ecc71; + animation: greenFlash 0.5s ease-out; +} + +.reaction-game-area.too-early { + background-color: #c0392b; + animation: shake 0.5s ease-out; +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.02); + } +} + +@keyframes greenFlash { + 0% { + background-color: #e74c3c; + } + 100% { + background-color: #2ecc71; + } +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-10px); } + 75% { transform: translateX(10px); } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.reaction-message { + text-align: center; + padding: 2rem; + z-index: 2; +} + +.message-text { + display: block; + font-size: 2.5rem; + color: white; + font-weight: 700; + text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.4); + margin-bottom: 1rem; +} + +.performance-badge { + margin-top: 1.5rem; + animation: bounceIn 0.6s ease-out; +} + +.badge-emoji { + font-size: 3rem; + display: block; + margin-bottom: 0.5rem; +} + +.badge-text { + font-size: 1.5rem; + font-weight: 600; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); +} + +@keyframes bounceIn { + 0% { + transform: scale(0); + opacity: 0; + } + 50% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +.too-early-message { + margin-top: 1rem; + animation: shake 0.5s ease-out; +} + +.error-emoji { + font-size: 3rem; + display: block; + margin-bottom: 0.5rem; +} + +.error-text { + font-size: 1.3rem; + color: white; + font-weight: 600; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.4); +} + +.pulse-indicator { + position: absolute; + width: 100px; + height: 100px; + bottom: 20%; +} + +.pulse-ring { + position: absolute; + width: 100%; + height: 100%; + border: 3px solid rgba(255, 255, 255, 0.6); + border-radius: 50%; + animation: pulseRing 2s cubic-bezier(0.215, 0.61, 0.355, 1) infinite; +} + +.pulse-ring:nth-child(2) { + animation-delay: 0.5s; +} + +.pulse-ring:nth-child(3) { + animation-delay: 1s; +} + +@keyframes pulseRing { + 0% { + transform: scale(0.5); + opacity: 1; + } + 100% { + transform: scale(1.5); + opacity: 0; + } +} + +.reaction-instructions { + max-width: 600px; + margin: 0 auto 2rem auto; + text-align: center; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + padding: 1rem; + border-radius: 10px; + animation: fadeInUp 0.8s ease-out; +} + +.reaction-instructions p { + color: white; + font-size: 1.1rem; + margin: 0; + font-weight: 500; +} + +.reaction-stats { + max-width: 800px; + margin: 0 auto 2rem auto; + background: white; + border-radius: 20px; + padding: 2rem; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + animation: fadeInUp 1s ease-out; +} + +.stats-title { + font-size: 2rem; + color: #2c3e50; + margin: 0 0 1.5rem 0; + text-align: center; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1rem; + margin-bottom: 2rem; +} + +.stat-card { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 15px; + padding: 1.5rem; + text-align: center; + color: white; + transition: transform 0.3s ease; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.stat-card:hover { + transform: translateY(-5px); +} + +.stat-card.highlight { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + transform: scale(1.05); +} + +.stat-icon { + font-size: 2.5rem; + margin-bottom: 0.5rem; +} + +.stat-label { + font-size: 0.9rem; + opacity: 0.9; + margin-bottom: 0.3rem; + font-weight: 500; +} + +.stat-value { + font-size: 1.8rem; + font-weight: 700; +} + +.attempts-history { + margin-top: 2rem; +} + +.history-title { + font-size: 1.5rem; + color: #2c3e50; + margin: 0 0 1rem 0; +} + +.attempts-list { + max-height: 300px; + overflow-y: auto; + border: 2px solid #e0e0e0; + border-radius: 10px; + padding: 0.5rem; +} + +.attempts-list::-webkit-scrollbar { + width: 8px; +} + +.attempts-list::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 10px; +} + +.attempts-list::-webkit-scrollbar-thumb { + background: #667eea; + border-radius: 10px; +} + +.attempts-list::-webkit-scrollbar-thumb:hover { + background: #764ba2; +} + +.attempt-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.8rem; + background: #f8f9fa; + border-radius: 8px; + margin-bottom: 0.5rem; + transition: all 0.3s ease; +} + +.attempt-item:hover { + background: #e9ecef; + transform: translateX(5px); +} + +.attempt-number { + font-weight: 600; + color: #667eea; + min-width: 40px; +} + +.attempt-time { + font-weight: 700; + font-size: 1.1rem; + color: #2c3e50; + flex: 1; + text-align: center; +} + +.attempt-rating { + font-weight: 600; + min-width: 150px; + text-align: right; +} + +.reset-button { + width: 100%; + padding: 1rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 10px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + margin-top: 1.5rem; + transition: all 0.3s ease; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.reset-button:hover { + transform: translateY(-3px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); +} + +.reset-button:active { + transform: translateY(-1px); +} + +.reaction-info { + max-width: 800px; + margin: 0 auto; + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 2rem; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + animation: fadeInUp 1.2s ease-out; +} + +.info-title { + font-size: 1.8rem; + color: #2c3e50; + margin: 0 0 1rem 0; +} + +.info-list { + list-style: none; + padding: 0; + margin: 0; +} + +.info-list li { + padding: 0.8rem; + margin-bottom: 0.5rem; + background: #f8f9fa; + border-radius: 8px; + color: #495057; + font-size: 1rem; + border-left: 4px solid #667eea; + transition: all 0.3s ease; +} + +.info-list li:hover { + background: #e9ecef; + transform: translateX(5px); +} + +.info-list li strong { + color: #2ecc71; + font-weight: 700; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .reaction-time-container { + padding: 1rem; + } + + .reaction-title { + font-size: 2rem; + } + + .reaction-subtitle { + font-size: 1rem; + } + + .reaction-game-area { + min-height: 300px; + } + + .message-text { + font-size: 1.8rem; + } + + .badge-emoji { + font-size: 2rem; + } + + .badge-text { + font-size: 1.2rem; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .stat-value { + font-size: 1.5rem; + } + + .reaction-stats, + .reaction-info { + padding: 1.5rem; + } + + .attempt-rating { + min-width: 120px; + font-size: 0.9rem; + } +} + +@media (max-width: 480px) { + .reaction-title { + font-size: 1.5rem; + } + + .reaction-game-area { + min-height: 250px; + } + + .message-text { + font-size: 1.5rem; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .stat-card.highlight { + transform: scale(1); + } + + .attempt-item { + flex-direction: column; + text-align: center; + gap: 0.5rem; + } + + .attempt-rating { + min-width: auto; + text-align: center; + } + + .info-list li { + font-size: 0.9rem; + } +} +