Name the project Pocketdex; full README, CONTRIBUTING, LICENSE

Rename the app from the working-title "Pokédex" to Pocketdex across the
nav brand, install toast, PWA manifest, index.html title/meta, and
package.json. Generic in-universe uses of "Pokédex" (sub-dex label, "all
Pokédex entries", save-import messages) are left as-is.

Docs for going public:
- README.md rewritten: quick start, full feature list, how-it-works
  (data/storage split, per-game tracking, routing), deployment, legal
  disclaimer, contributing pointer.
- CONTRIBUTING.md: ground rules (no copyrighted assets, framework-free,
  offline-first), setup, project map, code style, common tasks, manual
  test checklist, PR conventions.
- LICENSE: MIT (+ note that it covers source only, not Pokémon data).
- .editorconfig, .github/PULL_REQUEST_TEMPLATE.md.
- package.json: add "engines": node >=20.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
chris 2026-09-10 11:03:23 -04:00
parent c1f6007b25
commit 31cc076c0c
11 changed files with 458 additions and 144 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

25
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,25 @@
<!-- Thanks for contributing to Pocketdex! Keep PRs focused — one change per PR. -->
## What & why
<!-- What does this change, and what problem does it solve? Link any issue. -->
## How I tested
<!-- Manual steps you ran. See CONTRIBUTING.md → "Testing your change". -->
- [ ] `npm run build` succeeds
- [ ] Checked light and dark theme
- [ ] Checked a narrow (mobile) viewport
- [ ] Switched games and confirmed game-aware data still updates (if relevant)
## Screenshots / clip
<!-- Required for anything visual. Before/after if it's a tweak. -->
## Checklist
- [ ] No copyrighted assets committed (sprites, artwork, ROMs, save files)
- [ ] No new runtime dependencies (or discussed in an issue first)
- [ ] `dist/` and `src/data/snapshot.json` are not in the diff
- [ ] Matches the surrounding code style

123
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,123 @@
# Contributing to Pocketdex
Thanks for taking the time. This is a small, framework-free codebase and
easy to get into.
## Ground rules
- **No copyrighted assets.** Don't commit Pokémon sprites, artwork, audio,
ROMs or save files. Everything visual is fetched at runtime from PokéAPI.
PRs that add such files will be asked to remove them.
- **Non-commercial.** No ads, analytics, telemetry, paid tiers, affiliate
links or "sign in" flows.
- **Stay framework-free.** No React/Vue/Svelte/etc., and no new runtime
dependencies without discussing it in an issue first. Build-time dev
dependencies are a lower bar but still worth raising.
- **Offline-first stays true.** The list, search, sort, filters and game
switching must keep working with no network. New always-needed data goes
in the snapshot, not a runtime fetch.
- **Be kind.** Assume good faith in issues and reviews.
## Getting set up
Requires **Node 20+**.
```bash
npm install
npm run snapshot # build src/data/snapshot.json from PokéAPI (git-ignored)
npm run dev # http://localhost:5173
```
To exercise the service worker, offline behaviour or the install prompt,
you need a real build:
```bash
npm run build && npm run preview
```
## Where things live
See the **Project layout** and **How it works** sections in
[README.md](README.md). In short:
- `src/views/<Name>.js` — one file per route, an `async` function returning
a DOM node. Register it in `src/router.js`.
- `src/components/` — reusable pieces (`Card`, `Sprite`, `TypeChip`, …).
- `src/store/``createStore()`-backed reactive state, persisted to
`localStorage` under `pdx.*`.
- `src/lib/` — framework-y helpers with no app knowledge (`dom`, `anim`,
`damage-calc`, `savedex`, …).
- `src/data/` — the snapshot loader, the Pokédex resolver, the lazy PokéAPI
client, the type chart, natures.
- `scripts/build-snapshot.mjs` — the only thing that talks to PokéAPI at
build time.
## Code style
There's no linter or formatter config yet — **match the surrounding
code**:
- 2-space indent, semicolons, single quotes, trailing commas in
multi-line literals (an `.editorconfig` covers whitespace).
- Build DOM with the `el()` helper from `src/lib/dom.js`, not template
strings or `innerHTML` (except the deliberate `html:` prop for trusted
inline SVG).
- Stores: `set(fn)` **replaces** state — spread `...s` yourself. Read with
`get()`, react with `subscribe()`, and unsubscribe in the view's
teardown (`onTeardown(view, off)`).
- Keep comments about *why*, not *what*. Match the existing density.
- Respect `prefersReducedMotion()` for any new animation.
- Keep everything game-aware: if you touch typings, type effectiveness,
learnsets or evolution, honour the selected game's generation
(`typesForGen`, `multiplier(atk, def, gen)`, `past_values`, …).
## Common tasks
**Add a setting** — add the key + default to `src/store/settings.js`
(and `applyTheme()` if it affects the document), then a field in
`src/views/SettingsView.js`. Add it to the export payload if it should
survive a backup.
**Add a snapshot field** — add it in `scripts/build-snapshot.mjs`, run
`npm run snapshot -- --force`, and consume it via `loadSnapshot()`. Keep
the snapshot small; it's precached on every install.
**Add a route** — new file in `src/views/`, one entry in the `routes`
array in `src/router.js`. Add a skeleton if the view does async work
before it can render.
## Testing your change
There's no automated suite. Before opening a PR, manually check:
- The **dex grid** — filters, sort, the progress ring, catching from a
card.
- **Switching games** (the Games sheet) — regional numbers, era typings,
and per-game seen/caught all update.
- A **detail page** — every tab, form switching, the track buttons.
- **`npm run build`** succeeds, and `npm run preview` still works offline
(DevTools → Network → Offline, then reload).
- Light **and** dark theme, and a narrow (mobile) viewport.
Describe what you tested in the PR.
## Commits & pull requests
- Branch off `main`. One focused change per PR.
- Imperative commit subjects ("Add …", "Fix …"), with a body explaining
the *why* when it isn't obvious.
- Don't commit `dist/` or `src/data/snapshot.json` (both git-ignored).
- Fill in the PR template. Screenshots or a short clip for anything
visual.
- It's fine to open a draft PR early to discuss direction.
## Reporting bugs
Open an issue with: what you did, what you expected, what happened, the
browser/OS, and the selected game if it's relevant. A screenshot or the
console output helps a lot.
## License
By contributing you agree that your contributions are licensed under the
[MIT License](LICENSE).

28
LICENSE Normal file
View File

@ -0,0 +1,28 @@
MIT License
Copyright (c) 2026 Pocketdex contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
This license applies to the Pocketdex source code only. Pokémon and all
related names, imagery and data are the property of Nintendo, Game Freak and
The Pokémon Company, and are served at runtime via PokéAPI
(https://pokeapi.co/). No such assets are included in this repository.

387
README.md
View File

@ -1,157 +1,280 @@
# Pokédex PWA
# Pocketdex
A responsive, offline-capable Pokédex reference built on
[PokéAPI](https://pokeapi.co/). Vanilla JS + Vite + Workbox — no UI
framework.
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.
## Two headline features
- **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.
| 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`. |
| **Per-game Pokémon tracking** | Seen / caught are stored **per game** (version-group key) — catching Pikachu in Blue says nothing about your Gold save. `favorite` and `note` are global (they describe the Pokémon, keyed by National Dex id). The `all` bucket ("All games" mode) reads as the union — "caught anywhere" — and is where a pre-per-game save or an older backup migrates to. `#/progress` shows each game's own tally (National = the union), leading with the games you've played and tucking the rest behind a toggle. The active Pokémon is also a single deep-linkable route (`#/pokemon/25`). |
> **Not affiliated with Nintendo, Game Freak or The Pokémon Company.** This
> is a non-commercial fan reference tool. See [Legal](#legal).
## 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 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.
## Quick start
Export / import of all local state (settings, tracking, team, form
tracking, shiny hunts) as JSON lives in **Settings**, which also imports
the Pokédex straight out of a **game save file** (`.sav` / `.srm` /
`.dsv`): Gen 1 (R/B/Y), Gen 2 (G/S/C), Gen 3 (R/S/E, FR/LG), Gen 4
(D/P/Pt, HG/SS) and Gen 5 (B/W, B2/W2). It reads the game's own
seen/owned bitfields — checksum-picking Gold/Silver vs Crystal, Gen 3's
newer slot and rotated sections, Gen 4/5's active NDS slot (DeSmuME
footer stripped) and OR-ing their per-form "seen" copies — then a
dialog offers to merge or replace.
## Scripts
Requires **Node 20+**.
```bash
git clone https://github.com/OWNER/pocketdex.git
cd pocketdex
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)
npm run snapshot # fetch the data snapshot from PokéAPI (one time)
npm run dev # http://localhost:5173
```
Deploying under a sub-path (e.g. GitHub Pages project site): `BASE_PATH=/repo-name/ npm run build`.
| 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`. |
## Docker
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.
Multi-stage build (Node → nginx) that serves the static `dist/`:
---
## 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
docker compose up --build # → http://localhost:8080
# or without compose:
docker build -t pokedex-pwa .
docker run --rm -p 8080:80 pokedex-pwa
BASE_PATH=/pocketdex/ npm run build
```
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.
**Docker** (multi-stage Node → nginx):
## 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
```bash
docker compose up --build # → http://localhost:8080
# or
docker build -t pocketdex .
docker run --rm -p 8080:80 pocketdex
```
## Status
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.
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. When a specific game is selected,
cards show a version-exclusive badge ("Ruby only") — derived from the
snapshot's baked wild-encounter data, so gift/trade-only exclusives are
missed.
- **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 colors, 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); colored 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-color 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 (the same table is also a standalone
`#/compare` view with its own picker — up to 4 Pokémon, persisted).
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
color, 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. Installable: PNG +
maskable icons, `beforeinstallprompt` captured for an in-app "Install"
offer (toast + Settings button), manifest shortcuts to Team / Search /
Type chart.
## Contributing
Not yet done: an interactive region map (PokéAPI has no map imagery or
coordinates), and a formal Lighthouse pass.
Issues and pull requests are welcome — see
**[CONTRIBUTING.md](CONTRIBUTING.md)** for setup, project conventions, and
what's in and out of scope.
## Notes
Known gaps: no interactive region map (PokéAPI has no map imagery or
coordinates), no automated test suite yet, and no formal Lighthouse pass.
- `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.
---
## 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.

View File

@ -4,7 +4,7 @@ services:
context: .
args:
BASE_PATH: /
image: pokedex-pwa
image: pocketdex
ports:
- "8080:80"
restart: unless-stopped

View File

@ -9,18 +9,18 @@
<meta name="theme-color" content="#b3161a" />
<meta
name="description"
content="A responsive, offline-capable Pokédex reference powered by PokéAPI."
content="Pocketdex — an offline-first Pokédex, team builder and save-file tracker powered by PokéAPI."
/>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Podex" />
<title>Podex</title>
<meta name="apple-mobile-web-app-title" content="Pocketdex" />
<title>Pocketdex</title>
</head>
<body>
<div id="app" class="app">
<noscript>This Pokédex needs JavaScript enabled.</noscript>
<noscript>Pocketdex needs JavaScript enabled.</noscript>
</div>
<script type="module" src="/src/main.js"></script>
</body>

View File

@ -1,9 +1,9 @@
{
"name": "pokedex-pwa",
"name": "pocketdex",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "A responsive, offline-capable Pokédex reference PWA powered by PokéAPI.",
"description": "An offline-first Pokédex, team builder and save-file tracker powered by PokéAPI. Vanilla JS + Vite + Workbox.",
"scripts": {
"snapshot": "node scripts/build-snapshot.mjs",
"prebuild": "node scripts/build-snapshot.mjs",
@ -11,6 +11,9 @@
"build": "vite build",
"preview": "vite preview"
},
"engines": {
"node": ">=20"
},
"license": "MIT",
"devDependencies": {
"vite": "^5.4.21",

View File

@ -31,7 +31,7 @@ export function Nav() {
? el('button', { class: 'nav__link', type: 'button', onclick: () => item.action() }, ...inner)
: el('a', { class: 'nav__link', href: item.href }, ...inner);
});
nav.replaceChildren(el('span', { class: 'nav__brand' }, 'Podex'), ...links);
nav.replaceChildren(el('span', { class: 'nav__brand' }, 'Pocketdex'), ...links);
nav._items = items;
sync();
}

View File

@ -68,7 +68,7 @@ let installOffered = false;
onInstallChange((can) => {
if (!can || installOffered) return;
installOffered = true;
showToast('Install this Pokédex for offline, full-screen use?', 'Install', () => {
showToast('Install Pocketdex for offline, full-screen use?', 'Install', () => {
promptInstall();
});
});

View File

@ -27,10 +27,10 @@ export default defineConfig({
'apple-touch-icon.png',
],
manifest: {
name: 'Podex',
short_name: 'Podex',
name: 'Pocketdex',
short_name: 'Pocketdex',
description:
'A responsive, offline-capable Pokédex reference powered by PokéAPI.',
'An offline-first Pokédex, team builder and save-file tracker powered by PokéAPI.',
start_url: './',
scope: './',
display: 'standalone',