dex/src/views/ItemDetail.js
chris 0f00b50198 Move & item lookup: tabbed Search + detail pages
- 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>
2026-08-27 13:51:51 -04:00

72 lines
2.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}