# Pocketdex An offline-first **Pokédex, team builder and save-file tracker**, built on [PokéAPI](https://pokeapi.co/). Vanilla JS + [Vite](https://vitejs.dev/) + [Workbox](https://developer.chrome.com/docs/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 1–5, 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](#legal).

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

--- ## Quick start Requires **Node 20+**. ```bash 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). ### Search (`#/search`) 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 1–3 are checksum-verified; Gen 4–5 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/`): ```bash BASE_PATH=/pocketdex/ npm run build ``` **Docker** (multi-stage Node → nginx): ```bash 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](CONTRIBUTING.md)** for setup, project conventions, and what's in and out of scope. AI coding agents: there's an **[AGENTS.md](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. --- ## Legal 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](https://pokeapi.co/) 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](LICENSE) — applies to the Pocketdex source code only, not to any Pokémon data or assets it displays.