New #/types route (linked from the Team header alongside Natures): the full 18x18 attack/defend grid, gen-aware from the selected game — drops Fairy pre-Gen 6, Steel/Dark pre-Gen 2, and applies the Gen 1 / pre-Gen 6 quirks the type-chart module already models. Tap a type on either edge to highlight its row+column and get an offensive + defensive breakdown as type chips. 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. 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, 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.
- 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. 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.
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.