- 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>
72 lines
2.2 KiB
JavaScript
72 lines
2.2 KiB
JavaScript
import { el, clear } from '../lib/dom.js';
|
||
import { getItem } from '../data/api.js';
|
||
import { prettify } from '../data/pokedex-resolver.js';
|
||
|
||
const ITEM_SPRITE = (name) =>
|
||
`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/${name}.png`;
|
||
|
||
const english = (entries, key) => {
|
||
const hit = (entries || []).find((e) => e.language.name === 'en');
|
||
return hit ? hit[key].replace(/\s+/g, ' ').trim() : '';
|
||
};
|
||
|
||
function fact(k, v) {
|
||
return el('div', { class: 'fact' }, el('dt', {}, k), el('dd', {}, String(v)));
|
||
}
|
||
|
||
export async function ItemDetail(id) {
|
||
const view = el('section', { class: 'view lookup' });
|
||
view.append(el('div', { class: 'view--loading' }, 'Loading item…'));
|
||
|
||
let d;
|
||
try {
|
||
d = await getItem(id);
|
||
} catch (err) {
|
||
clear(view).append(
|
||
el(
|
||
'div',
|
||
{ class: 'view--error' },
|
||
el('h1', {}, 'Could not load this item'),
|
||
el('p', {}, navigator.onLine ? err.message : 'You appear to be offline.'),
|
||
el('a', { class: 'button', href: '#/search' }, 'Back to lookup'),
|
||
),
|
||
);
|
||
return view;
|
||
}
|
||
|
||
const effect =
|
||
english(d.effect_entries, 'short_effect') ||
|
||
english(d.flavor_text_entries, 'text') ||
|
||
'No description.';
|
||
|
||
const img = el('img', { class: 'lookup__item-img', alt: d.name, src: ITEM_SPRITE(d.name) });
|
||
img.addEventListener('error', () => img.remove(), { once: true });
|
||
|
||
clear(view).append(
|
||
el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/search' }, '‹ Lookup')),
|
||
el(
|
||
'header',
|
||
{ class: 'lookup__head lookup__head--item' },
|
||
img,
|
||
el(
|
||
'div',
|
||
{},
|
||
el('h1', {}, d.name.replace(/-/g, ' ')),
|
||
el('span', { class: 'lookup__class' }, prettify(d.category?.name || 'item')),
|
||
),
|
||
),
|
||
el(
|
||
'dl',
|
||
{ class: 'pfacts' },
|
||
d.cost ? fact('Buy price', `₽${d.cost.toLocaleString()}`) : null,
|
||
d.fling_power != null ? fact('Fling power', d.fling_power) : null,
|
||
d.attributes?.length
|
||
? fact('Attributes', d.attributes.map((a) => prettify(a.name)).join(', '))
|
||
: null,
|
||
fact('Held by', `${d.held_by_pokemon.length} Pokémon`),
|
||
),
|
||
el('section', { class: 'detail__section' }, el('h2', {}, 'Effect'), el('p', {}, effect)),
|
||
);
|
||
return view;
|
||
}
|