Spaces:
Running
Running
| // Shared utility functions and state | |
| const appState = { | |
| currentTest: null, | |
| activeButton: null | |
| }; | |
| // Format time as MM:SS | |
| function formatTime(seconds) { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = seconds % 60; | |
| return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; | |
| } | |
| // Calculate typing statistics | |
| function calculateTypingStats(startTime, endTime, correctChars, totalChars) { | |
| const timeElapsed = (endTime - startTime) / 1000; | |
| const wpm = Math.round((correctChars / 5) / (timeElapsed / 60)); | |
| const accuracy = totalChars > 0 ? Math.round((correctChars / totalChars) * 100) : 0; | |
| return { wpm, accuracy, timeElapsed, totalChars }; | |
| } | |
| // Highlight active button | |
| function setActiveButton(button, category) { | |
| if (appState.activeButton) { | |
| appState.activeButton.classList.remove('active-button'); | |
| } | |
| button.classList.add('active-button'); | |
| appState.activeButton = button; | |
| appState.currentTest = { ...appState.currentTest, [category]: button.dataset[category] }; | |
| } | |
| // Show modal with results | |
| function showResultsModal(stats) { | |
| const modal = document.createElement('div'); | |
| modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; | |
| modal.innerHTML = ` | |
| <div class="bg-gray-800 rounded-xl p-8 max-w-md w-full"> | |
| <h2 class="text-2xl font-bold mb-4">Test Results</h2> | |
| <div class="space-y-4"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">WPM:</span> | |
| <span class="font-bold">${stats.wpm}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Accuracy:</span> | |
| <span class="font-bold">${stats.accuracy}%</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Time:</span> | |
| <span class="font-bold">${formatTime(stats.timeElapsed)}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Characters:</span> | |
| <span class="font-bold">${stats.totalChars}</span> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-end space-x-3"> | |
| <button id="closeModal" class="px-4 py-2 bg-gray-700 rounded-lg hover:bg-gray-600">Close</button> | |
| <button id="tryAgain" class="px-4 py-2 bg-blue-600 rounded-lg hover:bg-blue-700">Try Again</button> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| modal.querySelector('#closeModal').addEventListener('click', () => { | |
| modal.remove(); | |
| window.location.href = 'leaderboard.html'; | |
| }); | |
| modal.querySelector('#tryAgain').addEventListener('click', () => { | |
| modal.remove(); | |
| window.location.href = 'typing-test.html'; | |
| }); | |
| } | |
| // Save result to leaderboard | |
| function saveToLeaderboard(result) { | |
| const userData = JSON.parse(localStorage.getItem('userData')); | |
| if (!userData) return; | |
| let leaderboard = JSON.parse(localStorage.getItem('leaderboard')) || []; | |
| // Check if user already has an entry and update if new score is higher | |
| const existingEntryIndex = leaderboard.findIndex( | |
| entry => entry.name === userData.name && | |
| entry.gradeLevel === userData.gradeLevel && | |
| entry.section === userData.section | |
| ); | |
| if (existingEntryIndex >= 0) { | |
| // Update only if new score is better | |
| if (result.wpm > leaderboard[existingEntryIndex].wpm) { | |
| leaderboard[existingEntryIndex] = { | |
| ...leaderboard[existingEntryIndex], | |
| wpm: result.wpm, | |
| accuracy: result.accuracy, | |
| time: result.timeElapsed, | |
| chars: result.totalChars, | |
| timestamp: new Date().toISOString() | |
| }; | |
| } | |
| } else { | |
| // Add new entry | |
| leaderboard.push({ | |
| name: userData.name, | |
| gradeLevel: userData.gradeLevel, | |
| section: userData.section, | |
| wpm: result.wpm, | |
| accuracy: result.accuracy, | |
| time: result.timeElapsed, | |
| chars: result.totalChars, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } | |
| // Sort by WPM descending | |
| leaderboard.sort((a, b) => b.wpm - a.wpm); | |
| // Keep only top 100 entries | |
| if (leaderboard.length > 100) { | |
| leaderboard = leaderboard.slice(0, 100); | |
| } | |
| localStorage.setItem('leaderboard', JSON.stringify(leaderboard)); | |
| } | |