25 Commits

Author SHA1 Message Date
840031c48a Import: ask merge vs replace; make the feed progress ring a link
- Save import now opens a small choice dialog (new lib/dialog.js) instead
  of a confirm(): "Add to my Pokédex" (merge, keeps everything) or
  "Replace mine with this". Replace resets seen/caught to exactly the
  save but keeps favourites and notes (new selection.replaceFlags). The
  dialog also explains that caught state is shared across games, so an
  imported save only ever adds species that game has and they count
  toward the National Dex too — no per-game caught flag, by design.
- The dex-feed progress ring is now an <a href="#/progress"> with a
  hover/press nudge — tapping it opens the per-game breakdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 09:52:26 -04:00
17e8e30f9b Import the Pokédex from a Gen 3 GBA save (.sav / .srm)
lib/savedex.js parses a Generation III save (Ruby/Sapphire/Emerald/
FireRed/LeafGreen): picks the newer of the two save slots, resolves the
rotated section order via each section's footer, and reads the seen /
owned bitfields out of `struct Pokedex` in section 0 (bit dexNo-1, 386
species). Also decodes the trainer name and detects the national-dex
magic byte. Soft checksum check.

Settings > Your data: "Import from a GBA save" — file picker → confirm →
selection.importFlags() merges seen/caught in (never removes). Verified
against synthetic saves (slot selection, rotation, FRLG vs RSE, bit
decode) and end-to-end through the Settings UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 09:29:34 -04:00
7f12f08718 Forms in the team
Team slots are now { id, form } instead of bare ids (old saves migrate
on read via slot()). The detail page's team button adds whatever
mechanical form is selected — "+ Mega X" / "✓ Mega X in team" — and
TeamView merges that form's baked snapshot types/stats/BST into the
member so Coverage, Compare and the Calc all analyse the form (Mega
Charizard X shows as Fire/Dragon, BST 634). Lineup shows the form name
and sprite; remove targets the exact slot. The Add picker still adds
base forms — Megas etc. come from their own page.

CompareTable takes an optional spriteId; addToTeam/removeFromTeam/inTeam
gain a form argument.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-09 19:22:32 -04:00
b15ee3dd7f Shiny hunt tracker (#/shiny)
New store + view (linked from Settings > Your data): one card per active
hunt with a big +1 counter, the method's odds, and the running cumulative
chance (1 - (1-p)^count). Nine methods with shiny-charm variants
(Full odds, Masuda, SOS, Radar, DexNav, Catch Combo, Dynamax Adventure,
Mass/Massive Outbreak). "Found it" marks the Pokémon caught and clears
the hunt; per-hunt notes field.

Also: JSON export/import now covers team, formTracking and shinyHunts
alongside settings/selection; openPokemonPicker gains a `closeAfterPick`
option (used by the shiny + breeding single-pick flows).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-09 19:06:46 -04:00
48cb471489 Breeding helper (#/breeding)
Two modes, linked from the Team header:
- Egg groups — browse every species in each of the 14 breedable egg
  groups (offline, from the snapshot's eggGroups).
- Can breed with — pick a Pokémon and get its compatible partners
  (shared egg group; Ditto pairs with everything; genderless pairs only
  with Ditto; Undiscovered can't breed), plus its family's egg moves
  (walks evolves-from to the base stage — Pikachu shows Pichu's), each
  linking to the move page (which already lists who can learn it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-09 19:01:24 -04:00
5f6414528e Compare any two Pokémon — standalone #/compare view
Extracted the Team Compare table into components/CompareTable.js and
reused it for a new #/compare route: its own picker (up to 4 Pokémon,
persisted in ui.compareIds), gen-aware types, per-stat max highlight.
Linked from the Team header alongside Natures / Type chart. TeamView's
Compare tab now just calls CompareTable too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-09 18:48:27 -04:00
fa302af7f1 Search moves: sort by TM/HM number; add an "HMs only" filter
TM/HM numbers are now baked into the snapshot (build-snapshot resolves
every machine id -> "TM25" etc. in a post-pass, ~2400 lookups), so the
Search moves list no longer lazily fetches /machine per row — it shows
numbers synchronously and can sort by them.

- New "TM / HM number" sort option, shown only while a machine filter is
  on, and dropped back to A–Z when neither is.
- "TMs only" now means strictly TMs; added a parallel "HMs only". Either
  or both. In "All games" mode a move can be a TM in one game and an HM
  in another, so machineFor picks the newest entry of a requested kind
  rather than just the newest entry (fixes "HMs only" showing 0).

Removes the getMachine/mapLimit lazy-resolution path (and its now-unused
stale-token guard) from SearchView.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-08 10:44:14 -04:00
4b9bc5c0ab Settings: text size, reduce motion, landing screen, haptics
Four new preferences, all in settings.js / applied via applyTheme() so
boot and live changes go through one path:

- fontScale (small/default/large): scales root font-size (92/100/115%);
  the whole stylesheet is already rem-based so this alone rescales
  everything.
- reduceMotion: an explicit in-app override alongside the existing
  `prefers-reduced-motion` OS check, via a new
  :root[data-reduce-motion="true"] rule in tokens.css mirroring the
  existing media-query kill-switch. New prefersReducedMotion() helper
  (checks both) replaces the three hand-rolled matchMedia checks in
  router.js/Card.js/PokemonDetail.js.
- defaultRoute (dex/team/search): redirects on a genuinely hash-less
  launch only (bookmark, home-screen icon, bare URL) — explicit nav
  (e.g. tapping "Dex") is never touched, since it's applied once in
  main.js before initRouter runs.
- haptics: a short navigator.vibrate() pulse on the same catch/seen/
  favorite toggles that already play the pop animation, via a new
  lib/haptics.js buzz() (no-ops without Vibration API support, e.g. iOS
  Safari).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-04 12:35:26 -04:00
c9807127d6 Dex grid: ability, egg group, min BST and fully-evolved filters
Extends the existing filter drawer (type/gen) with four more:

- Ability — snapshot abilities already carry a reverse pokemon[] index,
  so this is a plain lookup, no new data needed.
- Egg group — options derived from the species list's own eggGroups.
- Min BST — a number field; hard filter, distinct from sorting by BST.
- Fully evolved only — new species.evolvesFromId field (from
  pokemon-species' evolves_from_species, already fetched during the
  snapshot build, so no extra requests). "Fully evolved" is computed
  client-side as "no other species points here via evolvesFromId" —
  correctly handles branching lines (Eevee's evolutions all count as
  fully evolved; Eevee itself doesn't).

All four persist via the ui store and count toward the filter badge, same
as the existing filters.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-04 12:14:36 -04:00
1ed8fcb709 Add a per-species form dex (Unown letters, Vivillon patterns, ...)
New tab on the detail page — "<Species> Dex" — for any species with more
than one cosmetic form: a checklist grid (sprite + label + tap-to-toggle)
tracking which appearances you've caught, separate from the species' own
caught flag. Persisted in a new pdx.formTracking store keyed by form slug.

Only offered when the species is actually in the currently selected
game's dex (reuses the page's existing prev/next row lookup), so Unown
gets its dex in HeartGold/SoulSilver but not in Sword/Shield, where it
was never released.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-04 11:35:03 -04:00
415cf3edec Abilities lookup + detail; Natures reference
- Snapshot bakes an abilities index (373: name, generation, effect, and the
  species that have it). Snapshot ~872 KB / 133 KB gzip.
- Search gains an 'Abilities' tab — browse all 373 offline, filter/search by
  name or effect, sort A–Z / most-Pokemon / newest
- #/ability/:id detail: effect + a grid of every Pokemon with the ability
  (all from the snapshot, no fetch)
- Pokemon detail: the Abilities line is now tappable, linking to each ability
- #/natures: the full 25-nature +10%/-10% table; linked from the Team view

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:00:14 -04:00
79a5efaf11 Browsable moves & items lists (offline)
- Snapshot now bakes a moves index (937: type, damage class, power,
  accuracy, pp, priority, generation, machine ids per version group) and an
  items index (2223: id, name, category — resolved via the 54 item
  categories). ~806 KB / 117 KB gzip.
- Search Moves/Items tabs show the FULL list when the query is empty:
  - Moves: filter by type / damage class / 'TMs only' (for the selected
    game), sort by A–Z / power / accuracy / newest; TM numbers resolve
    lazily via /machine. Rich rows (type + class + power) with no fetch.
  - Items: filter by category, sort A–Z / dex order; sprite + category tag.
  - Result count shown; capped at 400 with a 'refine to see more' note.
- Both tabs are now fully offline (dropped the online name-index calls).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:42:03 -04:00
ddc84c2a48 Team Builder + Compare views
- New #/team view: a persisted lineup of up to 6 Pokémon (pdx.team store)
  with a search-picker overlay, plus a Coverage/Compare toggle
  - Coverage: 18x6 weakness table (colour-coded multipliers + per-type weak
    count), offensive STAB gaps, team at-a-glance (avg BST, fastest, bulkiest)
  - Compare: per-member column table of types + 6 stats + BST with mini bars
    and row-max highlighting; scrolls horizontally past 4 members
- '+ Team' / '✓ In team' toggle on the Pokémon detail page (disabled when full)
- Nav gains a Team item; a Settings toggle ('Show Team in the navigation
  bar', on by default) hides it from the bar without removing the route
- type-chart.js: defenseVector() helper

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:33:52 -04:00
2e4572428a Dense-data pass: training/breeding facts, STAB coverage, filters, cries
- Detail About tab: EV yield, base EXP, catch rate + ~%, base friendship,
  growth rate + total EXP, held items; Breeding block with a gender-ratio
  bar, egg groups, egg cycles/steps
- Stats tab: 'STAB coverage (attacking)' — types the Pokemon's STAB hits
  hard / is walled by (data/type-chart.js offensiveSummary)
- Feed: Type and Generation filter dropdowns + a Random button; cards now
  resolve typings for the selected game's generation via snapshot pastTypes
- Recently-viewed strip on the feed (ui.recent, capped at 12)
- Play-cry button on the detail hero; service worker caches PokeAPI cries

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:06:36 -04:00
491d1102cc Center card sprite; ghost number to top:2px; scroll-position restore
- Card sprite sits centred in the upper area (flex:1 + place-items:center)
  instead of a forced margin; sprite-style / boxed sizing moved onto the
  img so it stays centred
- .card__ghost number nudged to top: 2px
- Feed Back-navigation restores the raw scroll offset again (dropped the
  card-anchor approach); keeps manual scrollRestoration + retry-on-reflow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 08:25:23 -04:00
23272b99a7 Land Back on the opened card (clear of header/nav); card spacing
- Feed remembers the opened card (ui.feedAnchor) and, on Back, scrolls that
  exact card just below the sticky header via scrollIntoView + a computed
  scroll-margin-top; re-corrects on webfont load and as off-screen cards
  render their real height, but yields once the user scrolls
- history.scrollRestoration = 'manual' so the browser doesn't fight it;
  per-session scroll memory cleared on cold launch
- Cards: ghost number nudged off the top edge; sprite dropped 10px (flex
  column grows the card, so nothing overlaps); intrinsic size bumped to match

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 17:49:23 -04:00
b0e3655dbf Restore feed scroll position on Back; enlarge the detail back button
- The dex feed remembers scrollY on teardown (ui.feedScroll) and restores
  it on re-mount, so viewing a Pokémon and going back lands where you were.
  Reset to top when the filter, sort, direction or game changes.
- Router no longer force-scrolls after mount; it scrolls to top on the
  loading placeholder and each view manages its own position (detail pages
  land at top, feed and search restore).
- .phero__back is now a rounded pill with a real 40px tap target.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 17:40:13 -04:00
0d95f9c93a TM/HM numbers; default to the National Dex
- Learnset 'By TM / HM' rows show the machine number for the selected game
  (TM25 / HM05 / TR10), resolved from /machine when the group is expanded
- Move detail page gains a 'TM / HM' fact (marked '(latest)' when the move
  isn't a machine in the currently-selected game)
- machineNumber() exported from MovesList and reused by MoveDetail
- Fresh installs now open on the National Dex (versionGroup 'all') instead
  of Scarlet/Violet

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:56:53 -04:00
0f00b50198 Move & item lookup: tabbed Search + detail pages
- Search becomes a tabbed lookup: Pokémon (offline snapshot) / Moves /
  Items. Moves + Items lazy-load a name index from PokéAPI (SW-cached) and
  filter client-side; short move result sets get type/class/power chips
- MoveDetail (#/move/:id): type-themed header, power/accuracy/PP/priority,
  effect text with %-substitution, target, ailment/crit/drain/stat-change
  summary, learned-by count
- ItemDetail (#/item/:id): item sprite, category, cost, fling power,
  attributes, held-by count, effect text
- Loose name matching (hyphens vs spaces); run-token guards stale async
  renders when the query or tab changes mid-fetch

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:51:51 -04:00
61ceb6abf3 Declutter feed header; add theme + accent options
- Feed header: the game pill is gone (the nav Games button opens the sheet);
  the active game is now a quiet text label under the title
- Settings > Theme: add Black (OLED) and Sepia alongside System/Light/Dark
- Settings > Accent colour: six swatches (red default, blue, green, amber,
  violet, rose) layered over any theme via [data-accent]; persisted
- applyTheme() takes the settings object; system theme now keys off the
  absence of [data-theme] so explicit light/sepia don't fight the media query

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:24:48 -04:00
fa6df9d60b Sorting + richer snapshot; Legendary filter; shiny toggle to hero top
- Snapshot now carries per-species base stats + BST, height, weight, base
  EXP, capture rate, growth rate, colour, egg groups, gender rate,
  legendary/mythical/baby flags, and past typings (~72 KB gzip)
- Dex feed: Sort control (dex / name / BST / each stat / height / weight /
  base EXP / catch rate / recently caught) with a direction toggle;
  sort + filter persisted in the ui store; cards show the sorted metric
- New 'Legendary' filter chip (legendary or mythical) with a live count
- Detail hero: shiny toggle moved up beside the favourite, top of the hero

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:20:19 -04:00
01269fd35a All-games mode, full flavour list, persistent search
- FlavorText: 'All Pokédex entries' disclosure listing every distinct
  English entry across all games (newest first, labelled by game) — not
  just the selected game's one line
- Search remembers its query and scroll position in the ui store, so Back
  or re-opening the tab restores the results; header explains the scope
  (matches all species; opens for the selected game / all games)
- GameSheet 'All games' option: National Dex, newest data, no generation
  limits — abilities/shiny ungated, types untrimmed, evolution unpruned;
  Moves/Locations prompt to pick a game

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:06:56 -04:00
5144f760f5 Games as an overlay sheet; remember the detail tab
- New GameSheet: bottom drop-up overlay (backdrop / grab handle / Esc to
  dismiss) summoned from the nav 'Games' button or the feed's game pill;
  picking a game/sub-dex writes settings and the feed updates in place
- Removed the #/games route and GamePicker view
- Detail page remembers the last-opened tab in a new pdx.ui store and
  restores it on return; selectTab guards unknown ids

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:37:16 -04:00
b4f2460341 Apply type-themed styling to grid, games, search
- Cards: primary-type gradient tint, radial spotlight behind the sprite,
  ghost dex number, official artwork by default, spring hover lift
- Game picker cards + search rows: rounder, soft shadows, hover motion
- Progress bar gradient; pill-shaped search inputs
- Shared typeHex() helper (src/lib/type-color.js), reused by detail + cards

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:04:21 -04:00
cd2655011f Scaffold Pokedex PWA (Vite + Workbox)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 10:54:36 -04:00