- Detail page: base stats, type matchups (defending), evolution chain, learnset with per-move power/type/acc/PP/effect, wild locations - Game-aware: abilities gated to Gen 3+ (hidden to Gen 5+), type chart applies Gen 1 / pre-Gen 6 rules, move stats use PokeAPI past_values, pre-Gen 4 physical/special-by-type, evolution tree re-parented to the selected generation - Moves and evolution clickable/expandable; no horizontal scroll - Fix: .view-host shrink-wrapped as a grid item on desktop (margin:0 auto) - Era-accurate sprite option; Search tab relabelled as global lookup Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
112 lines
3.3 KiB
JavaScript
112 lines
3.3 KiB
JavaScript
import { el } from '../lib/dom.js';
|
|
import { Sprite } from './Sprite.js';
|
|
|
|
const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop());
|
|
|
|
/** Human-readable trigger for one evolution step. */
|
|
function describe(details) {
|
|
if (!details || !details.length) return '';
|
|
const d = details[0];
|
|
if (d.min_level) return `Lv. ${d.min_level}`;
|
|
if (d.item) return `use ${d.item.name.replace(/-/g, ' ')}`;
|
|
if (d.trigger?.name === 'trade') {
|
|
return d.held_item ? `trade w/ ${d.held_item.name.replace(/-/g, ' ')}` : 'trade';
|
|
}
|
|
if (d.min_happiness) return 'high friendship';
|
|
if (d.known_move_type) return `knows a ${d.known_move_type.name} move`;
|
|
if (d.trigger?.name) return d.trigger.name.replace(/-/g, ' ');
|
|
return '';
|
|
}
|
|
|
|
function node(stage, style, versionGroup) {
|
|
return el(
|
|
'a',
|
|
{ class: 'evo__node', href: `#/pokemon/${stage.id}` },
|
|
Sprite(stage.id, { style, versionGroup, alt: stage.name, size: 72 }),
|
|
el('span', { class: 'evo__name' }, stage.name.replace(/-/g, ' ')),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* PokéAPI returns the full modern evolution tree. Re-parent it around the
|
|
* selected game's generation: stages introduced later (cross-gen evos, later
|
|
* babies) are dropped, and their in-era descendants attach to the nearest
|
|
* surviving ancestor — so Pikachu → Raichu still shows in Red/Blue even
|
|
* though Pichu doesn't, and Bellossom never appears there.
|
|
*/
|
|
export function EvolutionChain(chain, { style = 'default', versionGroup, genOf = () => 1, maxGen = 9 } = {}) {
|
|
const nodes = new Map();
|
|
let pruned = false;
|
|
|
|
const collect = (link, parentId) => {
|
|
const id = idFromUrl(link.species.url);
|
|
let currentId = parentId;
|
|
if (genOf(id) <= maxGen) {
|
|
if (!nodes.has(id)) {
|
|
nodes.set(id, {
|
|
id,
|
|
name: link.species.name,
|
|
parent: parentId,
|
|
via: parentId != null ? describe(link.evolution_details) : '',
|
|
kids: [],
|
|
});
|
|
}
|
|
currentId = id;
|
|
} else {
|
|
pruned = true;
|
|
}
|
|
for (const child of link.evolves_to) collect(child, currentId);
|
|
};
|
|
collect(chain.chain, null);
|
|
|
|
for (const n of nodes.values()) {
|
|
if (n.parent != null && nodes.has(n.parent)) nodes.get(n.parent).kids.push(n.id);
|
|
}
|
|
|
|
if (nodes.size <= 1) {
|
|
return el(
|
|
'p',
|
|
{ class: 'detail__muted' },
|
|
pruned ? 'No evolutions available in this game.' : 'Does not evolve.',
|
|
);
|
|
}
|
|
|
|
const roots = [...nodes.values()].filter((n) => n.parent == null || !nodes.has(n.parent));
|
|
const rows = [];
|
|
const walkPaths = (n, acc) => {
|
|
const path = [...acc, n];
|
|
if (!n.kids.length) rows.push(path);
|
|
else for (const k of n.kids) walkPaths(nodes.get(k), path);
|
|
};
|
|
roots.forEach((r) => walkPaths(r, []));
|
|
|
|
const wrap = el(
|
|
'div',
|
|
{ class: 'evo' },
|
|
...rows.map((path) => {
|
|
const row = el('div', { class: 'evo__row' });
|
|
path.forEach((stage, i) => {
|
|
if (i > 0) {
|
|
row.append(
|
|
el(
|
|
'span',
|
|
{ class: 'evo__arrow' },
|
|
'→',
|
|
stage.via ? el('span', { class: 'evo__via' }, stage.via) : null,
|
|
),
|
|
);
|
|
}
|
|
row.append(node(stage, style, versionGroup));
|
|
});
|
|
return row;
|
|
}),
|
|
);
|
|
|
|
if (pruned) {
|
|
wrap.append(
|
|
el('p', { class: 'evo__note' }, 'Cross-generation stages hidden for this game.'),
|
|
);
|
|
}
|
|
return wrap;
|
|
}
|