Move detail: list the Pokémon that can learn the move
The "Learned by N Pokémon" fact was a dead number. Now the move page
renders that list as a clickable sprite grid (links to each Pokémon),
with a filter box once it's more than ~24 long.
PokéAPI's learned_by_pokemon is flat across all games, so for a specific
game it's narrowed to species that existed by that generation — a
"Crystal" view of Surf shows 69 Gen 1–2 Pokémon, not the full 231. Not
perfectly game-exact (a mon that only gained the move in a later gen can
slip through) but far better than guess-and-check. National ("all") shows
the unfiltered list.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
parent
3193b167ac
commit
86e9bc60bc
@ -102,7 +102,9 @@ 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. Query, scroll, tab and filters persist.
|
||||
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.
|
||||
- **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
|
||||
|
||||
@ -2700,6 +2700,9 @@
|
||||
grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.learners__filter {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ab-mon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { el, clear } from '../lib/dom.js';
|
||||
import { getMove } from '../data/api.js';
|
||||
import { loadSnapshot } from '../data/snapshot.js';
|
||||
import { prettify } from '../data/pokedex-resolver.js';
|
||||
import { settings } from '../store/settings.js';
|
||||
import { machineNumber } from '../components/MovesList.js';
|
||||
import { Sprite } from '../components/Sprite.js';
|
||||
import { TypeChip } from '../components/TypeChip.js';
|
||||
import { typeHex } from '../lib/type-color.js';
|
||||
|
||||
const DMG = { physical: 'Physical', special: 'Special', status: 'Status' };
|
||||
const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop());
|
||||
|
||||
const english = (entries, key) => {
|
||||
const hit = (entries || []).find((e) => e.language.name === 'en');
|
||||
@ -21,6 +24,8 @@ export async function MoveDetail(id) {
|
||||
const view = el('section', { class: 'view lookup' });
|
||||
view.append(el('div', { class: 'view--loading' }, 'Loading move…'));
|
||||
|
||||
const snap = await loadSnapshot();
|
||||
|
||||
let d;
|
||||
try {
|
||||
d = await getMove(id);
|
||||
@ -64,6 +69,17 @@ export async function MoveDetail(id) {
|
||||
.join(', ');
|
||||
if (statChanges) bits.push(statChanges);
|
||||
|
||||
// Pokémon that can learn this move. PokéAPI only gives a flat, all-games
|
||||
// list — narrow it to species that existed by the selected game's
|
||||
// generation so a "Crystal" view isn't full of Gen 9 Pokémon.
|
||||
const vg = settings.get().versionGroup;
|
||||
const gen = snap.versionGroupByKey.get(vg)?.generation ?? 9;
|
||||
let learners = (d.learned_by_pokemon || [])
|
||||
.map((p) => snap.speciesById.get(idFromUrl(p.url)))
|
||||
.filter(Boolean);
|
||||
if (vg !== 'all') learners = learners.filter((sp) => sp.generation <= gen);
|
||||
learners.sort((a, b) => a.id - b.id);
|
||||
|
||||
const facts = el(
|
||||
'dl',
|
||||
{ class: 'pfacts' },
|
||||
@ -72,7 +88,7 @@ export async function MoveDetail(id) {
|
||||
fact('PP', d.pp ?? '—'),
|
||||
fact('Priority', d.priority ?? 0),
|
||||
fact('Introduced', prettify(d.generation.name)),
|
||||
fact('Learned by', `${d.learned_by_pokemon.length} Pokémon`),
|
||||
fact('Learned by', `${learners.length} Pokémon`),
|
||||
);
|
||||
if (d.machines?.length) {
|
||||
const vg = settings.get().versionGroup;
|
||||
@ -88,6 +104,56 @@ export async function MoveDetail(id) {
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Learners grid (filterable) ----------------------------------
|
||||
const style = settings.get().spriteStyle;
|
||||
const LEARNER_CAP = 400;
|
||||
const learnersGrid = el('div', { class: 'ab-mons' });
|
||||
function paintLearners(q) {
|
||||
const qq = (q || '').trim().toLowerCase().replace(/\s+/g, '-');
|
||||
const shown = qq ? learners.filter((sp) => sp.name.includes(qq)) : learners;
|
||||
clear(learnersGrid);
|
||||
if (!shown.length) {
|
||||
learnersGrid.append(el('p', { class: 'detail__muted' }, 'No matches.'));
|
||||
return;
|
||||
}
|
||||
for (const sp of shown.slice(0, LEARNER_CAP)) {
|
||||
learnersGrid.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 > LEARNER_CAP) {
|
||||
learnersGrid.append(
|
||||
el('p', { class: 'detail__muted' }, `+${shown.length - LEARNER_CAP} more — filter to narrow.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
paintLearners('');
|
||||
const learnerFilter = el('input', {
|
||||
type: 'search',
|
||||
class: 'search__input learners__filter',
|
||||
placeholder: 'Filter Pokémon…',
|
||||
oninput: (e) => paintLearners(e.target.value),
|
||||
});
|
||||
|
||||
const learnersSection = learners.length
|
||||
? el(
|
||||
'section',
|
||||
{ class: 'detail__section' },
|
||||
el(
|
||||
'h2',
|
||||
{},
|
||||
`Pokémon that can learn this${vg === 'all' ? '' : ` in ${prettify(vg)}`} (${learners.length})`,
|
||||
),
|
||||
learners.length > 24 ? learnerFilter : null,
|
||||
learnersGrid,
|
||||
)
|
||||
: null;
|
||||
|
||||
clear(view).append(
|
||||
el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/search' }, '‹ Lookup')),
|
||||
el(
|
||||
@ -109,6 +175,7 @@ export async function MoveDetail(id) {
|
||||
el('p', {}, effect),
|
||||
bits.length ? el('p', { class: 'movebody__bits' }, bits.join(' · ')) : null,
|
||||
),
|
||||
learnersSection,
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user