From 262e1a454e51aeaa36515db26dfd2ac81d189810 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 9 Sep 2026 17:50:54 -0400 Subject: [PATCH] Align ability page with move/item; link Team at-a-glance names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AbilityDetail: "Pokémon with this ability" grid now gets a filter box and the same game/generation narrowing as the move and item pages, plus a note when the ability postdates the selected game (Libero in Ruby/Sapphire -> "introduced in Gen 8"). - TeamView "At a glance": Fastest / Bulkiest names now link to their Pokémon pages. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu --- README.md | 8 +++-- src/views/AbilityDetail.js | 70 +++++++++++++++++++++++++++++++------- src/views/TeamView.js | 18 +++++++--- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index df40aff..04b57c7 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,11 @@ Working app with a type-themed UI: / "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 page lists every Pokémon that can - learn it (filterable, narrowed to species that existed by the selected - game's generation). Query, scroll, tab and filters persist. + 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 diff --git a/src/views/AbilityDetail.js b/src/views/AbilityDetail.js index 2204f9c..d6b9995 100644 --- a/src/views/AbilityDetail.js +++ b/src/views/AbilityDetail.js @@ -1,6 +1,7 @@ import { el, clear } from '../lib/dom.js'; import { loadSnapshot } from '../data/snapshot.js'; import { settings } from '../store/settings.js'; +import { prettify } from '../data/pokedex-resolver.js'; import { Sprite } from '../components/Sprite.js'; export async function AbilityDetail(id) { @@ -21,9 +22,51 @@ export async function AbilityDetail(id) { } const style = settings.get().spriteStyle; - const mons = a.pokemon.map((pid) => snap.speciesById.get(pid)).filter(Boolean); + const vg = settings.get().versionGroup; + const gen = snap.versionGroupByKey.get(vg)?.generation ?? 9; + + // All-games: every species with the ability. A specific game: only those + // that existed by its generation. + let mons = a.pokemon.map((pid) => snap.speciesById.get(pid)).filter(Boolean); + if (vg !== 'all') mons = mons.filter((sp) => sp.generation <= gen); mons.sort((x, y) => x.id - y.id); + const CAP = 400; + const grid = el('div', { class: 'ab-mons' }); + function paint(q) { + const qq = (q || '').trim().toLowerCase().replace(/\s+/g, '-'); + const shown = qq ? mons.filter((sp) => sp.name.includes(qq)) : mons; + clear(grid); + if (!shown.length) { + grid.append(el('p', { class: 'detail__muted' }, 'No matches.')); + return; + } + for (const sp of shown.slice(0, CAP)) { + grid.append( + el( + 'a', + { class: 'ab-mon', href: `#/pokemon/${sp.id}` }, + Sprite(sp.id, { style, alt: sp.name, size: 48 }), + el('span', {}, sp.name.replace(/-/g, ' ')), + ), + ); + } + if (shown.length > CAP) { + grid.append( + el('p', { class: 'detail__muted' }, `+${shown.length - CAP} more — filter to narrow.`), + ); + } + } + paint(''); + const filterInput = el('input', { + type: 'search', + class: 'search__input learners__filter', + placeholder: 'Filter Pokémon…', + oninput: (e) => paint(e.target.value), + }); + + const predatesGame = vg !== 'all' && a.generation > gen; + clear(view).append( el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/search' }, '‹ Lookup')), el( @@ -45,20 +88,21 @@ export async function AbilityDetail(id) { el( 'section', { class: 'detail__section' }, - el('h2', {}, `Pokémon with this ability (${mons.length})`), - mons.length + el( + 'h2', + {}, + `Pokémon with this ability${vg === 'all' ? '' : ` in ${prettify(vg)}`} (${mons.length})`, + ), + predatesGame ? el( - 'div', - { class: 'ab-mons' }, - ...mons.map((sp) => - el( - 'a', - { class: 'ab-mon', href: `#/pokemon/${sp.id}` }, - Sprite(sp.id, { style, alt: sp.name, size: 48 }), - el('span', {}, sp.name.replace(/-/g, ' ')), - ), - ), + 'p', + { class: 'detail__muted' }, + `Not in ${prettify(vg)} — this ability was introduced in Gen ${a.generation}.`, ) + : null, + mons.length > 24 ? filterInput : null, + mons.length + ? grid : el('p', { class: 'detail__muted' }, 'None on record.'), ), ); diff --git a/src/views/TeamView.js b/src/views/TeamView.js index 9342e17..1ce5415 100644 --- a/src/views/TeamView.js +++ b/src/views/TeamView.js @@ -309,17 +309,27 @@ export async function TeamView() { 'dl', { class: 'pfacts' }, fact('Average BST', String(avgBst)), - fact('Fastest', `${prettify(fastest.name)} (${fastest.stats?.spe ?? '?'} Spe)`), + fact('Fastest', monLink(fastest, `${fastest.stats?.spe ?? '?'} Spe`)), fact( 'Bulkiest', - `${prettify(bulkiest.name)} (${ - (bulkiest.stats?.hp || 0) + (bulkiest.stats?.def || 0) + (bulkiest.stats?.spd || 0) - } HP+Def+SpD)`, + monLink( + bulkiest, + `${(bulkiest.stats?.hp || 0) + (bulkiest.stats?.def || 0) + (bulkiest.stats?.spd || 0)} HP+Def+SpD`, + ), ), ), ); } + function monLink(sp, tail) { + return el( + 'span', + {}, + el('a', { class: 'link', href: `#/pokemon/${sp.id}` }, prettify(sp.name)), + ` (${tail})`, + ); + } + function compare(mem) { const vg = snap.versionGroupByKey.get(settings.get().versionGroup); const gen = vg ? vg.generation : 9;