Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TypeMaster - Typing Game</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 75% { transform: translateX(5px); } | |
| } | |
| .pulse { | |
| animation: pulse 1.5s infinite; | |
| } | |
| .shake { | |
| animation: shake 0.3s ease-in-out; | |
| } | |
| .glow { | |
| box-shadow: 0 0 15px rgba(59, 130, 246, 0.7); | |
| } | |
| .word-highlight { | |
| background-color: rgba(96, 165, 250, 0.3); | |
| border-radius: 4px; | |
| padding: 2px 4px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen flex flex-col"> | |
| <header class="bg-gray-800 py-6 px-4 shadow-lg"> | |
| <div class="container mx-auto flex justify-between items-center"> | |
| <h1 class="text-3xl font-bold text-blue-400 flex items-center"> | |
| <i class="fas fa-keyboard mr-2"></i> TypeMaster | |
| </h1> | |
| <div id="sound-toggle" class="cursor-pointer text-xl text-blue-400 hover:text-blue-300"> | |
| <i class="fas fa-volume-up"></i> | |
| </div> | |
| </div> | |
| </header> | |
| <main class="flex-grow container mx-auto px-4 py-8 flex flex-col items-center"> | |
| <div class="w-full max-w-4xl"> | |
| <!-- Game Info Section --> | |
| <section class="bg-gray-800 rounded-xl p-6 mb-8 shadow-lg"> | |
| <div class="flex flex-col md:flex-row justify-between items-center mb-6"> | |
| <div class="flex items-center space-x-4 mb-4 md:mb-0"> | |
| <div class="bg-blue-500 px-4 py-2 rounded-lg"> | |
| <span class="text-sm font-semibold">Time</span> | |
| <div class="text-3xl font-bold" id="timer">60</div> | |
| </div> | |
| <div class="bg-purple-500 px-4 py-2 rounded-lg"> | |
| <span class="text-sm font-semibold">Score</span> | |
| <div class="text-3xl font-bold" id="score">0</div> | |
| </div> | |
| <div class="bg-green-500 px-4 py-2 rounded-lg"> | |
| <span class="text-sm font-semibold">WPM</span> | |
| <div class="text-3xl font-bold" id="wpm">0</div> | |
| </div> | |
| <div class="bg-yellow-500 px-4 py-2 rounded-lg"> | |
| <span class="text-sm font-semibold">Accuracy</span> | |
| <div class="text-3xl font-bold" id="accuracy">100%</div> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div> | |
| <label class="block text-sm font-medium mb-1" for="difficulty">Difficulty</label> | |
| <select id="difficulty" class="bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-sm focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="easy">Easy</option> | |
| <option value="medium" selected>Medium</option> | |
| <option value="hard">Hard</option> | |
| <option value="insane">Insane</option> | |
| </select> | |
| </div> | |
| <button id="restart-btn" class="bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded-lg transition flex items-center"> | |
| <i class="fas fa-redo mr-2"></i> Restart | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-lg font-semibold">Instructions</h2> | |
| <span id="game-status" class="bg-blue-500 text-xs px-2 py-1 rounded-full">Ready</span> | |
| </div> | |
| <p class="text-gray-300 mt-1 text-sm">Type the words below as quickly and accurately as possible. The game ends when time runs out.</p> | |
| </div> | |
| </section> | |
| <!-- Typing Area --> | |
| <section class="relative mb-8"> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-lg"> | |
| <div class="mb-4 flex items-center justify-between"> | |
| <h2 class="text-xl font-bold text-blue-400">Your Typing Test</h2> | |
| <span id="words-count" class="bg-gray-700 px-3 py-1 rounded-full text-sm">0/10 words</span> | |
| </div> | |
| <div id="word-display" class="bg-gray-900 text-2xl font-mono p-6 rounded-lg h-32 overflow-auto mb-4 flex flex-wrap gap-2"> | |
| <!-- Words will appear here --> | |
| </div> | |
| <div class="relative"> | |
| <input type="text" id="word-input" class="w-full bg-gray-700 border-2 border-gray-600 rounded-lg py-3 px-4 text-xl focus:outline-none focus:border-blue-500 transition" | |
| placeholder="Type the word here..." autocomplete="off" autofocus> | |
| <div id="input-status" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-xl hidden"> | |
| <i class="fas fa-check-circle text-green-500"></i> | |
| </div> | |
| </div> | |
| <div class="mt-4 text-sm text-gray-400"> | |
| <span id="current-progress" class="text-blue-400 font-medium">0 characters typed</span> | |
| <span id="mistakes" class="float-right text-red-400">0 mistakes</span> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Results Section (Hidden by default) --> | |
| <section id="results-section" class="hidden bg-gray-800 rounded-xl p-6 shadow-lg"> | |
| <h2 class="text-2xl font-bold text-center mb-6 text-blue-400">Game Results</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-gray-900 p-6 rounded-lg text-center"> | |
| <div class="text-5xl font-bold text-blue-400 mb-2" id="final-score">0</div> | |
| <div class="text-lg font-medium">Total Score</div> | |
| </div> | |
| <div class="bg-gray-900 p-6 rounded-lg text-center"> | |
| <div class="text-5xl font-bold text-green-400 mb-2" id="final-wpm">0</div> | |
| <div class="text-lg font-medium">Words Per Minute</div> | |
| </div> | |
| <div class="bg-gray-900 p-6 rounded-lg text-center"> | |
| <div class="text-5xl font-bold text-yellow-400 mb-2" id="final-accuracy">100%</div> | |
| <div class="text-lg font-medium">Accuracy</div> | |
| </div> | |
| </div> | |
| <div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div class="bg-gray-900 p-4 rounded-lg"> | |
| <div class="mb-1 flex justify-between"> | |
| <span class="text-gray-400">Correct words:</span> | |
| <span class="font-medium" id="correct-words">0</span> | |
| </div> | |
| <div class="mb-1 flex justify-between"> | |
| <span class="text-gray-400">Incorrect words:</span> | |
| <span class="font-medium" id="incorrect-words">0</span> | |
| </div> | |
| <div class="mb-1 flex justify-between"> | |
| <span class="text-gray-400">Character count:</span> | |
| <span class="font-medium" id="characters-typed">0</span> | |
| </div> | |
| </div> | |
| <div class="bg-gray-900 p-4 rounded-lg"> | |
| <div class="mb-1 flex justify-between"> | |
| <span class="text-gray-400">Mistakes:</span> | |
| <span class="font-medium" id="total-mistakes">0</span> | |
| </div> | |
| <div class="mb-1 flex justify-between"> | |
| <span class="text-gray-400">Time played:</span> | |
| <span class="font-medium" id="time-played">60s</span> | |
| </div> | |
| <div class="mb-1 flex justify-between"> | |
| <span class="text-gray-400">Difficulty:</span> | |
| <span class="font-medium" id="played-difficulty">Medium</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 text-center"> | |
| <button id="play-again-btn" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg text-lg transition"> | |
| <i class="fas fa-play mr-2"></i> Play Again | |
| </button> | |
| </div> | |
| </section> | |
| </div> | |
| </main> | |
| <footer class="bg-gray-800 py-4 px-4 text-center text-gray-400 text-sm"> | |
| <p>Made with <i class="fas fa-heart text-red-500"></i> by TypeMaster | © 2023 All rights reserved</p> | |
| </footer> | |
| <script> | |
| // DOM Elements | |
| const wordDisplay = document.getElementById('word-display'); | |
| const wordInput = document.getElementById('word-input'); | |
| const timerDisplay = document.getElementById('timer'); | |
| const scoreDisplay = document.getElementById('score'); | |
| const wpmDisplay = document.getElementById('wpm'); | |
| const accuracyDisplay = document.getElementById('accuracy'); | |
| const wordsCountDisplay = document.getElementById('words-count'); | |
| const difficultySelect = document.getElementById('difficulty'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| const resultsSection = document.getElementById('results-section'); | |
| const gameStatus = document.getElementById('game-status'); | |
| const inputStatus = document.getElementById('input-status'); | |
| const currentProgress = document.getElementById('current-progress'); | |
| const mistakesDisplay = document.getElementById('mistakes'); | |
| const soundToggle = document.getElementById('sound-toggle'); | |
| const playAgainBtn = document.getElementById('play-again-btn'); | |
| // Game variables | |
| let words = []; | |
| let currentWordIndex = 0; | |
| let score = 0; | |
| let timeLeft = 60; | |
| let timer; | |
| let gameActive = false; | |
| let correctWords = 0; | |
| let incorrectWords = 0; | |
| let totalCharacters = 0; | |
| let correctCharacters = 0; | |
| let mistakes = 0; | |
| let startTime; | |
| let wordCount = 10; | |
| let soundEnabled = true; | |
| let currentDifficulty = 'medium'; | |
| // Word banks for different difficulty levels | |
| const wordBanks = { | |
| easy: [ | |
| 'cat', 'dog', 'sun', 'fun', 'run', 'big', 'red', 'blue', 'tree', 'book', | |
| 'ball', 'fish', 'moon', 'star', 'love', 'happy', 'beach', 'apple', 'house', 'school', | |
| 'water', 'music', 'smile', 'green', 'pizza', 'friend', 'summer', 'winter', 'flower', 'cloud' | |
| ], | |
| medium: [ | |
| 'computer', 'keyboard', 'monitor', 'internet', 'browser', 'website', 'program', | |
| 'network', 'digital', 'gaming', 'mobile', 'system', 'developer', 'software', | |
| 'hardware', 'database', 'algorithm', 'security', 'password', 'interface', | |
| 'graphics', 'console', 'wireless', 'function', 'virtual', 'language', 'storage', | |
| 'battery', 'process', 'service' | |
| ], | |
| hard: [ | |
| 'pneumonoultramicroscopicsilicovolcanoconiosis', 'hippopotomonstrosesquipedaliophobia', | |
| 'supercalifragilisticexpialidocious', 'pseudopseudohypoparathyroidism', | |
| 'floccinaucinihilipilification', 'antidisestablishmentarianism', | |
| 'honorificabilitudinitatibus', 'thyroparathyroidectomized', | |
| 'dichlorodifluoromethane', 'incomprehensibilities', 'uncharacteristically', | |
| 'counterrevolutionary', 'internationalization', 'electroencephalogram' | |
| ], | |
| insane: [ | |
| 'zyzzyva', 'quizzify', 'jazziness', 'pizzazz', 'huzzah', 'buzzword', | |
| 'fuzzbox', 'whizzbang', 'razzmatazz', 'squeezebox', 'zigzagging', | |
| 'syzygy', 'xylophone', 'yacht', 'sphinx', 'mnemonic', 'rhythm', | |
| 'fjord', 'crypt', 'gypsy', 'python', 'awkward', 'zombie', 'xenon' | |
| ] | |
| }; | |
| // Initialize game | |
| function initGame() { | |
| // Reset game state | |
| currentWordIndex = 0; | |
| score = 0; | |
| correctWords = 0; | |
| incorrectWords = 0; | |
| totalCharacters = 0; | |
| correctCharacters = 0; | |
| mistakes = 0; | |
| timeLeft = 60; | |
| gameActive = false; | |
| // Update UI | |
| scoreDisplay.textContent = '0'; | |
| wpmDisplay.textContent = '0'; | |
| accuracyDisplay.textContent = '100%'; | |
| wordsCountDisplay.textContent = '0/' + wordCount + ' words'; | |
| mistakesDisplay.textContent = '0 mistakes'; | |
| currentProgress.textContent = '0 characters typed'; | |
| gameStatus.textContent = 'Ready'; | |
| gameStatus.className = 'bg-blue-500 text-xs px-2 py-1 rounded-full'; | |
| // Generate words based on difficulty | |
| currentDifficulty = difficultySelect.value; | |
| const wordBank = wordBanks[currentDifficulty]; | |
| words = []; | |
| for (let i = 0; i < wordCount; i++) { | |
| const randomIndex = Math.floor(Math.random() * wordBank.length); | |
| words.push(wordBank[randomIndex]); | |
| } | |
| // Display words | |
| displayWords(); | |
| // Clear input and enable | |
| wordInput.value = ''; | |
| wordInput.disabled = false; | |
| wordInput.focus(); | |
| // Hide results | |
| resultsSection.classList.add('hidden'); | |
| // Start timer when first key is pressed | |
| wordInput.addEventListener('keydown', startGameOnFirstKey); | |
| } | |
| // Start game on first key press | |
| function startGameOnFirstKey(e) { | |
| if (!gameActive && e.key !== 'Shift' && e.key !== 'Tab' && e.key !== 'CapsLock' && !e.ctrlKey && !e.metaKey && !e.altKey) { | |
| gameActive = true; | |
| gameStatus.textContent = 'Playing'; | |
| gameStatus.className = 'bg-green-500 text-xs px-2 py-1 rounded-full'; | |
| startTimer(); | |
| startTime = new Date(); | |
| wordInput.removeEventListener('keydown', startGameOnFirstKey); | |
| } | |
| } | |
| // Display words in the word display area | |
| function displayWords() { | |
| wordDisplay.innerHTML = ''; | |
| words.forEach((word, index) => { | |
| const wordSpan = document.createElement('span'); | |
| wordSpan.textContent = word; | |
| if (index === currentWordIndex) { | |
| wordSpan.className = 'word-highlight text-yellow-400'; | |
| } else if (index < currentWordIndex) { | |
| wordSpan.className = 'text-green-400'; | |
| } else { | |
| wordSpan.className = 'text-white'; | |
| } | |
| wordDisplay.appendChild(wordSpan); | |
| // Add space between words but not after last word | |
| if (index < words.length - 1) { | |
| const spaceSpan = document.createElement('span'); | |
| spaceSpan.textContent = ' '; | |
| wordDisplay.appendChild(spaceSpan); | |
| } | |
| }); | |
| // Update words count | |
| wordsCountDisplay.textContent = currentWordIndex + '/' + wordCount + ' words'; | |
| } | |
| // Start timer | |
| function startTimer() { | |
| timer = setInterval(() => { | |
| timeLeft--; | |
| timerDisplay.textContent = timeLeft; | |
| if (timeLeft <= 10) { | |
| timerDisplay.classList.add('text-red-500', 'pulse'); | |
| } | |
| if (timeLeft <= 0) { | |
| endGame(); | |
| } | |
| // Update WPM every second | |
| updateWPM(); | |
| }, 1000); | |
| } | |
| // Update Words Per Minute | |
| function updateWPM() { | |
| if (correctWords === 0 || !startTime) return; | |
| const now = new Date(); | |
| const timeElapsed = (now - startTime) / 1000 / 60; // in minutes | |
| const wpm = Math.round(correctWords / timeElapsed); | |
| wpmDisplay.textContent = wpm; | |
| // Update accuracy | |
| const accuracy = totalCharacters > 0 ? Math.round((correctCharacters / totalCharacters) * 100) : 100; | |
| accuracyDisplay.textContent = accuracy + '%'; | |
| } | |
| // Handle word input | |
| wordInput.addEventListener('input', (e) => { | |
| const inputText = wordInput.value.trim(); | |
| const currentWord = words[currentWordIndex]; | |
| totalCharacters = inputText.length; | |
| currentProgress.textContent = totalCharacters + ' characters typed'; | |
| // Check if word is complete | |
| if (inputText === currentWord) { | |
| handleCorrectWord(); | |
| } | |
| }); | |
| wordInput.addEventListener('keydown', (e) => { | |
| if (e.key === ' ') { | |
| e.preventDefault(); | |
| checkCurrentWord(); | |
| } else if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| checkCurrentWord(); | |
| } | |
| }); | |
| // Check if current word matches input | |
| function checkCurrentWord() { | |
| const inputText = wordInput.value.trim(); | |
| const currentWord = words[currentWordIndex]; | |
| if (inputText === currentWord) { | |
| handleCorrectWord(); | |
| } else { | |
| handleIncorrectWord(); | |
| } | |
| } | |
| // Handle correct word | |
| function handleCorrectWord() { | |
| if (soundEnabled) { | |
| playSound('correct'); | |
| } | |
| // Update stats | |
| correctWords++; | |
| correctCharacters += words[currentWordIndex].length; | |
| // Calculate score (based on word length and speed) | |
| const wordScore = words[currentWordIndex].length * 5; | |
| score += wordScore; | |
| // Animate UI | |
| inputStatus.innerHTML = '<i class="fas fa-check-circle text-green-500"></i>'; | |
| inputStatus.classList.remove('hidden'); | |
| scoreDisplay.textContent = score; | |
| scoreDisplay.classList.add('glow'); | |
| setTimeout(() => { | |
| inputStatus.classList.add('hidden'); | |
| scoreDisplay.classList.remove('glow'); | |
| }, 500); | |
| // Move to next word | |
| moveToNextWord(); | |
| } | |
| // Handle incorrect word | |
| function handleIncorrectWord() { | |
| if (soundEnabled) { | |
| playSound('incorrect'); | |
| } | |
| // Update stats | |
| incorrectWords++; | |
| mistakes++; | |
| mistakesDisplay.textContent = mistakes + ' mistakes'; | |
| // Animate UI | |
| wordInput.classList.add('shake', 'border-red-500'); | |
| inputStatus.innerHTML = '<i class="fas fa-times-circle text-red-500"></i>'; | |
| inputStatus.classList.remove('hidden'); | |
| setTimeout(() => { | |
| wordInput.classList.remove('shake', 'border-red-500'); | |
| inputStatus.classList.add('hidden'); | |
| }, 500); | |
| } | |
| // Move to next word or end game if all words are done | |
| function moveToNextWord() { | |
| currentWordIndex++; | |
| wordInput.value = ''; | |
| // Update progress | |
| currentProgress.textContent = '0 characters typed'; | |
| if (currentWordIndex >= words.length) { | |
| // All words completed before time ran out | |
| if (gameActive) { | |
| wordCount += 5; // Increase word count for next round | |
| initGame(); // Start new round with more words | |
| } | |
| } else { | |
| displayWords(); | |
| } | |
| } | |
| // End game | |
| function endGame() { | |
| clearInterval(timer); | |
| gameActive = false; | |
| // Disable input | |
| wordInput.disabled = true; | |
| // Update game status | |
| gameStatus.textContent = 'Finished'; | |
| gameStatus.className = 'bg-red-500 text-xs px-2 py-1 rounded-full'; | |
| // Calculate final stats | |
| const now = new Date(); | |
| const timePlayedInMinutes = (now - startTime) / 1000 / 60; | |
| const finalWPM = Math.round(correctWords / timePlayedInMinutes); | |
| const finalAccuracy = totalCharacters > 0 ? Math.round((correctCharacters / totalCharacters) * 100) : 100; | |
| // Show results | |
| document.getElementById('final-score').textContent = score; | |
| document.getElementById('final-wpm').textContent = finalWPM; | |
| document.getElementById('final-accuracy').textContent = finalAccuracy + '%'; | |
| document.getElementById('correct-words').textContent = correctWords; | |
| document.getElementById('incorrect-words').textContent = incorrectWords; | |
| document.getElementById('characters-typed').textContent = totalCharacters; | |
| document.getElementById('total-mistakes').textContent = mistakes; | |
| document.getElementById('time-played').textContent = Math.round((60 - timeLeft) * 10) / 10 + 's'; | |
| document.getElementById('played-difficulty').textContent = currentDifficulty.charAt(0).toUpperCase() + currentDifficulty.slice(1); | |
| resultsSection.classList.remove('hidden'); | |
| // Scroll to results | |
| resultsSection.scrollIntoView({ behavior: 'smooth' }); | |
| if (soundEnabled) { | |
| playSound('gameover'); | |
| } | |
| } | |
| // Play sound | |
| function playSound(type) { | |
| if (!soundEnabled) return; | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| switch(type) { | |
| case 'correct': | |
| oscillator.frequency.value = 880; | |
| oscillator.type = 'sine'; | |
| break; | |
| case 'incorrect': | |
| oscillator.frequency.value = 220; | |
| oscillator.type = 'square'; | |
| break; | |
| case 'gameover': | |
| oscillator.frequency.setValueAtTime(440, audioContext.currentTime); | |
| oscillator.frequency.exponentialRampToValueAtTime(55, audioContext.currentTime + 0.5); | |
| oscillator.type = 'sawtooth'; | |
| break; | |
| } | |
| gainNode.gain.exponentialRampToValueAtTime(0.00001, audioContext.currentTime + 0.3); | |
| oscillator.start(); | |
| oscillator.stop(audioContext.currentTime + 0.3); | |
| } | |
| // Event listeners | |
| restartBtn.addEventListener('click', initGame); | |
| playAgainBtn.addEventListener('click', initGame); | |
| difficultySelect.addEventListener('change', () => { | |
| if (!gameActive) { | |
| initGame(); | |
| } | |
| }); | |
| soundToggle.addEventListener('click', () => { | |
| soundEnabled = !soundEnabled; | |
| if (soundEnabled) { | |
| soundToggle.innerHTML = '<i class="fas fa-volume-up"></i>'; | |
| soundToggle.classList.add('text-blue-400'); | |
| soundToggle.classList.remove('text-gray-500'); | |
| } else { | |
| soundToggle.innerHTML = '<i class="fas fa-volume-mute"></i>'; | |
| soundToggle.classList.remove('text-blue-400'); | |
| soundToggle.classList.add('text-gray-500'); | |
| } | |
| }); | |
| // Initialize game on load | |
| document.addEventListener('DOMContentLoaded', initGame); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=A3qualityo/type-master" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |