Learn Git commands by typing them for real — an animated codebook, solo practice with three difficulties, and a real-time multiplayer battle with power-ups. Bilingual (Español / English), with light & dark themes and sound effects.
npm install
npm startOpen http://localhost:3000. The server binds to 0.0.0.0, so other players on your network can
join at http://<your-ip>:3000.
Cloudflare Pages serves only static files — there is no Node server — so multiplayer there runs over
the InsForge host-authoritative transport (see Multiplayer transports).
The build just assembles public/ into dist/ and injects the InsForge config:
npm run build # -> dist/ (also copies insforge-config.js if present locally)Option A — direct upload from your machine (uses your local public/js/insforge-config.js):
npm run deploy # npm run build && wrangler pages deploy dist --project-name git-battleOption B — git-connected build (Cloudflare clones the repo and builds). Since insforge-config.js
is gitignored, provide the config via environment variables so the build generates it:
| Cloudflare Pages setting | Value |
|---|---|
| Build command | npm run build |
| Build output directory | dist |
Environment variable INSFORGE_URL |
your https://<appkey>.us-east.insforge.app |
Environment variable INSFORGE_ANON_KEY |
your anon_… key |
The anon key is browser-safe (Row-Level-Security-protected) and is shipped to every browser anyway,
so it is fine as a Cloudflare env var. Never put the uak_ admin key here. Without any config the site
still builds and Practice/Learn work — only multiplayer needs the backend.
Routing is hash-based (#learn/…), so no _redirects file is needed.
An interactive reference of 28 commands grouped into chapters (Getting Started, Branching, Remotes, History & Undo, Advanced, Pro Tools) plus a Guides & Best Practices chapter.
- Syntax-highlighted commands and an animated diagram of what each one does
(
.gitappearing oninit, files sliding into staging onadd, branches diverging and merging, commits flying to the cloud onpush, three messy commits collapsing into one on squash…). - A real-life anecdote for every command.
- Guides: Squash vs Merge vs Cherry-pick (when to use / when NOT to), .gitignore (why it matters + quick generation via the toptal generator), and Good commits (do / don't).
- Progress tracking (saved in your browser): you confirm you've read an entry by clicking Mark as read (opening it only sets a 📍 bookmark to resume where you left off), so a ✓ really means you read it. Each entry can be toggled read/unread.
- Every entry is deep-linkable as
#learn/<id>— failed questions link straight to the relevant page.
5 lessons, each playable at 3 difficulties:
- Easy — the command is shown with one part blanked out; type the missing part (or the full command).
- Normal — you only get the description plus small hints; type the full command.
- Hard — only the description is shown; type the full command from memory.
Placeholders like <file>, <name>, "<message>" accept any value. When you miss a question you
get a link to learn it in the codebook. Each difficulty is tracked per lesson: ✓ done, ★ perfect.
Lessons unlock progressively — finish one to open the next.
A gamification section (all in your browser): a daily streak counter and 12 medals — each with its own SVG badge, greyed until earned, some hidden until unlocked. Earn them by practicing, reading the codebook, keeping streaks, and winning multiplayer (including a hidden "win without power-ups"). Unlocks pop a toast, and each earned medal has a Share button.
- One player clicks Create room and shares the 4-letter code.
- Others Join with the code; the host sees the live player list and configures the match, then presses Start (2–6 players).
- Everyone answers the same question each round — speed-based scoring: every correct answer scores
50 + remaining-seconds × 10, so faster earns more but slower still scores. 15 s per round, then a 5-second pause showing everyone's result. Highest total wins.
Host settings (in the lobby): the host picks the mode, the number of questions, and which power-ups are available. Questions are drawn randomly from a bank of ~60 varied commands so matches don't repeat.
| Mode | Draws from (Learn chapters) |
|---|---|
| Novice | Getting Started + Branching |
| Medium | Remotes + History & Undo |
| Hard | Advanced |
| Expert | All chapters |
Power-ups — a fixed inventory per game (2 freeze, 2 reverse, 1 time):
| Power | Effect |
|---|---|
| 🧊 Freeze | Blocks opponents' input for 5 s so you can type freely |
| 🔄 Reverse | Flips opponents' text right-to-left for 5 s |
| ⏱ +Time | Adds 5 s to the round |
Power-ups only ever affect other players, and only one can be active at a time in the room — while a power-up is running, no one else can trigger one until it ends.
- Bilingual: 🌐 toggle between Español and English (commands are always in English; everything else is translated). Preference is saved.
- Light / dark theme: ☀️/🌙 toggle, saved.
- Sound: 🔊 toggle — a rising arpeggio on a correct answer, a low buzz on a wrong one.
- ♿ Accessibility (⚙️ in the header, or the menu card): high contrast, larger text (3 sizes), a dyslexia-friendly font (extra letter/line spacing, system fonts only), and reduce-motion (turns off confetti and non-essential animations). Preferences are saved. Keyboard focus is always visible.
- SEO/GEO metadata (title, description, Open Graph, JSON-LD) and a custom SVG logo / favicon.
server/index.js Express + Socket.IO — rooms, N-player round loop, speed scoring, power-ups
server/questions.js Re-exports the shared question bank for Node
public/index.html Single-page app (screens toggled with JS)
public/css/style.css Terminal-inspired theme (light & dark via CSS variables)
public/js/
i18n.js ES/EN strings + language switching
progress.js localStorage progress (read, bookmark, practice completion)
a11y.js Accessibility settings (contrast, text size, dyslexia font, reduce motion)
codebook-data.js 24 commands (bilingual desc + anecdote + animation key)
guides-data.js Guides & best-practices content
codebook.js Codebook nav, syntax highlight, animation engine
lessons-data.js Practice lessons (reference codebook commands)
app.js Screen router, theme, sound, celebration animations
practice.js Single-player logic (3 difficulties, answer checking)
questions-data.js Shared multiplayer question bank (browser + Node source of truth)
net.js MPNet transport: Socket.IO (local) OR InsForge host-authoritative (prod)
multiplayer.js Multiplayer UI — lobby, rounds, power-ups, timer (talks to MPNet)
insforge-config.example.js Copy to insforge-config.js (gitignored) to enable InsForge
The multiplayer UI talks to MPNet (see public/js/net.js), which mirrors the Socket.IO API
but runs over one of two interchangeable backends, chosen automatically at connect time:
- Socket.IO (
npm start, local dev) — the Node server inserver/index.jsis authoritative. - InsForge realtime (production, static hosting, no Node) — host-authoritative: the host
player's browser runs the game loop (ported from the server) and publishes authoritative events over
the channel
room:CODE; other players publish intents. If the host leaves mid-game, the next player is elected host and resumes from the last state snapshot. Identity is a per-tab id (no accounts). Anti-flood: client-side throttle + host-side per-client rate limiting.
InsForge is used when public/js/insforge-config.js is present (copy it from the .example.js file
and fill in your anon key — never the uak_ admin key). The browser only ever uses the anon key,
which is protected by Row-Level Security. Force a backend with ?net=socket / ?net=insforge.
Tech: Node.js, Express, Socket.IO, InsForge realtime, and vanilla HTML/CSS/JS (no build step).
See CONTRIBUTING.md for dev setup, the multiplayer architecture, project
conventions, and — importantly — how to handle secrets in this public repo (the browser-safe
anon key vs. the uak_ admin key, and how to rotate a leaked key).
