codetyper-racer / typing-test.html
ItsJayKee's picture
okay make all this interactable
449f607 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CodeTyper Racer - Challenge</title>
<!-- Tailwind -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Feather Icons (ONLY ONCE – FIXED) -->
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<!-- ================= HEADER ================= -->
<header class="bg-gray-800 py-4 shadow-lg">
<div class="container mx-auto px-4 flex justify-between items-center">
<h1 class="text-xl font-bold">CodeTyper Racer πŸš€</h1>
<div class="flex items-center space-x-4">
<div id="timerDisplay" class="bg-gray-700 px-4 py-2 rounded-lg font-mono">00:00</div>
<button id="leaderboardBtn" class="flex items-center space-x-1 bg-gray-700 px-3 py-2 rounded-lg hover:bg-gray-600">
<i data-feather="bar-chart-2"></i>
<span>Leaderboard</span>
</button>
</div>
</div>
</header>
<!-- ================= MAIN ================= -->
<main class="container mx-auto px-4 py-8">
<div class="flex flex-col lg:flex-row gap-8">
<!-- SETTINGS -->
<div class="lg:w-1/4 bg-gray-800 p-6 rounded-xl shadow-lg">
<h2 class="text-lg font-semibold mb-3">Language</h2>
<button data-language="html" class="language-btn w-full bg-blue-600 py-2 rounded mb-2">HTML</button>
<button data-language="css" class="language-btn w-full bg-blue-600 py-2 rounded mb-2">CSS</button>
<button data-language="javascript" class="language-btn w-full bg-blue-600 py-2 rounded">JavaScript</button>
<h2 class="text-lg font-semibold mt-6 mb-3">Timer</h2>
<button data-time="1" class="time-btn w-full bg-gray-700 py-2 rounded mb-2">1 min</button>
<button data-time="5" class="time-btn w-full bg-gray-700 py-2 rounded mb-2">5 min</button>
<button data-time="10" class="time-btn w-full bg-gray-700 py-2 rounded">10 min</button>
<h2 class="text-lg font-semibold mt-6 mb-3">Difficulty</h2>
<button data-difficulty="easy" class="difficulty-btn w-full bg-green-600 py-2 rounded mb-2">Easy</button>
<button data-difficulty="medium" class="difficulty-btn w-full bg-yellow-600 py-2 rounded mb-2">Medium</button>
<button data-difficulty="hard" class="difficulty-btn w-full bg-red-600 py-2 rounded">Hard</button>
<button id="startTestBtn" class="mt-6 w-full bg-purple-600 py-3 rounded font-bold">Start Test</button>
</div>
<!-- TYPING AREA -->
<div class="lg:w-3/4">
<div id="codeDisplay" class="bg-gray-800 p-6 rounded-xl font-mono h-64 overflow-y-auto mb-4">
<p class="italic text-gray-500">Select settings and start</p>
</div>
<textarea id="typingArea" disabled class="w-full h-48 bg-gray-800 p-4 rounded font-mono"></textarea>
<div class="h-2 bg-gray-700 rounded mt-2">
<div id="progressFill" class="h-full bg-blue-500 rounded" style="width:0%"></div>
</div>
<div class="flex justify-between mt-4">
<div id="wpmDisplay">WPM: 0</div>
<div id="accuracyDisplay">Accuracy: 0%</div>
<button id="submitTestBtn" disabled class="bg-green-600 px-4 py-2 rounded">Submit</button>
</div>
</div>
</div>
</main>
<!-- ================= SCRIPT ================= -->
<script>
feather.replace();
/* ---------- USER DATA ---------- */
if (!localStorage.getItem('userData')) {
localStorage.setItem('userData', JSON.stringify({
name: prompt("Name:") || "Anonymous",
gradeLevel: prompt("Grade:") || "-",
section: prompt("Section:") || "-"
}));
}
/* ---------- CODE SNIPPETS ---------- */
const codeSnippets = {
html:{easy:`<h1>Hello World</h1>`,medium:`<div><p>Sample</p></div>`,hard:`<!DOCTYPE html><html><body><h1>Hello</h1></body></html>`},
css:{easy:`body{margin:0}`,medium:`.box{padding:10px}`,hard:`@media(max-width:600px){body{background:red}}`},
javascript:{easy:`console.log("Hi")`,medium:`function add(a,b){return a+b}`,hard:`class App{constructor(){console.log("Start")}}`}
};
/* ---------- STATE ---------- */
let selectedLanguage="html",selectedTime=1,selectedDifficulty="easy";
let startTime,timer,testActive=false,currentCode="",correctChars=0;
/* ---------- DOM ---------- */
const codeDisplay=document.getElementById("codeDisplay");
const typingArea=document.getElementById("typingArea");
const timerDisplay=document.getElementById("timerDisplay");
const progressFill=document.getElementById("progressFill");
const wpmDisplay=document.getElementById("wpmDisplay");
const accuracyDisplay=document.getElementById("accuracyDisplay");
/* ---------- BUTTONS ---------- */
document.querySelectorAll(".language-btn").forEach(b =>
b.onclick = (e) => {
selectedLanguage = b.dataset.language;
setActiveButton(e.target, 'language');
}
);
document.querySelectorAll(".time-btn").forEach(b =>
b.onclick = (e) => {
selectedTime = +b.dataset.time;
setActiveButton(e.target, 'time');
}
);
document.querySelectorAll(".difficulty-btn").forEach(b =>
b.onclick = (e) => {
selectedDifficulty = b.dataset.difficulty;
setActiveButton(e.target, 'difficulty');
}
);
// Set first button in each category as active by default
document.querySelector('.language-btn').click();
document.querySelector('.time-btn').click();
document.querySelector('.difficulty-btn').click();
/* ---------- START ---------- */
document.getElementById("startTestBtn").onclick=()=>{
currentCode = codeSnippets[selectedLanguage][selectedDifficulty];
renderCodeDisplay();
typingArea.disabled = false;
typingArea.value = "";
typingArea.focus();
correctChars = 0;
progressFill.style.width = "0%";
wpmDisplay.textContent = "WPM: 0";
accuracyDisplay.textContent = "Accuracy: 0%";
startTime=Date.now();
testActive=true;
let sec=selectedTime*60;
timerDisplay.textContent=`00:${sec}`;
clearInterval(timer);
timer=setInterval(()=>{
sec--;
timerDisplay.textContent=`${Math.floor(sec/60).toString().padStart(2,"0")}:${(sec%60).toString().padStart(2,"0")}`;
if(sec<=0) endTest();
},1000);
};
/* ---------- TYPING ---------- */
function renderCodeDisplay() {
const typed = typingArea.value;
let html = '';
for (let i = 0; i < currentCode.length; i++) {
let char = currentCode[i];
let spanClass = '';
if (i < typed.length) {
spanClass = typed[i] === char ? 'correct' : 'incorrect';
} else if (i === typed.length) {
spanClass = 'current cursor';
}
char = char === '\n' ? '<br>' :
char === ' ' ? '&nbsp;' :
char === '<' ? '&lt;' :
char === '>' ? '&gt;' : char;
html += spanClass ? `<span class="${spanClass}">${char}</span>` : char;
}
codeDisplay.innerHTML = html;
}
typingArea.oninput = () => {
if (!testActive) return;
const typed = typingArea.value;
correctChars = 0;
for (let i = 0; i < typed.length; i++) {
if (typed[i] === currentCode[i]) correctChars++;
}
const acc = Math.round((correctChars / typed.length) * 100) || 0;
const wpm = Math.round((correctChars / 5) / ((Date.now() - startTime) / 60000)) || 0;
accuracyDisplay.textContent = `Accuracy: ${acc}%`;
wpmDisplay.textContent = `WPM: ${wpm}`;
progressFill.style.width = `${Math.min(typed.length / currentCode.length * 100, 100)}%`;
renderCodeDisplay();
if (typed.length >= currentCode.length) {
endTest();
}
};
/* ---------- END ---------- */
function endTest() {
if (!testActive) return;
testActive = false;
clearInterval(timer);
typingArea.disabled = true;
const endTime = Date.now();
const stats = calculateTypingStats(
startTime,
endTime,
correctChars,
currentCode.length
);
saveToLeaderboard(stats);
showResultsModal(stats);
}
</script>
</body>
</html>