Unlike maze-style Vim games, every challenge runs inside a real CodeMirror editor with genuine Vim keybindings — the skills transfer 1:1 to actual Vim/Neovim.
Tip
Try it in 30 seconds: npm install && npm run dev → press x once and
you've beaten level 1 at par. It escalates from there. 😄
| 🎯 VimGolf scoring | every keystroke counts — beat par for ⭐⭐⭐ |
| 👾 Boss fights | multi-stage battles with a keystroke-budget HP bar; undo can't rewind a cleared stage, and losing costs nothing but a retry |
| 🤖 Your Hero | a cel-shaded robot you restyle — body/visor/accessory colors + a custom aura — that idles, punches while you type, and celebrates your wins |
| 🌋 Living worlds | WebGL scenes behind smoked-glass panels (react-three-fiber, selective bloom) — with a full SVG lite mode for low-power devices |
| 🧠 Vim-state goals | challenges can verify registers, marks, modes and macros — not just buffer text |
| ⌨️ Command Belt | your growing, category-grouped collection of mastered commands |
| 📄 Downloadable cheatsheet | every command the game teaches, grouped by world — export as Markdown or a printable HTML page (→ Save as PDF), or open it in-play to look a key up |
| 🎛️ Play, unstuck | look up Commands, Restart a fumbled attempt, or Quit to the map — right from the play screen, without losing Vim focus |
| 🕹️ Motion Rush | whack-a-mole arcade drilling hjkl speed with combos |
| 📱 Quiz mode | a touch-first, tap-to-answer trainer covering the whole curriculum — the mobile way to drill when the real editor isn't practical, no keyboard needed |
| 💾 Zero backend | progress in localStorage with versioned migrations; fonts & models self-hosted — fully offline |
| World | You learn | Boss | Status | |
|---|---|---|---|---|
| 🟢 | 1 · Survive | modes i a o Esc · motion hjkl · first edits x dd u |
🚪 The Gatekeeper | ✅ |
| 🔵 | 2 · Comfortable | words w b e · line ends 0 $ · jumps gg G · f · cw |
🖋️ The Proofreader | ✅ |
| 🟠 | 3 · Faster | operators × motions × text objects (ciw ci( ci" daw cit) · visual v V Ctrl-v |
⚔️ The Refactor Gauntlet | ✅ |
| 🟣 | 4 · Seeker | search / ? n * · find f t ; · % · substitute :s :%s//g :s///gc · marks |
🔎 Grep & Gut | ✅ |
| 🩷 | 5 · Superpowers | registers "a "0 "_ "A · macros q @ @@ · the dot . · gn · Ctrl-a |
🏭 The Automaton | ✅ |
| 🟡 | 6 · Legend | :g :v · :sort · :normal · case ops gU g~ · J gJ · block-insert I $A · gqip |
🐉 The Archivist | ✅ |
All six worlds shipped — 77 levels (71 challenges + 6 bosses) — every par machine-proven solvable, and each new level re-verified solving at ⭐⭐⭐ in a real browser.
![]() |
![]() |
| A real editor, your Hero, and a living 3D world | Boss fights drain a keystroke-budget HP bar |
![]() |
![]() |
| Star-rated progression across worlds | Motion Rush — drill hjkl speed for combos |
One Hero, styled your way — pick body / trim / visor colors, a visor style, an accessory and a custom aura, previewed live in 3D. It then shows up everywhere you play. Earn coins by clearing levels and spend them in the Shop on animated scene backgrounds and terminal color themes.
npm install
npm run dev # play at http://localhost:5173
npm run build # production build → dist/
npm run preview # serve the build (port 4173)Deploy dist/ anywhere static (Netlify / Vercel / GitHub Pages). No special headers —
it's CodeMirror's vim keymap, not WASM, so no cross-origin isolation needed.
The game is 100% playable anonymously. Running the tiny zero-dependency backend in
server/ adds Google / GitHub sign-in, cross-device progress
sync (local + server merged on login, nothing lost), and verified share links
(?u=<id>) whose numbers come from the database instead of editable text.
docker compose up wires it automatically (/api behind nginx); a static deployment
simply hides all account UI. Set GOOGLE_CLIENT_ID/SECRET and/or
GITHUB_CLIENT_ID/SECRET plus SESSION_SECRET — see server/.env.example.
Note
Graphics tiers: quality is auto-detected per device — there's no toggle to fuss with. Reduced-motion, low-memory, or software-GL devices get Lite: the original procedural-SVG art, fully featured and not a downgrade of gameplay. If the WebGL context is ever lost, the session falls back to Lite automatically.
npm run test # 105 vitest tests
npm run qa # browser checks (needs `npm run preview` running)
npm run typecheck
cd server && npm test # backend tests (validator + live HTTP round-trips)| Layer | What it guarantees |
|---|---|
tests/content.test.ts |
ids unique · taught commands resolve · cursors in bounds · boss budgets sane |
tests/par.test.ts |
every challenge's par is solved by a reference solution driven through the real vim keymap — including search/ex/confirm dialogs, macros, Ctrl-a, :g/:sort/:normal and visual-block edits |
tests/cheatsheet.test.ts |
the downloadable cheatsheet covers every catalog command; the HTML export is self-contained and HTML-escaped |
tests/hero.test.ts · account.test.ts |
the Hero model resolves & normalizes and old saves migrate; the account / share-link layer round-trips |
scripts/qa/ |
real-Chromium suites: tier isolation (lite fetches zero 3D bytes), boss flow, save migration, offline reload |
Important
Adding a challenge? Its reference solution in tests/par.test.ts is mandatory —
see the authoring guide.
flowchart LR
subgraph SYNC["⚡ sync bundle · ~278 KB gz · 3D-free"]
APP[App + Nightglass UI]:::ui
ED[VimEditor<br>CM6 + real vim]:::core
CT[content/tier1-6<br>challenges as data]:::data
VF[verify.ts<br>vim-state checkers]:::data
ST[(zustand store<br>saves v15)]:::data
CT --> ED
VF --> ED
APP --> ED
APP --> ST
end
subgraph LAZY["🎨 lazy chunks · fetched on demand"]
S3[Stage3D underlay]:::three
H3[Hero3D robot]:::three
TH[three + r3f + drei<br>~208 KB gz]:::three
LH[lang-html<br>~64 KB gz]:::three
CS[CheatsheetModal<br>+ generator]:::three
S3 --> TH
H3 --> TH
end
APP -. "React.lazy (webgl tier only)" .-> S3
APP -. "React.lazy" .-> H3
APP -. "React.lazy (on open)" .-> CS
ED -. "loadLang (tag levels only)" .-> LH
classDef ui fill:#7c6bff,color:#0b0d14,stroke:none
classDef core fill:#ff6ac1,color:#0b0d14,stroke:none
classDef data fill:#1a2030,color:#e2e6f0,stroke:#7c6bff
classDef three fill:#4cc9f0,color:#0b0d14,stroke:none
The full story — z-stack contract, quality tiers, save migrations, the goal-check pipeline — lives in docs/ARCHITECTURE.md.
Challenges are pure data — a new level is ~15 lines:
{
id: 't3-daw',
tier: 3,
title: 'Word Surgeon',
brief: 'Delete the duplicated the cleanly with daw.',
taughtCommands: ['aw'],
startText: '… over the the lazy dot',
startCursor: { line: 1, ch: 36 },
goal: { targetText: '… over the lazy dot', describe: 'Single spaces everywhere' },
par: 3,
hint: 'daw = delete A word — the word PLUS its trailing space.',
}Step-by-step guide (goals, par math, bosses, the traps): docs/AUTHORING.md.
- Worlds 1–6 complete · boss mechanic · Nightglass 3D slice · vim-state verification
- 📄 Downloadable cheatsheet (Markdown / printable HTML) · in-play Commands · Restart · Quit
- 🎨 Visual rollout — per-world 3D environments, Map/Shop/Results polish
- ⚙️ Mechanics — achievements, daily quests, spaced-repetition review
- 🏌️ Golf mode, onboarding, remappable keys
| 🤖 3D hero | RobotExpressive by Tomás Laulhé (CC0, mod. Don McCurdy) — meshopt-optimized, re-shaded with the game's toon ramp |
| 🎨 2D Hero mark | original procedural SVG — the lite-tier twin of the 3D Hero, painted by the same colors, visor & accessory |
| 😀 UI glyphs | Twemoji (CC-BY 4.0), bundled locally |
| 🔤 Fonts | Space Grotesk + JetBrains Mono (OFL), self-hosted via Fontsource |
| 🌋 Scenes | original procedural CSS/SVG (lite) and three.js (3D) — no asset downloads |
Free & open source. Built with real Vim keybindings. ⌨️💜









