dex/README.md
chris 671e72972a Apply Prettier to the whole tree
Pure formatting — no behaviour change. Verified: build passes, all 14
routes render, service worker active, no console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 11:24:31 -04:00

298 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 15, 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).
<p align="center">
<img src="docs/screenshots/dex-grid.png" alt="Dex grid" width="24%">
<img src="docs/screenshots/detail.png" alt="Pokémon detail" width="24%">
<img src="docs/screenshots/team-coverage.png" alt="Team coverage analysis" width="24%">
<img src="docs/screenshots/progress.png" alt="Per-game progress" width="24%">
</p>
---
## 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 13 are checksum-verified; Gen 45 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.