Align ability page with move/item; link Team at-a-glance names

- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
chris 2026-09-09 17:50:54 -04:00
parent 9810eb0996
commit 262e1a454e
3 changed files with 76 additions and 20 deletions

View File

@ -102,9 +102,11 @@ Working app with a type-themed UI:
/ "TMs only" / "HMs only" for the selected game and sort by power / / "TMs only" / "HMs only" for the selected game and sort by power /
accuracy / recency — or, with a machine filter on, by TM/HM number accuracy / recency — or, with a machine filter on, by TM/HM number
(numbers are baked into the snapshot). Items filter by category. Each (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 has its own detail page; the move, ability and item pages list the
learn it (filterable, narrowed to species that existed by the selected Pokémon connected to them (learners / users / wild holders) as a
game's generation). Query, scroll, tab and filters persist. 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" - **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 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 resists), then a full matchup grid with a diverging resist◀│▶weak bar per

View File

@ -1,6 +1,7 @@
import { el, clear } from '../lib/dom.js'; import { el, clear } from '../lib/dom.js';
import { loadSnapshot } from '../data/snapshot.js'; import { loadSnapshot } from '../data/snapshot.js';
import { settings } from '../store/settings.js'; import { settings } from '../store/settings.js';
import { prettify } from '../data/pokedex-resolver.js';
import { Sprite } from '../components/Sprite.js'; import { Sprite } from '../components/Sprite.js';
export async function AbilityDetail(id) { export async function AbilityDetail(id) {
@ -21,9 +22,51 @@ export async function AbilityDetail(id) {
} }
const style = settings.get().spriteStyle; 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); 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( clear(view).append(
el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/search' }, ' Lookup')), el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/search' }, ' Lookup')),
el( el(
@ -45,20 +88,21 @@ export async function AbilityDetail(id) {
el( el(
'section', 'section',
{ class: 'detail__section' }, { class: 'detail__section' },
el('h2', {}, `Pokémon with this ability (${mons.length})`), el(
mons.length 'h2',
{},
`Pokémon with this ability${vg === 'all' ? '' : ` in ${prettify(vg)}`} (${mons.length})`,
),
predatesGame
? el( ? el(
'div', 'p',
{ class: 'ab-mons' }, { class: 'detail__muted' },
...mons.map((sp) => `Not in ${prettify(vg)} — this ability was introduced in Gen ${a.generation}.`,
el(
'a',
{ class: 'ab-mon', href: `#/pokemon/${sp.id}` },
Sprite(sp.id, { style, alt: sp.name, size: 48 }),
el('span', {}, sp.name.replace(/-/g, ' ')),
),
),
) )
: null,
mons.length > 24 ? filterInput : null,
mons.length
? grid
: el('p', { class: 'detail__muted' }, 'None on record.'), : el('p', { class: 'detail__muted' }, 'None on record.'),
), ),
); );

View File

@ -309,17 +309,27 @@ export async function TeamView() {
'dl', 'dl',
{ class: 'pfacts' }, { class: 'pfacts' },
fact('Average BST', String(avgBst)), fact('Average BST', String(avgBst)),
fact('Fastest', `${prettify(fastest.name)} (${fastest.stats?.spe ?? '?'} Spe)`), fact('Fastest', monLink(fastest, `${fastest.stats?.spe ?? '?'} Spe`)),
fact( fact(
'Bulkiest', 'Bulkiest',
`${prettify(bulkiest.name)} (${ monLink(
(bulkiest.stats?.hp || 0) + (bulkiest.stats?.def || 0) + (bulkiest.stats?.spd || 0) bulkiest,
} HP+Def+SpD)`, `${(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) { function compare(mem) {
const vg = snap.versionGroupByKey.get(settings.get().versionGroup); const vg = snap.versionGroupByKey.get(settings.get().versionGroup);
const gen = vg ? vg.generation : 9; const gen = vg ? vg.generation : 9;