- ci.yml runs `npm run format:check` before the build. - CONTRIBUTING / AGENTS: point at `npm run format` instead of the old "no formatter" note. - README: new "Built with AI assistance" section — the project is developed with Claude / Claude Code, human-directed and reviewed; visible in the Co-Authored-By trailers. CONTRIBUTING asks contributors to disclose AI-assisted PRs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
Pocketdex
An offline-first Pokédex, team builder and save-file tracker, built on PokéAPI. Vanilla JS + Vite + Workbox — no UI framework, no runtime dependencies in the app bundle.
- Pick a game and everything reshapes to it — regional numbering, era-accurate typings, that generation's type chart and move data, the right flavour text.
- Track seen/caught per game. Catching Pikachu in Blue says nothing
about your Gold save.
#/progressshows each game's own tally. - Import a real save file (
.sav/.srm/.dsv), Gen 1–5, and read the Pokédex straight out of it. - Works offline after the first load, and installs as a PWA.
Not affiliated with Nintendo, Game Freak or The Pokémon Company. This is a non-commercial fan reference tool. See Legal.
Quick start
Requires Node 20+.
git clone https://github.com/OWNER/pocketdex.git
cd pocketdex
npm install
npm run snapshot # fetch the data snapshot from PokéAPI (one time)
npm run dev # http://localhost:5173
| Script | What it does |
|---|---|
npm run snapshot |
Build src/data/snapshot.json from PokéAPI. Skips if the file is under 30 days old; npm run snapshot -- --force to rebuild anyway. |
npm run dev |
Vite dev server with HMR. |
npm run build |
Runs snapshot (as prebuild), then builds to dist/. |
npm run preview |
Serve the production build. Use this to test the service worker / offline / install — none of that runs under npm run dev. |
The snapshot is git-ignored — it's a build artifact. npm install
doesn't create it; run npm run snapshot (or npm run build) first, or
the app has no data to show.
Features
Dex grid
Type-tinted cards — official artwork over a type-coloured spotlight, ghost number, per-dex progress ring. Filter by name/number; chips for caught / missing / favourites / legendary / has-a-note; a filter drawer for type, generation, ability, egg group, minimum BST and "fully evolved only" (all derived client-side from the snapshot — no extra fetches). Sort by dex number, name, any base stat, BST, height, weight, base EXP, catch rate or "recently caught". When a specific game is selected, cards show a version-exclusive badge ("Ruby only"), derived from baked wild-encounter data (so gift/trade-only exclusives are missed).
Detail page
Type-gradient hero with a shared-element morph from the tapped card, then a tabbed sheet:
- About — flavour text for the selected game (with an "all entries" disclosure), physical data, abilities (each linking to its own page), held items, egg groups, gender rate, catch rate, growth rate.
- Stats — colour-coded animated bars with an animated total.
- Evolution — the chain re-parented to the selected game's generation.
- Moves — the full learnset with per-move power / type / damage class /
accuracy / PP / effect, era-accurate via PokéAPI
past_values(including pre-Gen-4 physical/special-by-type). The TM/HM group is ordered by machine number for the selected game. - Locations — wild encounter locations for the selected game.
Forms (Mega, Gigantamax, regional, alternate formes) get a pill switcher that rebuilds types / stats / matchups / abilities / learnset / artwork. Purely cosmetic variants — Unown's letters, Vivillon's patterns, Alcremie's decorations, Pikachu caps, seasonal Deerling, and so on — get a separate compact Appearance dropdown, plus a per-species "form dex" checklist tracked independently of the main caught flag.
Team builder (#/team)
A lineup of up to 6 (specific forms included). Three modes:
- Coverage — leads with a weak spots summary (types that hit 2+ members, or that someone's weak to and nobody resists), then a full defensive matchup grid with a diverging resist ◀│▶ weak bar per type, offensive STAB gaps, and at-a-glance stats.
- Compare — the six stacked side by side. (The same table is also a
standalone
#/compareview with its own picker, up to 4 Pokémon.) - Calc — a damage calculator. Attacker + move + defender → damage range, % of HP, hits-to-KO, STAB/effectiveness badges, with level / nature / EV controls. It's a base-stats estimate: 31 IVs assumed, no items / abilities / weather / terrain.
All three follow the selected game — its generation's type chart,
era-accurate typings (pre-Gen-6 Clefairy is Normal, etc.) and era move
data. Links out to a Natures table, an interactive Type chart
(#/types — tap a type for its offensive + defensive breakdown, gen-aware)
and a Breeding helper (#/breeding — egg-group browser, compatibility
check, and egg moves resolved to the base breeding stage).
Search (#/search)
Tabbed lookup — Pokémon, Moves, Items, Abilities — all browsable offline from the snapshot. Moves filter by type / damage class / "TMs only" / "HMs only" for the selected game and sort by power / accuracy / recency, or by TM/HM number when a machine filter is on. Items filter by category. The move, ability and item detail pages each list the Pokémon connected to them (learners / users / wild holders) as a filterable grid, narrowed to the selected game. Query, scroll, tab and filters persist.
Progress by game (#/progress)
Every game's seen/caught tally against its own dex, from its own per-game bucket. Leads with the games you've played (selected at least once, or imported a save for); the rest sit behind a toggle. The National Dex row is the union across all games.
Shiny hunt tracker (#/shiny)
Per-hunt counters with method-aware odds (full odds, Masuda, SOS, chain fishing, DexNav, radar, dynamax adventures, outbreaks, mass outbreaks) and Shiny Charm, cumulative-probability readout, notes, and a "found it" that marks the Pokémon caught.
Save-file import
Settings → Import from a game save. Reads the game's own seen/owned bitfields — not an approximation — from:
| Gen | Games | Format |
|---|---|---|
| 1 | Red / Blue / Yellow | 32 KB SRAM (.sav / .srm) |
| 2 | Gold / Silver / Crystal | 32 KB SRAM — checksum picks G/S vs Crystal |
| 3 | Ruby / Sapphire / Emerald, FireRed / LeafGreen | 128 KB — newer slot, rotated sections |
| 4 | Diamond / Pearl / Platinum, HeartGold / SoulSilver | 512 KB NDS — active slot, DeSmuME .dsv footer stripped |
| 5 | Black / White, Black 2 / White 2 | 512 KB NDS — per-form "seen" copies OR'd |
Gen 1–3 are checksum-verified; Gen 4–5 offsets come from PKHeX and are validated structurally (nothing set past the last species; every caught species is also seen). A dialog then offers merge or replace into one game's bucket — the selected game if the save could be it, otherwise the parser's best guess.
Full local state (settings, per-game tracking, team, form tracking, shiny hunts, played games) also exports / imports as a single JSON file.
Settings
Theme (System / Light / Dark / Black OLED / Sepia) + accent colour; text size; an in-app reduce-motion override (the OS preference is always respected too); sprite style (modern pixel / game-era pixel / official artwork / HOME); haptic feedback on catch (Vibration API); which screen to land on at launch; JSON export / import; cache and tracking resets.
PWA
Workbox service worker: precache the app shell + snapshot; stale-while-
revalidate for PokéAPI JSON (30-day TTL); cache-first LRU for sprites;
in-app update toast. Installable with maskable icons, beforeinstallprompt
captured for an in-app offer, and manifest shortcuts to Team / Search /
Type chart.
How it works
Data & storage
| Layer | Where | Notes |
|---|---|---|
| Preferences + tracking | localStorage (pdx.* keys) |
Tiny, synchronous, restored on reload. |
| Build-time snapshot | src/data/snapshot.json (~150 KB) |
Every species (id, name, generation, current + past typings, base stats, height/weight, species flags), every regional Pokédex, every version group, plus move / item / ability name indexes and baked TM numbers and version-exclusivity. Precached by the SW. |
| Detail data | pokeapi.co, fetched lazily per page |
Stats, abilities, flavour text, evolution, encounters. Cached by the SW (stale-while-revalidate). |
| Sprites | PokéAPI sprites repo, cache-first | Capped LRU. |
The list, search, sort, filters and game switching run entirely off the snapshot — no network until you open a detail page.
Per-game tracking
src/store/selection.js:
pokemon: { [id]: { favorite, note } } // global — describe the Pokémon
games: { [vgKey]: { [id]: { seen, caught } } } // per game
entry(id) / toggle(id, field) / stats(ids) default to the selected
game; favorite and note are always global. The all key ("All games"
mode) reads as the union of every game — and is where an older
single-record store or JSON backup migrates to (normalizeSelection), so
nothing is falsely attributed to a game you never played.
Routing & rendering
Hash router (src/router.js) over a small route table. Each view is an
async function that returns a DOM node; the router swaps it into #view
using the View Transitions API when available (and when the user hasn't
asked for reduced motion). Detail routes get a shaped skeleton first so the
tapped card's sprite can morph into the hero.
No framework. DOM is built with a ~30-line el() helper
(src/lib/dom.js). State is a handful of localStorage-backed reactive
stores from createStore() — get(), set(patch | fn),
subscribe(fn) → unsub, replace(next).
Project layout
scripts/build-snapshot.mjs PokéAPI → snapshot.json
src/
main.js boot: theme, nav, router, SW registration, install prompt
router.js hash router + View Transitions
sw.js Workbox service worker (injectManifest)
store/ createStore + settings / selection / team / ui / …
data/ snapshot loader, pokedex resolver, lazy API client,
type chart, natures, game colours
lib/ dom, anim, damage-calc, savedex, swipe, dialog, haptics, …
components/ Card, Sprite, TypeChip, StatBar, CompareTable, Nav, …
views/ one file per route (DexGrid, PokemonDetail, TeamView, …)
styles/ tokens.css (palette, type colours, themes), layout.css
Deployment
Static output — host dist/ anywhere.
Sub-path (e.g. a GitHub Pages project site at /pocketdex/):
BASE_PATH=/pocketdex/ npm run build
Docker (multi-stage Node → nginx):
docker compose up --build # → http://localhost:8080
# or
docker build -t pocketdex .
docker run --rm -p 8080:80 pocketdex
The build stage's prebuild fetches the snapshot, so docker build needs
network access to pokeapi.co unless a fresh src/data/snapshot.json is
already present (it's copied in and reused). Sub-path:
docker build --build-arg BASE_PATH=/pocketdex/ .. nginx.conf sets the
SPA fallback and long-cache headers for /assets/* while keeping
index.html / sw.js uncached.
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md for setup, project conventions, and what's in and out of scope. AI coding agents: there's an AGENTS.md.
Known gaps: no interactive region map (PokéAPI has no map imagery or coordinates), no automated test suite yet, and no formal Lighthouse pass.
Built with AI assistance
Pocketdex is developed with heavy use of AI coding tools — primarily
Anthropic's Claude, via Claude Code. Direction, design decisions, review
and testing are human-led; a large share of the implementation is
AI-generated and then reviewed before it lands. You'll see this in the git
history (Co-Authored-By: Claude … trailers).
This doesn't change anything for contributors: use whatever tools you like, disclose AI-generated PRs in the description, and expect the same review either way. There's an AGENTS.md so agents have the house rules too.
Legal
Pocketdex is an unofficial, non-commercial fan project. It is not affiliated with, endorsed by, or sponsored by Nintendo, Game Freak, The Pokémon Company, or PokéAPI.
- Pokémon and all related names, sprites and artwork are © Nintendo / Game Freak / The Pokémon Company. They are served at runtime from PokéAPI and its sprite repository; none are committed to this repository, and none should be added in a pull request.
- Game data is provided by PokéAPI under its terms.
- The build-time snapshot (
snapshot.json) is generated locally from PokéAPI and is git-ignored.
If you represent a rights holder and have a concern, please open an issue.
License
MIT — applies to the Pocketdex source code only, not to any Pokémon data or assets it displays.



