Tortoise is a focused typing test web app that emphasizes clean UX, instant settings feedback, and actionable analysis. It provides per-word heatmaps, WPM timelines, keystroke charts and a persistent session history stored in localStorage.
- Problem solved: built a compact typing test experience that immediately reflects user settings (difficulty/time/word count) without page reloads, and provides analyzable session data for performance review.
- Why this approach: local first (fast, private), simple predictable UI, and a single reusable hook (
useTyping) responsible for generation, sampling and metrics; avoids heavy backend dependencies for quick iteration. - Key technologies: Next.js (App Router), React 19, TypeScript 5, Tailwind CSS, Recharts.
- What to demo in an interview:
- Change difficulty/time/wordcount in Settings and show instant update in the Test view.
- Run a short timed test and open the Results modal to showcase the heatmap and WPM timeline.
- Open Analysis page to show session history thumbnails and Reset Data functionality.
- Explain
useTypingresponsibilities and how timeline sampling uses refs to avoid stale closures.
Requirements
- Node.js 18+ (recommended)
- npm, pnpm or yarn
Install and run locally:
# install
npm install
# run development server
npm run dev
# build and preview
npm run build
npm run startOpen http://localhost:3000 in your browser.
- ⚡ Next.js 16 (App Router)
- ⚛️ React 19
- 🧭 TypeScript 5
- 🎨 Tailwind CSS 4
- 📊 Recharts (charts)
- 🗄️ localStorage (session persistence)
- 🪄 Custom hooks (notably
useTyping)
- Configurable typing test: difficulty, word count, time limit, punctuation.
- Same-tab settings propagation (CustomEvent + localStorage) — changes apply immediately.
- Results modal with per-word heatmap and metrics.
- Analysis page with latest session details and previous session thumbnails.
- Theme & font selectors that apply instantly.
src/hooks/useTyping.ts— hook managing generation, timer, sampling, metrics and reset.src/components/typing/typing-box.tsx— typing UI, caret logic and input handling.src/components/charts/heatmap.tsx— heatmap renderer.src/lib/sessionStore.ts— localStorage session persistence helpers.src/app/globals.css— fonts and theme variables.
Controls persist choices to localStorage and emit a same-window CustomEvent (tortoise_setting_changed). The typing UI listens for that event, updates local component state and regenerates words or resets the test as needed — no navigation or reload required.
- Font/theme not applying instantly: the
FontSelectorsetsdocument.body.style.fontFamilyin addition to applying a body class so fonts apply immediately. - CSS build error about
@import: ensure@importstatements (Google Fonts) are at the top ofsrc/app/globals.css— this repo already places them correctly. - Empty WPM timeline: fixed by sampling using a
timeLeftref and appending a final sample on test finish (seeuseTyping).
- Type check:
npx tsc --noEmit - Lint:
npm run lint(ESLint configured)
- Add server-side export (API route) to download session CSVs.
- Replace native
titlehover on heatmap with a styled tooltip component. - Add unit tests for
useTypingsampling and metric calculations.
PRs welcome. For larger changes, open an issue first. Licensed under MIT.
