31 Commits

Author SHA1 Message Date
31cc076c0c 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
2026-09-10 11:03:23 -04:00
d275117569 Simplify the import dialog copy; spell it "color"
- The save-import dialog body is now just "N seen · N caught in this
  save." — the button labels carry the merge/replace meaning.
- "colour" → "color" throughout (the visible "Accent color" field, plus
  comments and README for consistency).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 10:01:54 -04:00
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
5bce0f93f3 Fix swipe hijacking h-scroll; drag bottom sheets down to dismiss
- onSwipe: before claiming a horizontal page-swipe, walk up from the
  touch target for an overflow-x auto/scroll ancestor that can still
  scroll that way (tab strip, moves list, coverage grid) and bail if
  found — sliding those no longer flips to the next Pokémon.
- segThumb: retry placement until the seg is laid out, and observe it for
  resizes, so the sliding pill is positioned on first paint instead of
  only after the first click. Dropped the :has() selector for a
  JS-applied .seg--thumbed class.
- New lib/sheet-drag.js: drag the game sheet / Pokémon picker down by its
  header to dismiss (past ~110px or a flick), else it springs back.
  Header gets touch-action:none + a grab cursor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 09:39:16 -04:00
a437f539fc Animation pass: shared-element morph, sliding tabs, count-ups
New lib/anim.js (countUp, fadeSlideIn, segThumb), all reduced-motion
aware:

- Shared-element morph: tapping a card tags its art with
  view-transition-name; the detail skeleton now renders the real sprite
  at hero size (router passes the match, animates the skeleton mount), so
  the sprite flies from the grid into the hero and the skeleton→content
  swap is seamless.
- Segmented controls (Team / Search / Breeding) get a sliding pill that
  measures each button and eases between them.
- Count-ups: the dex-feed progress number and the detail Stats total
  tick up.
- Detail tab content fades/rises on switch.
- Global :active scale-press on buttons/chips/rows, plus soft
  background/border transitions.
- Hero sprite has a slow idle bob.
- Router swallows the AbortError when a fast follow-up nav skips an
  in-flight view transition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 09:22:08 -04:00
2e4d9ab329 Version-exclusive badges on dex cards
build-snapshot: a new pass fetches /pokemon/:id/encounters for every
species and records, per game, the versions it has wild encounters in —
when that's a non-empty strict subset of the game's versions,
species.exclusiveIn[vg] holds them. Wild-only, so gift/trade/evolution
exclusives are missed.

Card: with a specific game selected, shows a "<Version> only" pill
(e.g. Seedot -> "Ruby only", Lotad -> "Sapphire only"). 220 species
flagged across all games; nothing shown in National mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-09 19:26:56 -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
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
d73438dd29 MovesList: order the TM/HM group by TM number
The learnset's "By TM / HM" group was alphabetical. TM numbers aren't in
the snapshot — they resolve lazily per move via /machine/{id}, which the
group's enrichers already fetch on expand. So: await those resolutions
(fillTm is now async and stashes the label on the move), and once the
whole group is enriched, re-append the rows in TM/HM/TR-then-number order
— TM01, TM02 … HM01 …, matching the in-game list. Verified against
Charizard in FireRed/LeafGreen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-08 10:28:27 -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
1711249dae Add a notes UI for the per-Pokémon note field
The selection store already had a note field and setNote() (from the
JSON export/import work) but nothing in the UI ever wrote to it. Adds:

- PokemonDetail: an auto-resizing textarea under the Seen/Caught/Team
  row. Saves debounced (500ms) as you type, immediately on blur, and
  flushes any unsaved keystrokes on teardown so navigating away right
  after typing never loses them. A brief "Saved" flash confirms the
  write.
- Card: a small 📝 mark on any card whose Pokémon has a note.
- DexGrid: a "Notes" filter chip (same pattern as Caught/Favorites) to
  browse everything you've annotated.

Notes already ride along in the existing settings/selection JSON
export-import, so no changes needed there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-04 12:24:22 -04:00
bdc21fc4e6 Team builder: add a damage calculator
New "Calc" mode alongside Coverage/Compare. Pick (or quick-pick from your
current team) an attacker, a move, and a defender; get a damage range, %
of the defender's HP, hits-to-KO, and STAB/effectiveness badges.

- src/lib/damage-calc.js: the standard Gen 3+ stat and damage formulas
  (statAt, calcDamage), evaluated across the 85–100% roll.
- Move data is era-accurate: picking a move fetches it live for
  past_values (power/type/class changes), reusing MovesList's
  forGeneration (now exported) via a snapshot-shaped shim so the instant
  preview and the refined post-fetch result use the same code path.
- Level/nature/EV inputs per side (defaults: level 100, neutral nature,
  0 EV, 31 IV assumed) plus a crit toggle.
- Explicitly out of scope and called out in the UI: items, abilities,
  weather, terrain — this is a base-stats estimate, not a battle-exact
  number.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-04 11:58:51 -04:00
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
344e641475 Cosmetic forms: compact Appearance picker
Pull sprite-only variants into the snapshot — Unown letters, Vivillon
patterns, Furfrou trims, seasonal Deerling/Sawsbuck, Flabébé-line colours,
Alcremie decorations, Pikachu caps (278 across 50 species). They share the
base Pokémon's typing/stats/abilities/learnset, so they carry only
{ slug, id, name, sprite } and never trigger a /pokemon fetch.

build-snapshot.mjs: cosmetic varieties (previously dropped) and the
/pokemon-form entries for species with >1 form now land in
species.cosmeticForms.

PokemonDetail: a single <select> "Appearance" dropdown, separate from the
mechanical form pills, that swaps only the hero artwork + label. Picking a
cosmetic look while a mechanical form is active reverts to base first.
Card "+N forms" badge counts cosmetic variants too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-08-28 11:59:20 -04:00
40667cbdb6 Forms & variants
- Snapshot: each species gains a forms[] list (slug, form id, label,
  category, types, stats, BST, height, weight) built from its non-default
  varieties — 264 forms across 203 species (Megas, G-max, regional,
  alt formes; cosmetic-only forms filtered out). Snapshot ~914 KB.
- Detail page: a form switcher (Base + each form). Selecting a form
  re-fetches its /pokemon and rebuilds the type-dependent parts —
  hero name/types/artwork, --type theming, stat bars + total, defensive
  matchups, STAB coverage, abilities, EV yield, held items, and learnset.
  Species-level data (flavour, breeding, evolution) stays put.
- Grid cards show a '+N forms' badge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:11:07 -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
8ebdb9d197 Vertical evolution tree; swipe to page through the dex
- Evolution chain is now a vertical tree (stage, then '↓ trigger' step,
  then the next stage) that indents by branch depth. Never overflows a
  phone; branching chains (Eevee, Wurmple) stay grouped and readable.
- Swipe left/right on the Pokémon detail page navigates prev/next in the
  current dex (src/lib/swipe.js). Claims horizontal drags via
  preventDefault so page scroll and the browser's edge back-gesture don't
  steal them; vertical drags pass through.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 08:49:15 -04:00
1d35f2d433 Motion pass: view transitions, skeletons, micro-interactions
- Router uses the View Transitions API to cross-fade placeholder -> content
  on navigation (falls back to a plain swap where unsupported or when the
  user prefers reduced motion)
- Shaped shimmer skeletons for the Pokémon / move / item detail pages
  (src/components/skeletons.js) so there's no layout shift on load
- Views fade+rise in; progress ring draws its arc from empty on mount;
  caught / seen / favourite toggles pop when switched on
- Offline indicator pill (online/offline events) + 'Back online' toast
- All new motion gated behind prefers-reduced-motion

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 08:36:41 -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
e61bf7ac6c Stylised game-cover tiles in the game picker
- Each game renders as a two-tone tile split in its version colours
  (Scarlet/Violet, Sword/Shield, Ruby/Sapphire…), keyed by version group
  with a per-generation fallback (src/data/game-colors.js)
- Clean flat split with a bottom scrim for legibility — no gloss/bubble

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:40:18 -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
0665d0f2ef Both-version flavour text; refresh detail page on game switch
- FlavorText: shows a version pill toggle (Sword/Shield, Scarlet/Violet…)
  when the two entries differ, sliding between them; collapses identical
  entries; falls back to the newest English entry, captioned
- Switching games while viewing a Pokémon now rebuilds the detail page
  (router.rerender wired from a settings subscription in main.js) so
  flavour text, learnset, matchups, evolution, abilities, locations and
  era sprites all follow the newly selected game

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:54:11 -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
404ca7dbbd Trim typings and matchups to the selected generation
- Detail page resolves a Pokemon's typing from PokeAPI past_types for the
  selected game's generation (Gen 1 Magnemite is pure Electric, pre-Gen 6
  Clefairy is Normal); feeds hero pills, theming and matchups
- Type chart excludes Steel/Dark (pre-Gen 2) and Fairy (pre-Gen 6) as
  attacking types entirely
- Gen 1-2 game-era sprites (opaque white bg) shown as a small framed tile
  on cards and the detail hero

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:27:02 -04:00
9216a53932 Modernise the dex feed; gate shiny to Gen 2+
- Feed header: large title, tappable game pill, circular progress ring,
  icon-in-field search, filter chips with live counts (All/Caught/Missing/Favorites)
- Cards: left-aligned info, number chip, corner ghost number (no longer hidden
  behind the sprite), top-down type gradient, staggered entrance animation;
  caught toggle moved to top-left
- Detail: hide the shiny toggle for Gen 1 games (shinies are Gen 2+);
  add Category (genus) to the About tab

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:16:52 -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
f1abe07128 Add moves, evolution, type matchups, locations; game-accurate data; fix desktop layout
- Detail page: base stats, type matchups (defending), evolution chain,
  learnset with per-move power/type/acc/PP/effect, wild locations
- Game-aware: abilities gated to Gen 3+ (hidden to Gen 5+), type chart
  applies Gen 1 / pre-Gen 6 rules, move stats use PokeAPI past_values,
  pre-Gen 4 physical/special-by-type, evolution tree re-parented to the
  selected generation
- Moves and evolution clickable/expandable; no horizontal scroll
- Fix: .view-host shrink-wrapped as a grid item on desktop (margin:0 auto)
- Era-accurate sprite option; Search tab relabelled as global lookup

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 11:35:30 -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