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>
2026-07-15 14:12:44 +02:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > typo< / title >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
2026-07-15 14:33:54 +02:00
< meta name = "description" content = "A typing test built from your own code and lore — real snippets from real repos." >
2026-07-19 19:50:43 +02:00
< link rel = "icon" type = "image/svg+xml" href = "/favicon.svg" >
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>
2026-07-15 14:12:44 +02:00
< 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 >
2026-07-15 14:33:54 +02:00
< p class = "hint" > real code and lore, from real repos — type it as fast and cleanly as you can< / p >
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>
2026-07-15 14:12:44 +02:00
< 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" > × < / button >
< / div >
< ul >
2026-07-15 14:33:54 +02:00
< li > Every snippet is real — either code pulled from real, shipped repos (flit, wisp, keep, stash, delve-term, goonk itself), or in-world lore/flavor text from the games (Delve's chronicle, djupet's event log — some of that in Swedish, on purpose).< / li >
< li > Click the block and start typing. Timing starts on your first keystroke, not on page load.< / li >
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>
2026-07-15 14:12:44 +02:00
< 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 >