How it was built

Building COTE

A 3D globe, typo-forgiving spell checking, and a leaderboard shared across every device — built iteratively with Claude Code from first prompt to public deployment.

  • React
  • Vite
  • D3.js
  • TopoJSON
  • Supabase

The requirements

Five things were non-negotiable from the start:

  • A 3D interactive globe updating in real time as players answer
  • Countries filled in with real topographical colors when guessed
  • A global leaderboard shared across all devices, no login
  • Smart spell-checking — accept small typos, suggest near-misses
  • Zero setup for the player: open a link, start typing

The stack, and why each piece

React + Vite

React breaks the game into components (globe, input bar, leaderboard) and re-renders automatically as state changes; Vite provides the instant dev server and production bundling.

D3.js + TopoJSON

D3 projects spherical map data onto a 2D screen and animates rotation; TopoJSON stores every country's outline in a compact format D3 reads directly.

Supabase

A cloud Postgres database with a REST API — the entire backend. It stores the leaderboard so every device reads and writes the same top-10 list.

Claude Code

Wrote, debugged, and iterated the whole codebase — including running a full 197-country simulation to verify rendering before launch.

Under the hood

Rendering the globe

The globe is D3's geoOrthographic projection mapping spherical coordinates to SVG paths — one <path> per country. A requestAnimationFrame loop runs continuously but only re-renders when the projection is "dirty" (the player dragged, or a zoom animation is playing). Fill colors come from a 197-entry TOPO_COLOR table keyed by country ID.

Typo forgiveness, done twice

Two Levenshtein edit-distance functions do different jobs. A fast version with an early exit at distance 2 decides acceptance: any input within one edit of a country name or alias counts. A separate full version computes true similarity for the "Did you mean?" feature — an unguessed country scoring ≥ 75% similarity gets suggested. They were originally one function; splitting them fixed a real bug (see below).

A leaderboard with no server

The game calls Supabase's REST API directly — no SDK, no backend code. Row Level Security allows public SELECT, INSERT, and DELETE, but blocks PATCH; updating a returning player's score is therefore a delete-then-insert, done only when the new score beats their best. Score formula: countries × 1000 − seconds, so more countries always outranks a faster clock.

Bugs that taught something

The gray-country bug

Correctly guessed countries stayed gray. TopoJSON zero-pads IDs ("076" for Brazil) while the game data doesn't ("76"). One conversion — String(+d.id) — fixed all 197, then a simulation of every country name verified it.

"Frnce" → North Macedonia?

The suggestion engine once offered absurd matches. Cause: the fast edit-distance's early exit returned capped values that inflated similarity scores. Fix: a dedicated full-distance function for suggestions only.

The lonely leaderboard

v1 stored scores in localStorage — every device saw only itself. The fix was replacing it wholesale with Supabase, turning a private scoreboard into a global one.

Tuning the zoom

The zoom-on-guess animation shipped at 7×, felt like whiplash, dropped to 5×, still too much — 3× stuck. Feel is found by iteration, not specification.

The Claude Code process

The game was built in phases — core loop, topographic colors, spell checking, cloud leaderboard, then polish — each one specified in conversation, implemented by Claude Code, played, critiqued, and revised. Features were cut as deliberately as they were added: an email-invite button (needed a backend), a copy-link share button (wrong URL in production), and a "share with friends" section all got removed to keep the end screen focused. The result ships as a single JSX file by design — fast to build, easy to reason about, no over-engineering for a game this size.

See it running