Expand snippet pool with more code and add lore/flavor text
All checks were successful
Docker / build-and-push (push) Successful in 37s
All checks were successful
Docker / build-and-push (push) Successful in 37s
8 more entries (16 total, up from 8): three more code snippets across delve-term/stash/goonk for variety, plus five real lore/flavor text fragments -- two from adventure's Chronicle of the Keepers (English) and three from djupet's event log (Swedish, on purpose -- genuinely different character set, and it's real shipped content, not filler). Added a `type` field (code/lore) to every entry for future use (could filter by type later) and updated the header/help-modal copy to stop saying "code" exclusively now that it isn't. Verified via simulated typing: a full Swedish snippet (öäå included) types and finishes correctly with 100% accuracy, and a lore snippet renders and completes the same as any code one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
.env
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
|
||||
|
||||
11
README.md
11
README.md
@@ -31,7 +31,16 @@ want them pleasant to type.
|
||||
|
||||
## Local development
|
||||
|
||||
Any static file server works, e.g.:
|
||||
Run with npm:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This serves the app on `http://localhost:8850`.
|
||||
|
||||
Any static file server still works, e.g.:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8850
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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.">
|
||||
<meta name="description" content="A typing test built from your own code and lore — 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" />
|
||||
@@ -16,7 +16,7 @@
|
||||
<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>
|
||||
<p class="hint">real code and lore, 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>
|
||||
@@ -42,8 +42,8 @@
|
||||
<button class="help-btn" id="close-help">×</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>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>
|
||||
<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>
|
||||
|
||||
1057
package-lock.json
generated
Normal file
1057
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "typo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "A tiny typing test served as a static app",
|
||||
"scripts": {
|
||||
"dev": "serve -l 8850 .",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"serve": "^14.2.4"
|
||||
}
|
||||
}
|
||||
@@ -3,48 +3,112 @@
|
||||
"repo": "delve-term",
|
||||
"file": "game/combat.go",
|
||||
"lang": "go",
|
||||
"type": "code",
|
||||
"code": "func withThe(name string) string {\n\tif strings.HasPrefix(name, \"the \") {\n\t\treturn name\n\t}\n\treturn \"the \" + name\n}"
|
||||
},
|
||||
{
|
||||
"repo": "delve-term",
|
||||
"file": "game/graves.go",
|
||||
"lang": "go",
|
||||
"type": "code",
|
||||
"code": "func RandomGraveNear(floor, spread int) *Grave {\n\tgraveMu.Lock()\n\tdefer graveMu.Unlock()\n\tvar candidates []Grave\n\tfor _, g := range graves {\n\t\td := g.Floor - floor\n\t\tif d < 0 {\n\t\t\td = -d\n\t\t}\n\t\tif d <= spread {\n\t\t\tcandidates = append(candidates, g)\n\t\t}\n\t}\n\tif len(candidates) == 0 {\n\t\treturn nil\n\t}\n\treturn &candidates[rand.Intn(len(candidates))]\n}"
|
||||
},
|
||||
{
|
||||
"repo": "delve-term",
|
||||
"file": "game/view.go",
|
||||
"lang": "go",
|
||||
"type": "code",
|
||||
"code": "func hpBar(cur, max int) string {\n\tstyle := hpStyle\n\tif cur <= max/4 {\n\t\tstyle = hpLowStyle\n\t}\n\tconst width = 20\n\tfilled := 0\n\tif max > 0 {\n\t\tfilled = cur * width / max\n\t}\n\tif filled < 0 {\n\t\tfilled = 0\n\t}\n\tbar := strings.Repeat(\"#\", filled) + strings.Repeat(\"-\", width-filled)\n\treturn style.Render(fmt.Sprintf(\"[%s] %d/%d\", bar, cur, max))\n}"
|
||||
},
|
||||
{
|
||||
"repo": "flit",
|
||||
"file": "cli/cmd/flit/main.go",
|
||||
"lang": "go",
|
||||
"type": "code",
|
||||
"code": "func decodeInvite(s string) (*invite, error) {\n\tconst prefix = \"flit:\"\n\tif len(s) < len(prefix) || s[:len(prefix)] != prefix {\n\t\treturn nil, fmt.Errorf(\"not a flit invite\")\n\t}\n\traw, err := base64.RawURLEncoding.DecodeString(s[len(prefix):])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar inv invite\n\tif err := json.Unmarshal(raw, &inv); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &inv, nil\n}"
|
||||
},
|
||||
{
|
||||
"repo": "wisp",
|
||||
"file": "client/src/crypto.ts",
|
||||
"lang": "ts",
|
||||
"type": "code",
|
||||
"code": "function concat(...parts: Uint8Array[]): Uint8Array {\n const total = parts.reduce((n, p) => n + p.length, 0)\n const out = new Uint8Array(total)\n let offset = 0\n for (const p of parts) {\n out.set(p, offset)\n offset += p.length\n }\n return out\n}"
|
||||
},
|
||||
{
|
||||
"repo": "stash",
|
||||
"file": "src/server/db.ts",
|
||||
"lang": "ts",
|
||||
"type": "code",
|
||||
"code": "export function deleteArticle(id: number): boolean {\n const result = db.prepare('DELETE FROM articles WHERE id = ?').run(id);\n return result.changes > 0;\n}\n\nexport function isValidToken(token: string): boolean {\n const row = db.prepare('SELECT 1 FROM tokens WHERE token = ?').get(token);\n return !!row;\n}"
|
||||
},
|
||||
{
|
||||
"repo": "stash",
|
||||
"file": "extension/background.js",
|
||||
"lang": "js",
|
||||
"type": "code",
|
||||
"code": "function notify(title, message) {\n chrome.notifications.create({\n type: 'basic',\n iconUrl: 'data:image/svg+xml;base64,' + btoa('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\"><rect width=\"48\" height=\"48\" rx=\"8\" fill=\"#0a5\"/></svg>'),\n title,\n message,\n });\n}"
|
||||
},
|
||||
{
|
||||
"repo": "keep",
|
||||
"file": "src/server/db.ts",
|
||||
"lang": "ts",
|
||||
"type": "code",
|
||||
"code": "export function hasGrant(vaultKey: string, recipientId: string): boolean {\n return !!getGrant(vaultKey, recipientId);\n}\n\nexport function hasWriteGrant(vaultKey: string, recipientId: string): boolean {\n const grant = getGrant(vaultKey, recipientId);\n return !!grant && grant.canWrite;\n}"
|
||||
},
|
||||
{
|
||||
"repo": "goonk",
|
||||
"file": "api/routes/lastfm.mjs",
|
||||
"lang": "js",
|
||||
"type": "code",
|
||||
"code": "router.get('/top-artists', async (req, res) => {\n const period = req.query.period || '7day';\n const limit = Math.min(Number(req.query.limit) || 10, 50);\n try {\n const data = await cached(`top-artists:${period}:${limit}`, CACHE_TTL_MS, () =>\n lfm('user.gettopartists', { period, limit })\n );\n res.json(data.topartists?.artist ?? []);\n } catch (e) {\n res.status(502).json({ error: e.message });\n }\n});"
|
||||
},
|
||||
{
|
||||
"repo": "goonk",
|
||||
"file": "api/routes/gitea.mjs",
|
||||
"lang": "js",
|
||||
"type": "code",
|
||||
"code": "router.get('/toprepos', async (_req, res) => {\n try {\n if (repoCache && Date.now() - repoCache.ts < CACHE_TTL_MS) {\n return res.json(repoCache.data);\n }\n const upstream = await fetch(`${BASE}/api/v1/users/${USER}/repos?limit=50`);\n if (!upstream.ok) return res.status(502).json({ error: 'upstream error' });\n const repos = await upstream.json();\n const data = repos\n .filter(r => !r.archived && !r.fork)\n .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))\n .slice(0, 6)\n .map(r => ({ name: r.name, url: r.html_url, updatedAt: r.updated_at }));\n repoCache = { data, ts: Date.now() };\n res.json(data);\n } catch (err) {\n res.status(500).json({ error: String(err) });\n }\n});"
|
||||
},
|
||||
{
|
||||
"repo": "goonk",
|
||||
"file": "public/scripts/easter-eggs.js",
|
||||
"lang": "js",
|
||||
"type": "code",
|
||||
"code": "const typedWordAchievements = ['sudo', 'vim', 'emacs', 'gravity'];\nif (typedWordAchievements.includes(achievementKey.toLowerCase()) && !unlocked.includes('console')) {\n setTimeout(() => unlockAchievement('CONSOLE'), 1000);\n}"
|
||||
},
|
||||
{
|
||||
"repo": "adventure",
|
||||
"file": "chronicle-3: The Last Watch",
|
||||
"lang": "en",
|
||||
"type": "lore",
|
||||
"code": "Maret fell at the fourth gate. Thessen did not return from the lower passage. I have not heard Voss in three days.\n\nI keep the watch alone.\n\nThe Heart is no longer dormant. I can feel it thinking through the walls -- old and patient and vast, the way a mountain is patient. It is testing the light. Each morning the Sunstone seems a little dimmer.\n\nI do not believe I can hold this post much longer. I am leaving notes for whoever comes after. I hope they are better prepared than we were.\n\n-- Aldric"
|
||||
},
|
||||
{
|
||||
"repo": "adventure",
|
||||
"file": "scroll-06: Mira Voss",
|
||||
"lang": "en",
|
||||
"type": "lore",
|
||||
"code": "My name is Mira Voss. I reached level eight. If you find this, it means I did not come back. The wraiths cannot be killed, only avoided."
|
||||
},
|
||||
{
|
||||
"repo": "djupet",
|
||||
"file": "events.ts",
|
||||
"lang": "sv",
|
||||
"type": "lore",
|
||||
"code": "Det finns en anledning till att alla änder vid Ribersborg vänder sig mot Turning Torso i ett nästan tillbedjande tillstånd. De anar mer än oss."
|
||||
},
|
||||
{
|
||||
"repo": "djupet",
|
||||
"file": "events.ts",
|
||||
"lang": "sv",
|
||||
"type": "lore",
|
||||
"code": "Skatteverket har skickat ett brev. De undrar om vår religiösa verksamhet är momsbefriad. Syster Hassan svarar dem med ett 47 sidor långt svar. De hör inte av sig igen."
|
||||
},
|
||||
{
|
||||
"repo": "djupet",
|
||||
"file": "events.ts",
|
||||
"lang": "sv",
|
||||
"type": "lore",
|
||||
"code": "Ett IKEA-bord av modell LACK har börjat blöda svart vätska. Vi har lagt en matta över det. Det hjälper inte men det ser snyggare ut."
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user