From 93a0f75eac18b65c3675c2359983cd89320c1dd2 Mon Sep 17 00:00:00 2001 From: glynis1314 Date: Sun, 26 Oct 2025 21:41:14 +0530 Subject: [PATCH 1/2] Quiz features updated --- projects/quiz/index.html | 63 ++++++++++++- projects/quiz/main.js | 199 +++++++++++++++++++++++++++++++-------- projects/quiz/styles.css | 100 ++++++++++++++++++++ 3 files changed, 316 insertions(+), 46 deletions(-) diff --git a/projects/quiz/index.html b/projects/quiz/index.html index d4528fd..018144b 100644 --- a/projects/quiz/index.html +++ b/projects/quiz/index.html @@ -15,14 +15,67 @@

Quiz

- Time Left: 15s + Time Left: 20s
-
-
-
-

Add question sets, scoring, and categories.

+ + +
+
+ + +
+ +
+ +
+ + + + +
+
+ +
+ + +
+ +
+ + +
+ + +
+ + + diff --git a/projects/quiz/main.js b/projects/quiz/main.js index d13e2e1..916e9fe 100644 --- a/projects/quiz/main.js +++ b/projects/quiz/main.js @@ -1,11 +1,18 @@ -const TIME_LIMIT = 15; // seconds per question +const TIME_LIMIT = 20; // seconds per question let timeLeft = TIME_LIMIT; let timerInterval = null; const timerElement = document.getElementById("time"); +const settingsPanel = document.getElementById("settings"); +const quizContent = document.getElementById("quiz-content"); +const progressFill = document.getElementById("progress-fill"); +const progressText = document.getElementById("progress-text"); +const startButton = document.getElementById("start-quiz"); let questions = []; // API-loaded questions +let currentQuestions = []; // Questions for current session let i = 0, score = 0; +let totalQuestions = 5; const q = document.getElementById('q'), answers = document.getElementById('answers'), @@ -35,6 +42,16 @@ const stored = safeGet('quiz-theme'); const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches; applyTheme(stored ? stored : (prefersLight ? 'light' : 'dark')); +/** Fisher-Yates shuffle algorithm */ +function shuffleArray(array) { + const newArray = [...array]; + for (let i = newArray.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; + } + return newArray; +} + /** Decode HTML entities from API */ function decodeHTML(str) { const txt = document.createElement('textarea'); @@ -42,31 +59,78 @@ function decodeHTML(str) { return txt.value; } -/** Shuffle array */ -function shuffle(arr) { - return arr.sort(() => Math.random() - 0.5); +/** Get settings from UI */ +function getSettings() { + const category = document.getElementById('category').value; + const difficulty = document.querySelector('input[name="difficulty"]:checked').value; + const questionCount = parseInt(document.getElementById('question-count').value); + const shuffleAnswers = document.getElementById('shuffle-answers').checked; + const shuffleQuestions = document.getElementById('shuffle-questions').checked; + + return { category, difficulty, questionCount, shuffleAnswers, shuffleQuestions }; } -/** Fetch questions from Open Trivia DB API */ +/** Fetch questions from Open Trivia DB API with filters */ async function loadQuestions() { try { - const res = await fetch('https://opentdb.com/api.php?amount=5&type=multiple'); + const settings = getSettings(); + totalQuestions = settings.questionCount; + + let apiUrl = `https://opentdb.com/api.php?amount=20&type=multiple`; + if (settings.category !== 'any') { + apiUrl += `&category=${settings.category}`; + } + if (settings.difficulty !== 'any') { + apiUrl += `&difficulty=${settings.difficulty}`; + } + + const res = await fetch(apiUrl); const data = await res.json(); + + if (data.response_code !== 0 || !data.results.length) { + throw new Error('No questions available with selected filters'); + } + questions = data.results.map(q => ({ q: decodeHTML(q.question), - a: shuffle([decodeHTML(q.correct_answer), ...q.incorrect_answers.map(decodeHTML)]), - c: null, // correct answer index - correctAnswer: decodeHTML(q.correct_answer) + a: [decodeHTML(q.correct_answer), ...q.incorrect_answers.map(decodeHTML)], + c: 0, // correct answer index will be set after shuffling + correctAnswer: decodeHTML(q.correct_answer), + difficulty: q.difficulty, + category: q.category })); - // Compute correct answer index - questions.forEach(qObj => { - qObj.c = qObj.a.findIndex(ans => ans === qObj.correctAnswer); + + // Prepare questions for current session + currentQuestions = settings.shuffleQuestions ? + shuffleArray(questions).slice(0, totalQuestions) : + questions.slice(0, totalQuestions); + + // Process each question + currentQuestions.forEach(qObj => { + if (settings.shuffleAnswers) { + const correctIndex = qObj.a.findIndex(ans => ans === qObj.correctAnswer); + const shuffledAnswers = shuffleArray(qObj.a); + qObj.a = shuffledAnswers; + qObj.c = shuffledAnswers.findIndex(ans => ans === qObj.correctAnswer); + } else { + qObj.c = 0; // Correct answer is always first if not shuffled + } }); + } catch (err) { console.error('Failed to load questions', err); - q.textContent = 'Failed to load questions 😢'; + q.textContent = 'Failed to load questions. Please try different filters. 😢'; answers.innerHTML = ''; + return false; } + return true; +} + +/** Update progress bar */ +function updateProgress() { + const progress = ((i + 1) / currentQuestions.length) * 100; + progressFill.style.width = `${progress}%`; + progressText.textContent = `Question ${i + 1} of ${currentQuestions.length}`; } /** Start timer for each question */ @@ -87,33 +151,73 @@ function startTimer() { if (timeLeft <= 0) { clearInterval(timerInterval); - handleNextQuestion(); + handleTimeUp(); } }, 1000); } +/** Handle when time runs out */ +function handleTimeUp() { + const currentQuestion = currentQuestions[i]; + Array.from(answers.children).forEach(btn => { + btn.disabled = true; + if (parseInt(btn.dataset.index) === currentQuestion.c) { + btn.classList.add('correct'); + } + }); + + result.textContent = 'Time\'s up!'; + + setTimeout(() => { + handleNextQuestion(); + }, 1500); +} + /** Move to next question */ function handleNextQuestion() { i++; - render(); + if (i < currentQuestions.length) { + render(); + } else { + endQuiz(); + } +} + +/** End quiz and show results */ +function endQuiz() { + clearInterval(timerInterval); + timerElement.parentElement.style.display = 'none'; + q.textContent = '🎉 Quiz Complete!'; + answers.innerHTML = ''; + result.textContent = `Final Score: ${score}/${currentQuestions.length}`; + + // Add restart button + const restartBtn = document.createElement('button'); + restartBtn.textContent = 'Try Again'; + restartBtn.className = 'start-btn'; + restartBtn.style.marginTop = '1rem'; + restartBtn.onclick = resetQuiz; + result.appendChild(restartBtn); +} + +/** Reset quiz to settings screen */ +function resetQuiz() { + i = 0; + score = 0; + currentQuestions = []; + settingsPanel.classList.remove('hidden'); + quizContent.classList.add('hidden'); + result.textContent = ''; } /** Render current question */ function render() { - if (!questions.length) return; - - if (i >= questions.length) { - clearInterval(timerInterval); - timerElement.parentElement.style.display = 'none'; - q.textContent = '🎉 Quiz Complete!'; - answers.innerHTML = ''; - result.textContent = `Score: ${score}/${questions.length}`; - return; - } + if (!currentQuestions.length) return; + updateProgress(); startTimer(); - const cur = questions[i]; + const cur = currentQuestions[i]; q.textContent = cur.q; answers.innerHTML = ''; result.textContent = ''; @@ -122,36 +226,49 @@ function render() { const b = document.createElement('button'); b.textContent = ans; b.className = 'answer-btn'; + b.dataset.index = idx; b.addEventListener('click', () => { - // prevent double clicks if (b.disabled) return; clearInterval(timerInterval); - // mark selected - Array.from(answers.children).forEach(x=>x.classList.remove('selected')); + + Array.from(answers.children).forEach(x => x.classList.remove('selected')); b.classList.add('selected'); - // mark correct/incorrect + if (idx === cur.c){ b.classList.add('correct'); score++; + result.textContent = 'Correct! 🎉'; } else { b.classList.add('incorrect'); - // reveal the correct one const correctBtn = answers.children[cur.c]; if (correctBtn) correctBtn.classList.add('correct'); + result.textContent = 'Incorrect 😞'; } - // disable all to avoid extra clicks - Array.from(answers.children).forEach(x=>x.disabled=true); - // short delay to show feedback - setTimeout(()=>{ + + Array.from(answers.children).forEach(x => x.disabled = true); + + setTimeout(() => { handleNextQuestion(); - }, 700); + }, 1500); }); answers.appendChild(b); }); } -(async function init() { - result.textContent = 'Loading questions...'; - await loadQuestions(); - render(); -})(); +/** Start quiz */ +async function startQuiz() { + const success = await loadQuestions(); + if (success && currentQuestions.length > 0) { + i = 0; + score = 0; + settingsPanel.classList.add('hidden'); + quizContent.classList.remove('hidden'); + render(); + } +} + +// Event Listeners +startButton.addEventListener('click', startQuiz); + +// Initialize +result.textContent = 'Configure your quiz settings and click "Start Quiz"'; \ No newline at end of file diff --git a/projects/quiz/styles.css b/projects/quiz/styles.css index 09ff40b..2e34d24 100644 --- a/projects/quiz/styles.css +++ b/projects/quiz/styles.css @@ -53,6 +53,100 @@ main{ .quiz-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem} .controls{display:flex;gap:.5rem;align-items:center} +/* Settings Panel */ +.settings-panel { + display: grid; + gap: 1.5rem; + margin-bottom: 1rem; +} + +.setting-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.setting-group label { + font-weight: 600; + color: var(--text); + font-size: 0.95rem; +} + +.setting-group select { + padding: 0.75rem; + border-radius: 8px; + border: 1px solid rgba(255,255,255,0.1); + background: var(--card); + color: var(--text); + font-size: 1rem; +} + +.radio-group { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.radio-group label { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: normal; + cursor: pointer; +} + +.radio-group input[type="radio"] { + margin: 0; +} + +.start-btn { + background: linear-gradient(135deg, var(--primary), var(--accent)); + color: white; + border: none; + padding: 1rem 2rem; + border-radius: 10px; + font-weight: 600; + font-size: 1.1rem; + cursor: pointer; + transition: all 0.2s ease; + margin-top: 0.5rem; +} + +.start-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(96,165,250,0.3); +} + +/* Progress Bar */ +.progress-container { + margin-bottom: 1.5rem; +} + +.progress-bar { + width: 100%; + height: 6px; + background: rgba(255,255,255,0.1); + border-radius: 3px; + overflow: hidden; + margin-bottom: 0.5rem; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--primary), var(--accent)); + border-radius: 3px; + transition: width 0.3s ease; + width: 0%; +} + +.progress-text { + font-size: 0.9rem; + color: var(--muted); + text-align: center; + font-weight: 600; +} + +/* Quiz Content */ #q{font-size:1.125rem;line-height:1.4;margin:0 0 1rem 0} #answers{display:flex;flex-direction:column;gap:.5rem} @@ -73,7 +167,13 @@ button#theme-toggle{background:transparent;border:1px solid rgba(255,255,255,0.0 .timer{font-weight:700;color:var(--muted);font-size:.95rem} .timer.warning{color:var(--danger)} +/* Utility Classes */ +.hidden { display: none !important; } +.visible { display: block !important; } + @media (max-width:520px){ main{padding:1rem;border-radius:12px} #q{font-size:1rem} + .radio-group { gap: 0.5rem; } + .setting-group select, .start-btn { font-size: 0.9rem; } } \ No newline at end of file From 3055d99b19f485b3aff3660cfc1cfd806943df18 Mon Sep 17 00:00:00 2001 From: glynis1314 Date: Sun, 26 Oct 2025 22:16:00 +0530 Subject: [PATCH 2/2] Add Echo Runner game: side-scrolling runner with ghost echoes --- data/projects.json | 11 +- projects/echo-runner/game.js | 374 ++++++++++++++++++++++++++++++++ projects/echo-runner/index.html | 42 ++++ projects/echo-runner/styles.css | 192 ++++++++++++++++ 4 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 projects/echo-runner/game.js create mode 100644 projects/echo-runner/index.html create mode 100644 projects/echo-runner/styles.css diff --git a/data/projects.json b/data/projects.json index 4f1aa5d..a384d2b 100644 --- a/data/projects.json +++ b/data/projects.json @@ -249,5 +249,14 @@ "category": "Small Games", "categoryKey": "games", "difficulty": "easy" - } + }, + { + + "title": "Echo Runner", + "slug": "echo-runner", + "description": "Side-scrolling runner where your previous runs become ghost obstacles", + "category": "Small Games", + "categoryKey": "games", + "difficulty": "easy" + } ] diff --git a/projects/echo-runner/game.js b/projects/echo-runner/game.js new file mode 100644 index 0000000..bb27f19 --- /dev/null +++ b/projects/echo-runner/game.js @@ -0,0 +1,374 @@ +class EchoRunner { + constructor() { + this.canvas = document.getElementById('gameCanvas'); + this.ctx = this.canvas.getContext('2d'); + this.scoreElement = document.getElementById('score'); + this.highScoreElement = document.getElementById('high-score'); + this.finalScoreElement = document.getElementById('final-score'); + this.gameOverElement = document.getElementById('gameOver'); + this.restartBtn = document.getElementById('restartBtn'); + this.playAgainBtn = document.getElementById('playAgainBtn'); + + this.init(); + this.setupEventListeners(); + } + + init() { + // Game state + this.gameRunning = false; + this.score = 0; + this.highScore = parseInt(localStorage.getItem('echoRunnerHighScore')) || 0; + this.highScoreElement.textContent = this.highScore; + + // Game settings + this.baseSpeed = 3; + this.currentSpeed = this.baseSpeed; + this.speedIncreaseRate = 0.001; + this.gravity = 0.8; + this.jumpForce = -15; + this.groundLevel = this.canvas.height - 50; + + // Player + this.player = { + x: 100, + y: this.groundLevel, + width: 40, + height: 40, + velocityY: 0, + isJumping: false, + color: '#e74c3c' + }; + + // Ghost echoes from previous runs + this.ghosts = []; + this.obstacles = []; + this.backgroundPos = 0; + + // Recording current run for next game + this.currentRunData = []; + this.recordingStartTime = 0; + + // Load previous ghost data + this.loadGhosts(); + } + + setupEventListeners() { + // Jump controls + document.addEventListener('keydown', (e) => { + if (e.code === 'Space' && this.gameRunning) { + e.preventDefault(); + this.jump(); + } else if (e.code === 'Space' && !this.gameRunning) { + this.startGame(); + } + }); + + this.canvas.addEventListener('click', () => { + if (this.gameRunning) { + this.jump(); + } else { + this.startGame(); + } + }); + + // Restart buttons + this.restartBtn.addEventListener('click', () => this.startGame()); + this.playAgainBtn.addEventListener('click', () => this.startGame()); + + // Touch support for mobile + this.canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + if (this.gameRunning) { + this.jump(); + } else { + this.startGame(); + } + }); + } + + startGame() { + this.gameRunning = true; + this.score = 0; + this.currentSpeed = this.baseSpeed; + this.obstacles = []; + this.currentRunData = []; + this.recordingStartTime = Date.now(); + this.gameOverElement.classList.add('hidden'); + + this.updateScore(); + this.gameLoop(); + } + + jump() { + if (!this.player.isJumping) { + this.player.velocityY = this.jumpForce; + this.player.isJumping = true; + + // Record jump for ghost data + this.recordAction('jump'); + } + } + + recordAction(type) { + this.currentRunData.push({ + type: type, + time: Date.now() - this.recordingStartTime, + x: this.player.x, + y: this.player.y + }); + } + + loadGhosts() { + try { + const savedGhosts = localStorage.getItem('echoRunnerGhosts'); + if (savedGhosts) { + this.ghosts = JSON.parse(savedGhosts); + } + } catch (e) { + console.warn('Could not load ghost data'); + this.ghosts = []; + } + } + + saveGhosts() { + try { + if (this.currentRunData.length > 0) { + this.ghosts.push({ + timestamp: Date.now(), + data: this.currentRunData, + score: this.score + }); + + // Keep only last 5 runs + if (this.ghosts.length > 5) { + this.ghosts = this.ghosts.slice(-5); + } + + localStorage.setItem('echoRunnerGhosts', JSON.stringify(this.ghosts)); + } + } catch (e) { + console.warn('Could not save ghost data'); + } + } + + updateScore() { + this.scoreElement.textContent = this.score; + if (this.score > this.highScore) { + this.highScore = this.score; + this.highScoreElement.textContent = this.highScore; + localStorage.setItem('echoRunnerHighScore', this.highScore); + } + } + + spawnObstacle() { + if (Math.random() < 0.02) { + this.obstacles.push({ + x: this.canvas.width, + y: this.groundLevel, + width: 30, + height: 30, + color: '#2c3e50' + }); + } + } + + updatePlayer() { + // Apply gravity + this.player.velocityY += this.gravity; + this.player.y += this.player.velocityY; + + // Ground collision + if (this.player.y >= this.groundLevel) { + this.player.y = this.groundLevel; + this.player.velocityY = 0; + this.player.isJumping = false; + } + } + + updateObstacles() { + // Move obstacles + this.obstacles.forEach(obstacle => { + obstacle.x -= this.currentSpeed; + }); + + // Remove off-screen obstacles + this.obstacles = this.obstacles.filter(obstacle => obstacle.x + obstacle.width > 0); + + // Spawn new obstacles + this.spawnObstacle(); + } + + updateGhosts() { + // Update ghost positions based on recorded data + this.ghosts.forEach(ghost => { + const currentTime = Date.now() - this.recordingStartTime; + + ghost.data.forEach(action => { + if (Math.abs(action.time - currentTime) < 50) { // 50ms tolerance + // Ghost would be at this position + const ghostX = this.canvas.width - (currentSpeed * action.time / 1000 * 60); + // We'll draw this in render method + } + }); + }); + } + + checkCollisions() { + // Check obstacle collisions + for (let obstacle of this.obstacles) { + if (this.isColliding(this.player, obstacle)) { + this.gameOver(); + return; + } + } + + // Check ghost collisions (simplified - using recorded positions) + const currentTime = Date.now() - this.recordingStartTime; + + this.ghosts.forEach(ghost => { + ghost.data.forEach(action => { + if (Math.abs(action.time - currentTime) < 30) { // 30ms collision window + const ghostX = this.canvas.width - (this.currentSpeed * action.time / 1000 * 60); + const ghostBounds = { + x: ghostX, + y: action.y, + width: this.player.width, + height: this.player.height + }; + + if (this.isColliding(this.player, ghostBounds)) { + this.gameOver(); + return; + } + } + }); + }); + } + + isColliding(rect1, rect2) { + return rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.y + rect1.height > rect2.y; + } + + gameOver() { + this.gameRunning = false; + this.finalScoreElement.textContent = this.score; + this.gameOverElement.classList.remove('hidden'); + this.saveGhosts(); + } + + render() { + // Clear canvas + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Draw background + this.drawBackground(); + + // Draw ghosts + this.drawGhosts(); + + // Draw obstacles + this.obstacles.forEach(obstacle => { + this.ctx.fillStyle = obstacle.color; + this.ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height); + + // Add some detail + this.ctx.fillStyle = '#34495e'; + this.ctx.fillRect(obstacle.x + 5, obstacle.y + 5, obstacle.width - 10, obstacle.height - 10); + }); + + // Draw player + this.ctx.fillStyle = this.player.color; + this.ctx.fillRect(this.player.x, this.player.y, this.player.width, this.player.height); + + // Player details + this.ctx.fillStyle = '#c0392b'; + this.ctx.fillRect(this.player.x + 5, this.player.y + 5, this.player.width - 10, this.player.height - 10); + + // Draw ground + this.ctx.fillStyle = '#27ae60'; + this.ctx.fillRect(0, this.groundLevel + this.player.height, this.canvas.width, this.canvas.height - this.groundLevel - this.player.height); + } + + drawBackground() { + // Sky + this.ctx.fillStyle = '#87CEEB'; + this.ctx.fillRect(0, 0, this.canvas.width, this.groundLevel); + + // Moving clouds + this.backgroundPos = (this.backgroundPos - this.currentSpeed * 0.5) % this.canvas.width; + + this.ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; + for (let i = -1; i < 2; i++) { + const x = this.backgroundPos + i * this.canvas.width; + this.ctx.beginPath(); + this.ctx.arc(x, 80, 30, 0, Math.PI * 2); + this.ctx.arc(x + 40, 70, 25, 0, Math.PI * 2); + this.ctx.arc(x + 80, 80, 35, 0, Math.PI * 2); + this.ctx.fill(); + } + } + + drawGhosts() { + const currentTime = Date.now() - this.recordingStartTime; + + this.ghosts.forEach((ghost, ghostIndex) => { + ghost.data.forEach((action, actionIndex) => { + if (action.type === 'jump' || actionIndex % 10 === 0) { // Sample positions + const ghostX = this.canvas.width - (this.currentSpeed * action.time / 1000 * 60); + const timeDiff = Math.abs(currentTime - action.time); + const alpha = Math.max(0, 1 - (timeDiff / 1000)); // Fade based on time difference + + if (alpha > 0 && ghostX > -50 && ghostX < this.canvas.width + 50) { + this.ctx.save(); + this.ctx.globalAlpha = alpha * 0.6; + this.ctx.fillStyle = `hsl(${ghostIndex * 60}, 70%, 50%)`; + this.ctx.fillRect(ghostX, action.y, this.player.width, this.player.height); + + // Ghost trail effect + this.ctx.globalAlpha = alpha * 0.3; + for (let i = 1; i <= 3; i++) { + this.ctx.fillRect(ghostX + i * 5, action.y, this.player.width, this.player.height); + } + this.ctx.restore(); + } + } + }); + }); + } + + gameLoop() { + if (!this.gameRunning) return; + + // Update game state + this.updatePlayer(); + this.updateObstacles(); + this.updateGhosts(); + this.checkCollisions(); + + // Increase difficulty + this.currentSpeed = this.baseSpeed + (this.score * this.speedIncreaseRate); + this.score += 0.1; + + // Render + this.render(); + this.updateScore(); + + // Continue game loop + requestAnimationFrame(() => this.gameLoop()); + } +} + +// Initialize game when page loads +document.addEventListener('DOMContentLoaded', () => { + new EchoRunner(); +}); + +// Pause game when tab is not visible +document.addEventListener('visibilitychange', () => { + if (document.hidden) { + // Could add pause functionality here + } +}); \ No newline at end of file diff --git a/projects/echo-runner/index.html b/projects/echo-runner/index.html new file mode 100644 index 0000000..55942da --- /dev/null +++ b/projects/echo-runner/index.html @@ -0,0 +1,42 @@ + + + + + + Echo Runner + + + +
+
+

Echo Runner

+
+
Score: 0
+
High Score: 0
+
+
+ + + +
+
+ SPACE or CLICK to jump +
+ +
+ + + +
+

Survive as long as possible. Your previous runs become ghost obstacles!

+
+
+ + + + \ No newline at end of file diff --git a/projects/echo-runner/styles.css b/projects/echo-runner/styles.css new file mode 100644 index 0000000..37c5263 --- /dev/null +++ b/projects/echo-runner/styles.css @@ -0,0 +1,192 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.game-container { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 20px; + padding: 30px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.2); + max-width: 900px; + width: 100%; +} + +.game-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 15px; +} + +.game-header h1 { + color: white; + font-size: 2.5rem; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); +} + +.game-info { + display: flex; + gap: 30px; + color: white; + font-size: 1.1rem; + font-weight: bold; +} + +#gameCanvas { + background: linear-gradient(180deg, #87CEEB 0%, #98FB98 100%); + border: 3px solid #2c3e50; + border-radius: 10px; + display: block; + margin: 0 auto; + box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3); +} + +.controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + flex-wrap: wrap; + gap: 15px; +} + +.control-info { + color: white; + font-size: 1rem; +} + +kbd { + background: rgba(255, 255, 255, 0.2); + padding: 4px 8px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.restart-btn { + background: linear-gradient(135deg, #ff6b6b, #ee5a24); + color: white; + border: none; + padding: 12px 24px; + border-radius: 25px; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); +} + +.restart-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); +} + +.restart-btn:active { + transform: translateY(0); +} + +.game-over { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.9); + padding: 40px; + border-radius: 20px; + text-align: center; + color: white; + border: 2px solid #ff6b6b; + box-shadow: 0 0 50px rgba(255, 107, 107, 0.5); +} + +.game-over h2 { + color: #ff6b6b; + font-size: 2.5rem; + margin-bottom: 20px; + text-shadow: 0 0 10px rgba(255, 107, 107, 0.5); +} + +.game-over p { + font-size: 1.2rem; + margin-bottom: 15px; +} + +.instructions { + text-align: center; + color: rgba(255, 255, 255, 0.8); + margin-top: 20px; + font-style: italic; +} + +.hidden { + display: none !important; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .game-header { + flex-direction: column; + text-align: center; + } + + .game-info { + justify-content: center; + } + + #gameCanvas { + width: 100%; + height: auto; + } + + .controls { + flex-direction: column; + } +} + +/* Accessibility */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* Focus styles for accessibility */ +button:focus { + outline: 3px solid #ffd700; + outline-offset: 2px; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + body { + background: #000; + } + + .game-container { + background: #fff; + color: #000; + } + + .game-header h1, + .game-info, + .control-info { + color: #000; + } +} \ No newline at end of file