dex/README.md
chris 671e72972a Apply Prettier to the whole tree
Pure formatting — no behaviour change. Verified: build passes, all 14
routes render, service worker active, no console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 11:24:31 -04:00

15 KiB
Raw Blame History

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. #/progress shows each game's own tally.
  • Import a real save file (.sav / .srm / .dsv), Gen 15, 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.

Dex grid Pokémon detail Team coverage analysis Per-game progress


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 #/compare view 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).

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 13 are checksum-verified; Gen 45 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.


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.