- Save import now opens a small choice dialog (new lib/dialog.js) instead of a confirm(): "Add to my Pokédex" (merge, keeps everything) or "Replace mine with this". Replace resets seen/caught to exactly the save but keeps favourites and notes (new selection.replaceFlags). The dialog also explains that caught state is shared across games, so an imported save only ever adds species that game has and they count toward the National Dex too — no per-game caught flag, by design. - The dex-feed progress ring is now an <a href="#/progress"> with a hover/press nudge — tapping it opens the per-game breakdown. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
Pokédex PWA
A responsive, offline-capable Pokédex reference built on PokéAPI. Vanilla JS + Vite + Workbox — no UI framework.
Two headline features
| Feature | How it works |
|---|---|
| Game series selection | Pick a game (PokéAPI version group, e.g. Scarlet & Violet). If it has more than one regional dex (Paldea / Kitakami / Blueberry) a sub-dex switcher appears. The choice drives which species show, their regional numbering, and which version's flavor text the detail page uses. Stored in localStorage. |
| Unified Pokémon selection | One tracking record per Pokémon, keyed by National Dex id. Mark Pikachu once and every game's view reflects it — each dex computes its own "Seen / Caught" totals by intersecting its species list with that single map. #/progress breaks that down for every game at once. The active Pokémon is also a single deep-linkable route (#/pokemon/25). |
Data & storage split
- Preferences + tracking state →
localStorage(pdx.settings,pdx.selection). Tiny, synchronous, restored on reload. - Build-time snapshot (
src/data/snapshot.json, ~150 KB) → generated bynpm run snapshotfrom PokéAPI: every species (id, name, generation, types), all regional Pokédex lists, and all version groups. Precached by the service worker, so the list / search / game switching work on the first offline launch. - Detail data (stats, abilities, flavor text, evolution) → fetched lazily from
pokeapi.coper Pokémon, then cached by the service worker (stale-while-revalidate, 30-day TTL). - Sprites → cache-first with a capped LRU.
Export / import of all local state (settings, tracking, team, form
tracking, shiny hunts) as JSON lives in Settings, which also imports
the Pokédex straight out of a game save file (.sav / .srm):
Gen 1 (R/B/Y), Gen 2 (G/S/C) and Gen 3 (R/S/E, FR/LG). It reads the
game's own seen/owned bitfields — checksum-picking Gold/Silver vs
Crystal, resolving Gen 3's newer slot and rotated sections — and merges
them in without removing anything.
Scripts
npm install
npm run snapshot # fetch data from PokéAPI -> src/data/snapshot.json (run once; refreshes if >30 days old)
npm run dev # vite dev server
npm run build # runs snapshot (prebuild) then vite build
npm run preview # serve the production build (needed to exercise the service worker)
Deploying under a sub-path (e.g. GitHub Pages project site): BASE_PATH=/repo-name/ npm run build.
Docker
Multi-stage build (Node → nginx) that serves the static dist/:
docker compose up --build # → http://localhost:8080
# or without compose:
docker build -t pokedex-pwa .
docker run --rm -p 8080:80 pokedex-pwa
The build stage runs npm run build, whose prebuild step fetches the
PokéAPI snapshot — so docker build needs network access to pokeapi.co
unless a fresh src/data/snapshot.json is already present (it gets copied in
and reused). Serving under a sub-path: docker build --build-arg BASE_PATH=/dex/ ..
nginx.conf sets the SPA fallback and long-cache headers for /assets/*
while keeping index.html / sw.js uncached.
Project layout
scripts/build-snapshot.mjs PokéAPI -> snapshot.json
src/
main.js boot: theme, nav, router, SW registration
router.js hash router (#/, #/pokemon/:id, #/games, #/search, #/settings)
sw.js Workbox service worker (injectManifest)
store/ createStore + settings + selection (localStorage)
data/ snapshot loader, pokedex resolver, lazy API client
components/ Card, Sprite, TypeChip, StatBar, ProgressHeader, Nav
views/ DexGrid, PokemonDetail, GamePicker, SearchView, SettingsView
styles/ tokens.css (palette, type colors, light/dark), layout.css
Status
Working app with a type-themed UI:
- Dex grid — type-tinted cards (official artwork, spotlight, ghost number), per-dex progress, name/number filter, caught/favorite filters, plus a filter drawer: type, generation, ability, egg group, minimum BST, and "fully evolved only" (derived client-side from each species' evolves-from link — no extra fetches). A "Notes" chip filters to Pokémon with a note; cards with one get a small 📝 mark. When a specific game is selected, cards show a version-exclusive badge ("Ruby only") — derived from the snapshot's baked wild-encounter data, so gift/trade-only exclusives are missed.
- Notes — a free-text note per Pokémon on its detail page (trade plans, where you caught it, anything), autosaving as you type and flushing immediately on blur or navigation so a quick tab-away never drops a keystroke. Rides along in the existing JSON export/import.
- Forms — Megas, Gigantamax, regional forms and alternate formes in the snapshot; a pill switcher on the detail page rebuilds types / stats / matchups / abilities / learnset / artwork for the chosen form. Purely cosmetic variants (Unown's 27 letters, Vivillon's 19 patterns, Furfrou trims, seasonal Deerling/Sawsbuck, Flabébé line colours, Alcremie's 62 decorations, Pikachu caps — 278 across 50 species) get a separate compact "Appearance" dropdown that only swaps the sprite. Cards show a "+N forms" badge.
- Detail — type-gradient hero + tabbed sheet (About / Stats / Evolution /
Moves / Locations); coloured animated stat bars; defensive type matchups;
evolution chain re-parented to the selected game's generation; learnset with
per-move power/type/accuracy/PP/effect (era-accurate via
past_values, incl. pre-Gen-4 physical/special-by-type), the TM/HM group ordered by TM number for the selected game; wild encounter locations. - Game-aware — abilities gated to Gen 3+ (hidden to Gen 5+); type chart applies Gen 1 / pre-Gen 6 rules.
- Games — overlay picker with stylised version-colour cover tiles, sub-dex switch, and an "All games" (National, no gen limits) option.
- Search — tabbed lookup: Pokémon, Moves, Items and 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, with a machine filter on, by TM/HM number (numbers are baked into the snapshot). Items filter by category. Each has its own detail page; the move, ability and item pages list the Pokémon connected to them (learners / users / wild holders) as a filterable grid, narrowed to the selected game (by generation, or exactly by version for held items). Query, scroll, tab and filters persist.
- Team — a lineup of up to 6. Coverage leads with a "weak spots"
summary (types that hit 2+ members, or that someone's weak to and nobody
resists), then a full matchup grid with a diverging resist◀│▶weak bar per
type; plus offensive STAB gaps and at-a-glance stats. A Compare table
stacks the six side by side (the same table is also a standalone
#/compareview with its own picker — up to 4 Pokémon, persisted). A third Calc mode is a damage calculator — pick (or quick-pick from your team) an attacker + move + defender, and get a 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. Toggleable in the nav from Settings. Links out to a Natures table and an interactive Type chart (tap a type for its offensive + defensive breakdown; both gen-aware). - Settings — theme (System / Light / Dark / Black / Sepia) + accent colour, text size, an in-app reduce-motion override (on top of the OS preference, which is always respected too), sprite style, haptic feedback on catch (Vibration API), which screen to land on at launch, JSON export/import.
- PWA — Workbox SW: precache shell + snapshot, SWR for API JSON,
cache-first LRU for sprites, in-app update toast. Installable: PNG +
maskable icons,
beforeinstallpromptcaptured for an in-app "Install" offer (toast + Settings button), manifest shortcuts to Team / Search / Type chart.
Not yet done: an interactive region map (PokéAPI has no map imagery or coordinates), and a formal Lighthouse pass.
Notes
npm auditreports the known esbuild dev-server advisory via Vite 5. It affects the local dev server only, not the production build. Vite 8 (which fixes it) requires a newer Node than this environment has.- Data and images © Nintendo / Game Freak / The Pokémon Company, served via PokéAPI. This project is a non-commercial reference tool.