- Search becomes a tabbed lookup: Pokémon (offline snapshot) / Moves / Items. Moves + Items lazy-load a name index from PokéAPI (SW-cached) and filter client-side; short move result sets get type/class/power chips - MoveDetail (#/move/:id): type-themed header, power/accuracy/PP/priority, effect text with %-substitution, target, ailment/crit/drain/stat-change summary, learned-by count - ItemDetail (#/item/:id): item sprite, category, cost, fling power, attributes, held-by count, effect text - Loose name matching (hyphens vs spaces); run-token guards stale async renders when the query or tab changes mid-fetch Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.3 KiB
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. 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 settings + selection as JSON lives in Settings.
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.
- 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); 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 (offline, from the snapshot), Moves and Items (lazy name index + on-demand detail, SW-cached), each with its own detail page. Query, scroll and tab persist.
- Settings — theme (System / Light / Dark / Black / Sepia) + accent colour, sprite style, JSON export/import.
- PWA — Workbox SW: precache shell + snapshot, SWR for API JSON, cache-first LRU for sprites, in-app update toast.
Not yet done: version-exclusive badges, type/generation filters on the grid, an interactive region map (PokéAPI has no map imagery or coordinates), skeleton loaders, install-prompt handling, PNG/maskable raster icons (currently SVG only), and a Lighthouse PWA 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.