import { el, clear, onTeardown } from '../lib/dom.js'; import { loadSnapshot } from '../data/snapshot.js'; import { settings } from '../store/settings.js'; import { ui } from '../store/ui.js'; import { getPokemon } from '../data/api.js'; import { prettify } from '../data/pokedex-resolver.js'; import { Sprite } from '../components/Sprite.js'; import { openPokemonPicker } from '../components/PokemonPicker.js'; import { segThumb } from '../lib/anim.js'; const EGG_LABEL = { water1: 'Water 1', water2: 'Water 2', water3: 'Water 3', ground: 'Field', humanshape: 'Human-Like', indeterminate: 'Amorphous', plant: 'Grass', 'no-eggs': 'Undiscovered', ditto: 'Ditto', }; const eggLabel = (g) => EGG_LABEL[g] || prettify(g); const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop()); export async function BreedingView() { const view = el('section', { class: 'view lookup breedview' }); const snap = await loadSnapshot(); const style = settings.get().spriteStyle; const vg = settings.get().versionGroup; const groups = [...new Set(snap.species.flatMap((s) => s.eggGroups || []))] .filter((g) => g !== 'no-eggs') .sort((a, b) => eggLabel(a).localeCompare(eggLabel(b))); let mode = ui.get().breedMode === 'compat' ? 'compat' : 'groups'; let group = groups.includes(ui.get().breedGroup) ? ui.get().breedGroup : groups[0]; let target = snap.speciesById.get(ui.get().breedTarget) || null; const SEG = [ ['groups', 'Egg groups'], ['compat', 'Can breed with'], ]; const seg = el( 'div', { class: 'seg' }, ...SEG.map(([id, label], i) => el( 'button', { type: 'button', class: `seg__btn${id === mode ? ' is-active' : ''}`, onclick: () => { mode = id; ui.set({ breedMode: id }); seg .querySelectorAll('.seg__btn') .forEach((b, j) => b.classList.toggle('is-active', SEG[j][0] === id)); setThumb(i); render(); }, }, label, ), ), ); const setThumb = segThumb(seg, SEG.length, Math.max(0, SEG.findIndex(([id]) => id === mode))); const body = el('div', { class: 'breedview__body' }); function monGrid(list) { return list.length ? el( 'div', { class: 'ab-mons' }, ...list.map((sp) => el( 'a', { class: 'ab-mon', href: `#/pokemon/${sp.id}` }, Sprite(sp.id, { style, alt: sp.name, size: 48 }), el('span', {}, sp.name.replace(/-/g, ' ')), ), ), ) : el('p', { class: 'detail__muted' }, 'None.'); } function renderGroups() { const chips = el( 'div', { class: 'breed__chips' }, ...groups.map((g) => el( 'button', { type: 'button', class: `breed__chip${g === group ? ' is-on' : ''}`, onclick: () => { group = g; ui.set({ breedGroup: g }); render(); }, }, eggLabel(g), ), ), ); const list = snap.species .filter((s) => (s.eggGroups || []).includes(group)) .sort((a, b) => a.id - b.id); body.append( chips, el('h2', { class: 'ppanel__sub' }, `${eggLabel(group)} (${list.length})`), monGrid(list), ); } function renderCompat() { const slot = el( 'button', { type: 'button', class: 'breed__pick', onclick: () => openPokemonPicker( (id) => { target = snap.speciesById.get(id); ui.set({ breedTarget: id }); render(); }, { closeAfterPick: true }, ), }, target ? el('span', {}, Sprite(target.id, { style, size: 40, alt: target.name }), ` ${prettify(target.name)}`) : 'Choose a Pokémon…', ); body.append(slot); if (!target) return; const tGroups = new Set(target.eggGroups || []); if (tGroups.has('no-eggs')) { body.append(el('p', { class: 'detail__muted' }, `${prettify(target.name)} is in the Undiscovered egg group — it can’t be bred.`)); return; } const isDitto = tGroups.has('ditto'); const genderless = target.genderRate === -1; let list; if (isDitto) { list = snap.species.filter((s) => !s.eggGroups.includes('no-eggs') && !s.eggGroups.includes('ditto')); } else if (genderless) { list = snap.species.filter((s) => (s.eggGroups || []).includes('ditto')); } else { list = snap.species.filter((s) => { if (s.id === target.id) return false; if ((s.eggGroups || []).includes('ditto')) return true; if ((s.eggGroups || []).includes('no-eggs')) return false; return (s.eggGroups || []).some((g) => tGroups.has(g) && g !== 'ditto'); }); } list.sort((a, b) => a.id - b.id); const note = genderless ? 'Genderless — can only breed with Ditto.' : isDitto ? 'Ditto breeds with any Pokémon that can breed.' : `Egg groups: ${[...tGroups].map(eggLabel).join(', ')}. Needs an opposite-gender partner (or Ditto).`; // Egg moves belong to the family's lowest breeding stage (Pichu, not // Pikachu) — walk down the evolves-from chain. let base = target; while (base.evolvesFromId) { const prev = snap.speciesById.get(base.evolvesFromId); if (!prev) break; base = prev; } const eggMovesBox = el('div', { class: 'breed__eggmoves' }, el('p', { class: 'detail__muted' }, 'Loading egg moves…')); getPokemon(base.id) .then((pk) => { const eggMoves = pk.moves.filter((m) => m.version_group_details.some( (d) => d.move_learn_method.name === 'egg' && (vg === 'all' || d.version_group.name === vg), ), ); clear(eggMovesBox); if (!eggMoves.length) { eggMovesBox.append(el('p', { class: 'detail__muted' }, 'No egg moves for this game.')); return; } eggMovesBox.append( el( 'h3', { class: 'ppanel__sub' }, `Egg moves (${eggMoves.length})${base.id !== target.id ? ` — hatches as ${prettify(base.name)}` : ''}`, ), el( 'p', { class: 'detail__muted' }, 'A parent that knows one of these passes it down. Open a move to see which Pokémon can learn it.', ), el( 'div', { class: 'breed__eggmove-list' }, ...eggMoves.map((m) => el('a', { class: 'link', href: `#/move/${idFromUrl(m.move.url)}` }, m.move.name.replace(/-/g, ' ')), ), ), ); }) .catch(() => clear(eggMovesBox).append(el('p', { class: 'detail__muted' }, 'Egg moves unavailable offline.'))); body.append( el('p', { class: 'breed__note' }, note), eggMovesBox, el('h2', { class: 'ppanel__sub' }, `Compatible partners (${list.length})`), monGrid(list), ); } function render() { clear(body); if (mode === 'groups') renderGroups(); else renderCompat(); } clear(view).append( el('nav', { class: 'lookup__nav' }, el('a', { class: 'link', href: '#/team' }, '‹ Team')), el( 'header', { class: 'view__header' }, el('h1', {}, 'Breeding'), el('p', {}, 'Egg-group members and who a Pokémon can produce eggs with.'), ), seg, body, ); render(); onTeardown(view, () => {}); return view; }