diff --git a/src/data/content.js b/src/data/content.js
index 1652e19..a9e379d 100644
--- a/src/data/content.js
+++ b/src/data/content.js
@@ -28,6 +28,7 @@ import { CatHttpCode } from "../pages/activities/CatHttpCode";
import FlappyBird from "../pages/games/FlappyBird";
import RandomIdentity from "../pages/activities/RandomIdentity";
import word_scramble_icon from "../assets/games/WordScramble/word_scramble.png";
+import TypingTest from "../pages/games/TypingTest";
export const activities = [
{
@@ -179,5 +180,13 @@ export const games = [
icon: word_scramble_icon,
urlTerm: "WordScramble",
element: ,
+ },
+ {
+ title: "Typing Test",
+ description: "Test your typing skills",
+ icon :"https://typingmentor.com/_next/image/?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fbs3wcomf%2Fproduction%2F9f626374aa1c388a6418a077990771aff53d054f-1200x800.jpg%3Frect%3D0%2C35%2C1200%2C731%26w%3D808%26h%3D492%26fit%3Dcrop%26auto%3Dformat&w=1920&q=75",
+ urlTerm:"TypingTest",
+ element:,
+
}
];
\ No newline at end of file
diff --git a/src/pages/games/TypingTest.js b/src/pages/games/TypingTest.js
new file mode 100644
index 0000000..009392c
--- /dev/null
+++ b/src/pages/games/TypingTest.js
@@ -0,0 +1,166 @@
+import React, { useEffect, useMemo, useState } from "react";
+import "../../styles/pages/games/TypingTest.css";
+
+const SAMPLE_PARAGRAPHS = [
+ "The quick brown fox jumps over the lazy dog.",
+ "Typing is a skill that improves with focus and consistency.",
+ "React makes it painless to create interactive user interfaces.",
+ "Code slowly, read carefully, and test your changes.",
+ "Practice daily to increase your speed and accuracy."
+];
+
+function getRandomParagraph() {
+ const idx = Math.floor(Math.random() * SAMPLE_PARAGRAPHS.length);
+ return SAMPLE_PARAGRAPHS[idx];
+}
+
+export default function TypingTest() {
+ const [targetText, setTargetText] = useState(getRandomParagraph);
+ const [typedText, setTypedText] = useState("");
+ const [started, setStarted] = useState(false);
+ const [time, setTime] = useState(0);
+ const [finished, setFinished] = useState(false);
+ const [bestWpm, setBestWpm] = useState(() => {
+ const fromLS = localStorage.getItem("typing_best_wpm");
+ return fromLS ? Number(fromLS) : 0;
+ });
+
+ // timer: start on first key, stop on finish
+ useEffect(() => {
+ let timer;
+ if (started && !finished) {
+ timer = setInterval(() => {
+ setTime((t) => t + 1);
+ }, 1000);
+ } else {
+ if (timer) clearInterval(timer);
+ }
+ return () => {
+ if (timer) clearInterval(timer);
+ };
+ }, [started, finished]);
+
+ // derived metrics
+ const stats = useMemo(() => {
+ const wordsTyped =
+ typedText.trim().length === 0
+ ? 0
+ : typedText.trim().split(/\s+/).length;
+ const minutes = time / 60;
+ const wpm = minutes > 0 ? Math.round(wordsTyped / minutes) : 0;
+
+ // accuracy
+ let correct = 0;
+ for (let i = 0; i < typedText.length; i++) {
+ if (typedText[i] === targetText[i]) correct++;
+ }
+ const accuracy =
+ typedText.length === 0
+ ? 100
+ : Math.round((correct / typedText.length) * 100);
+
+ return { wpm, accuracy, wordsTyped };
+ }, [typedText, time, targetText]);
+
+ // when completed, stop and save best
+ useEffect(() => {
+ if (typedText === targetText && targetText.length > 0) {
+ setFinished(true);
+ setStarted(false);
+ if (stats.wpm > bestWpm) {
+ setBestWpm(stats.wpm);
+ localStorage.setItem("typing_best_wpm", String(stats.wpm));
+ }
+ }
+ }, [typedText, targetText, stats.wpm, bestWpm]);
+
+ function handleChange(e) {
+ const val = e.target.value;
+
+ // auto start on first key
+ if (!started && !finished) {
+ setStarted(true);
+ }
+
+ // restrict to paragraph length
+ if (val.length <= targetText.length) {
+ setTypedText(val);
+
+ // auto end on completion
+ if (val === targetText) {
+ setFinished(true);
+ setStarted(false);
+ }
+ }
+ }
+
+ function handleRestart() {
+ const nextPara = getRandomParagraph();
+ setTargetText(nextPara);
+ setTypedText("");
+ setTime(0);
+ setFinished(false);
+ setStarted(false);
+ }
+
+ return (
+
+
+
Typing Speed Test
+
Type the text below as fast and as accurately as you can.
+
+
+
+
+ Time
+ {time}s
+
+
+ WPM
+ {stats.wpm}
+
+
+ Accuracy
+ {stats.accuracy}%
+
+
+ Best WPM
+ {bestWpm}
+
+
+
+
+ {targetText.split("").map((ch, idx) => {
+ let cls = "";
+ if (idx < typedText.length) {
+ cls = typedText[idx] === ch ? "correct" : "incorrect";
+ }
+ return (
+
+ {ch}
+
+ );
+ })}
+
+
+
+
+
+
+ {finished && (
+
+ ✅ Paragraph completed! Final WPM: {stats.wpm}
+
+ )}
+
+
+ );
+}
diff --git a/src/styles/pages/games/TypingTest.css b/src/styles/pages/games/TypingTest.css
new file mode 100644
index 0000000..3a80a5f
--- /dev/null
+++ b/src/styles/pages/games/TypingTest.css
@@ -0,0 +1,169 @@
+.typing-test-container {
+ max-width: 750px;
+ margin: 2rem auto;
+ padding: 2rem;
+ background: #ffffff;
+ border-radius: 18px;
+ border: 1px solid #e2e8f0;
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.06);
+ font-family: "Poppins", "Segoe UI", system-ui, sans-serif;
+ transition: all 0.25s ease;
+}
+
+.typing-test-container:hover {
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.09);
+ border-color: #cbd5e1;
+}
+
+.typing-header {
+ text-align: center;
+ border-bottom: 1px solid #e2e8f0;
+ padding-bottom: 1rem;
+}
+
+.typing-header h2 {
+ font-size: 1.8rem;
+ font-weight: 700;
+ color: #1e293b;
+ margin-bottom: 0.3rem;
+}
+
+.typing-header p {
+ color: #64748b;
+ font-size: 0.9rem;
+}
+
+.typing-stats {
+ display: flex;
+ justify-content: space-around;
+ flex-wrap: wrap;
+ margin-top: 1.3rem;
+}
+
+.stat-card {
+ background: #f9fafb;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ padding: 0.8rem 1.1rem;
+ text-align: center;
+ width: 150px;
+ margin: 0.4rem;
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+}
+
+.stat-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 6px 16px rgba(37, 99, 235, 0.08);
+}
+
+.stat-label {
+ display: block;
+ font-size: 0.7rem;
+ color: #94a3b8;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+}
+
+.stat-value {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: #0f172a;
+}
+
+.typing-target {
+ background: #f8fafc;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ padding: 1rem;
+ margin-top: 1.5rem;
+ font-family: "JetBrains Mono", monospace;
+ line-height: 1.6;
+ font-size: 1rem;
+ color: #0f172a;
+ min-height: 90px;
+ letter-spacing: 0.02em;
+}
+
+.typing-target span.correct {
+ background: rgba(34, 197, 94, 0.08);
+ color: #15803d;
+ border-radius: 4px;
+}
+
+.typing-target span.incorrect {
+ background: rgba(244, 63, 94, 0.1);
+ color: #b91c1c;
+ text-decoration: line-through;
+ border-radius: 4px;
+}
+
+.typing-input {
+ margin-top: 1.3rem;
+ width: 100%;
+ min-height: 120px;
+ border-radius: 10px;
+ border: 1px solid #cbd5e1;
+ padding: 0.9rem 1rem;
+ font-size: 0.95rem;
+ color: #1e293b;
+ font-family: "Inter", system-ui, sans-serif;
+ line-height: 1.4;
+ resize: vertical;
+ outline: none;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+
+.typing-input:focus {
+ border-color: #2563eb;
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+}
+
+.typing-actions {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 1.2rem;
+ flex-wrap: wrap;
+}
+
+.typing-btn {
+ background: linear-gradient(135deg, #2563eb, #3b82f6);
+ color: #fff;
+ border: none;
+ border-radius: 10px;
+ padding: 0.6rem 1.3rem;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ box-shadow: 0 4px 10px rgba(37, 99, 235, 0.25);
+}
+
+.typing-btn:hover {
+ background: linear-gradient(135deg, #1d4ed8, #2563eb);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 14px rgba(37, 99, 235, 0.3);
+}
+
+.typing-done {
+ font-size: 0.9rem;
+ color: #16a34a;
+ font-weight: 600;
+ text-align: right;
+ flex: 1;
+}
+
+@media (max-width: 640px) {
+ .typing-stats {
+ flex-direction: column;
+ align-items: center;
+ }
+ .stat-card {
+ width: 80%;
+ }
+ .typing-actions {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.6rem;
+ }
+}