Initial build: typo, a typing test built from your own code

No backend, no build step -- static HTML/CSS/JS, themed identically to
flit/wisp (same CSS vars, same "> " title prefix, same dark/mono/green
aesthetic). Snippets are real, shipped code pulled from six repos in
this family (flit, wisp, keep, stash, delve-term, goonk) rather than
generic pangrams.

Stats: WPM = (correct chars / 5) / minutes elapsed, timing from first
keystroke not page load. Accuracy = correct keystrokes / total
keystrokes tracked at the keydown level (comparing the pressed key
against the exact next expected character before it's inserted), so
backspaced-over mistakes still count against it rather than only
diffing the final text. Keys/sec updates live via a 200ms tick.
Tabs in source are rendered as two spaces so players never need to
press Tab -- sidesteps a browser default-Tab-moves-focus fight
entirely rather than fighting it with preventDefault.

Verified via simulated typing sessions (puppeteer), not just static
review -- both an artificially fast pass (confirms the math is
internally consistent: 308 correct/310 total keystrokes -> 99%,
matches displayed) and a realistic ~60wpm-paced pass (confirms no
unit-conversion bug that only a slow, deliberate typist would hit
tests-at-lightspeed wouldn't surface). That process caught two real
bugs before they'd have hit a real player:

1. A stray `tabindex="0"` on the code display let the browser's
   native click-to-focus behavior steal focus away from the hidden
   textarea before our own focus() call ran -- typing silently did
   nothing. Fixed by removing the tabindex and switching to a
   mousedown+preventDefault handler instead of click.
2. The help modal's CSS set `display: flex` unconditionally, which
   (author CSS always beats the UA stylesheet) overrode the `hidden`
   attribute's default `display: none` -- the modal was invisibly
   covering the entire viewport and intercepting every click, at all
   times, whether or not it had been opened. Fixed with an explicit
   `.modal-overlay:not([hidden])` rule.

Also verified the actual Docker image builds and serves correctly
(nginx, no build stage needed since there's nothing to compile).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-07-15 14:12:44 +02:00
commit 4192a05a78
10 changed files with 677 additions and 0 deletions

57
index.html Normal file
View File

@@ -0,0 +1,57 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>typo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A typing test built from your own code — real snippets from real repos.">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<div class="header-row">
<h1>typo</h1>
<button class="help-btn" id="help-btn" title="How this works">?</button>
</div>
<p class="hint">real code, from real repos — type it as fast and cleanly as you can</p>
<div class="card" id="game-card">
<div class="source-line" id="source-line">loading a snippet…</div>
<pre class="code-display" id="code-display"></pre>
<textarea id="typing-input" class="sr-input" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="Type the code shown above"></textarea>
<div class="stat-row" id="stat-row">
<div class="stat"><span class="stat-value" id="stat-wpm">0</span><span class="stat-label">wpm</span></div>
<div class="stat"><span class="stat-value" id="stat-acc">100%</span><span class="stat-label">accuracy</span></div>
<div class="stat"><span class="stat-value" id="stat-kps">0.0</span><span class="stat-label">keys/sec</span></div>
<div class="stat"><span class="stat-value" id="stat-time">0.0s</span><span class="stat-label">time</span></div>
</div>
<div class="actions">
<button class="btn btn-primary" id="restart-btn">new snippet</button>
</div>
</div>
<div class="modal-overlay" id="help-modal" hidden>
<div class="modal-card">
<div class="modal-header">
<span>how it works</span>
<button class="help-btn" id="close-help">&times;</button>
</div>
<ul>
<li>Every snippet is real code pulled from real, shipped repos — flit, wisp, keep, stash, delve-term, goonk itself.</li>
<li>Click the code block and start typing. Timing starts on your first keystroke, not on page load.</li>
<li><strong>WPM</strong> = (correct characters / 5) / minutes elapsed — the standard typing-test formula.</li>
<li><strong>Accuracy</strong> = correct keystrokes / total keystrokes, including ones you backspaced over.</li>
<li>Mistakes are highlighted in place. You can backspace to fix them or keep going — either way, they count against accuracy.</li>
<li>Tabs in the original source are shown as two spaces, so you never need to press Tab.</li>
</ul>
</div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>