chris 99d472f469 Fix learnset levels and scroll-position loss on detail visits
MovesList: a move can have several version_group_details for one game —
one per learn method, plus a Gen 1/2 data artifact that repeats a
level-up move at level 1 next to its real level (e.g. Gyarados / Dragon
Rage in Red-Blue: level 1, level 25 and a machine entry). The old
`.find()` grabbed the first, so Dragon Rage showed "Lv 1". Now collect
every matching detail, group by method, and keep the highest level per
method — a move can also legitimately land in both "By level up" and
"By TM / HM".

PokemonDetail: the recently-viewed write used the function form of
ui.set(), which *replaces* state instead of merging. Every Pokémon visit
was wiping feedScroll (so Back returned to the top of the dex) along with
the saved sort / filters / tab. Spread `...s`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-08-28 21:42:42 -04:00

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 statelocalStorage (pdx.settings, pdx.selection). Tiny, synchronous, restored on reload.
  • Build-time snapshot (src/data/snapshot.json, ~150 KB) → generated by npm run snapshot from 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.co per 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.
  • 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); 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 and Items, all browsable offline from the snapshot. Moves filter by type / damage class / "TMs in this game" and sort by power / accuracy / recency; Items filter by category. Each has its own detail page. Query, scroll, tab and filters persist.
  • Team — a lineup of up to 6 with a Coverage table (weaknesses, STAB gaps) and a Compare table; toggleable in the nav from Settings.
  • 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 audit reports 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.
Description
No description provided
Readme 2.4 MiB
Languages
JavaScript 79.9%
CSS 19.5%
HTML 0.4%
Dockerfile 0.2%