- .github/workflows/ci.yml — npm ci + npm run build on push/PR, with the PokéAPI snapshot cached so only the first run pays the fetch cost. - .github/dependabot.yml — weekly npm, monthly actions, grouped. - .github/ISSUE_TEMPLATE/ — bug report, feature request, config with discussion / upstream-data links. - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 (contact method is a placeholder to fill in). - SECURITY.md — private reporting + the known dev-only Vite/esbuild advisory. - AGENTS.md — machine-facing version of CONTRIBUTING for AI coding agents. - docs/screenshots/ + a strip in the README (resized + pngquant'd, ~240 KB). - public/og.png + Open Graph / Twitter card meta in index.html. - .nvmrc (20); gitignore .claude/ .idea/ .vscode/. 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.
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.



