Compare commits

..

No commits in common. "c1f6007b250caeea34ec8e9c3369258f26a8099b" and "83cb89aedd05c64eb2b623013fa095a1443f9896" have entirely different histories.

7 changed files with 118 additions and 206 deletions

View File

@ -9,7 +9,7 @@ framework.
| Feature | How it works | | Feature | How it works |
| --- | --- | | --- | --- |
| **Game series selection** | Pick a *game* (PokéAPI version group, e.g. Scarlet & Violet). If it has more than one regional dex (Paldea / Kitakami / Blueberry) a sub-dex switcher appears. The choice drives which species show, their regional numbering, and which version's flavor text the detail page uses. Stored in `localStorage`. | | **Game series selection** | Pick a *game* (PokéAPI version group, e.g. Scarlet & Violet). If it has more than one regional dex (Paldea / Kitakami / Blueberry) a sub-dex switcher appears. The choice drives which species show, their regional numbering, and which version's flavor text the detail page uses. Stored in `localStorage`. |
| **Per-game Pokémon tracking** | Seen / caught are stored **per game** (version-group key) — catching Pikachu in Blue says nothing about your Gold save. `favorite` and `note` are global (they describe the Pokémon, keyed by National Dex id). The `all` bucket ("All games" mode) reads as the union — "caught anywhere" — and is where a pre-per-game save or an older backup migrates to. `#/progress` shows each game's own tally (National = the union), leading with the games you've played and tucking the rest behind a toggle. The active Pokémon is also a single deep-linkable route (`#/pokemon/25`). | | **Unified Pokémon selection** | One tracking record per Pokémon, keyed by **National Dex id**. Mark Pikachu once and every game's view reflects it — each dex computes its own "Seen / Caught" totals by intersecting its species list with that single map. `#/progress` breaks that down per game, leading with the ones you've actually played (selected, or imported a save for) and tucking the rest behind a toggle. The active Pokémon is also a single deep-linkable route (`#/pokemon/25`). |
## Data & storage split ## Data & storage split

View File

@ -1,160 +1,98 @@
import { createStore } from './createStore.js'; import { createStore } from './createStore.js';
import { settings } from './settings.js';
/** /**
* Per-game Pokémon tracking. * The "unified Pokémon selection".
* *
* Seen / caught are stored *per game* (PokéAPI version-group key) catching * One record per Pokémon, keyed by NATIONAL dex id. That key is what makes
* Pikachu in Blue says nothing about your Gold save. `favorite` and `note` * it unified: mark Pikachu once and every game's view Kanto, Paldea,
* are the exception: those describe the Pokémon itself, so they're global, * National, search results reflects it, because each view computes its
* one record keyed by National dex id. * own progress by intersecting its species list with this single map.
*
* pokemon: { [id]: { favorite, note, updatedAt } } // global
* games: { [vgKey]: { [id]: { seen, caught, updatedAt } } } // per game
*
* The 'all' key ("All games" / National mode) is special: reading it returns
* the union of every game "have I caught this anywhere". It's also a real
* bucket you write to when you mark something while no specific game is
* selected (and where a pre-per-game save's data was migrated to).
*/ */
export const selection = createStore('pdx.selection', { export const selection = createStore('pdx.selection', {
version: 3, version: 2,
pokemon: {}, pokemon: {},
games: {},
}); });
const GBLANK = { seen: false, caught: false }; const BLANK = { seen: false, caught: false, favorite: false, note: '' };
const PBLANK = { favorite: false, note: '' };
/** Convert any older (flat, one-record) shape to the current per-game one. */ export function entry(id) {
export function normalizeSelection(raw) { return selection.get().pokemon[id] || BLANK;
if (!raw || typeof raw !== 'object') return { version: 3, pokemon: {}, games: {} };
if (raw.version >= 3 && raw.games) {
return { version: 3, pokemon: raw.pokemon || {}, games: raw.games || {} };
}
const pokemon = {};
const all = {};
for (const [id, e] of Object.entries(raw.pokemon || {})) {
if (e.favorite || (e.note && String(e.note).trim())) {
pokemon[id] = { favorite: !!e.favorite, note: e.note || '', updatedAt: e.updatedAt };
}
if (e.seen || e.caught) {
all[id] = { seen: !!e.seen, caught: !!e.caught, updatedAt: e.updatedAt };
}
}
return { version: 3, pokemon, games: Object.keys(all).length ? { all } : {} };
} }
// One-time migration of an existing v2 store on this device. export function toggle(id, field) {
{
const cur = selection.get();
if (!cur || cur.version < 3 || !cur.games) selection.replace(normalizeSelection(cur));
}
function curGame() {
return settings.get().versionGroup || 'all';
}
function unionEntry(id) {
const games = selection.get().games;
let seen = false;
let caught = false;
let updatedAt = '';
for (const k in games) {
const e = games[k][id];
if (!e) continue;
if (e.seen || e.caught) seen = true;
if (e.caught) caught = true;
if (e.updatedAt && e.updatedAt > updatedAt) updatedAt = e.updatedAt;
}
return { seen, caught, updatedAt };
}
/** Raw seen/caught for one game (union when vgKey === 'all'). */
export function gameEntry(id, vgKey = curGame()) {
if (vgKey === 'all') return unionEntry(id);
return selection.get().games[vgKey]?.[id] || GBLANK;
}
/** Merged view for a game: its seen/caught + the global favorite/note. */
export function entry(id, vgKey = curGame()) {
const g = gameEntry(id, vgKey);
const p = selection.get().pokemon[id] || PBLANK;
return { seen: !!g.seen, caught: !!g.caught, favorite: !!p.favorite, note: p.note || '' };
}
export function toggle(id, field, vgKey = curGame()) {
selection.set((s) => { selection.set((s) => {
const stamp = new Date().toISOString(); const current = s.pokemon[id] || BLANK;
if (field === 'favorite') { const next = {
const cur = s.pokemon[id] || PBLANK; ...current,
return { [field]: !current[field],
...s, updatedAt: new Date().toISOString(),
pokemon: { ...s.pokemon, [id]: { ...cur, favorite: !cur.favorite, updatedAt: stamp } }, };
}; // Catching something implies you've seen it.
}
const bucket = s.games[vgKey] || {};
const cur = bucket[id] || GBLANK;
const next = { ...cur, [field]: !cur[field], updatedAt: stamp };
if (field === 'caught' && next.caught) next.seen = true; if (field === 'caught' && next.caught) next.seen = true;
return { ...s, games: { ...s.games, [vgKey]: { ...bucket, [id]: next } } }; return { ...s, pokemon: { ...s.pokemon, [id]: next } };
}); });
} }
export function setNote(id, note) { export function setNote(id, note) {
selection.set((s) => { selection.set((s) => {
const cur = s.pokemon[id] || PBLANK; const current = s.pokemon[id] || BLANK;
return { return {
...s, ...s,
pokemon: { ...s.pokemon, [id]: { ...cur, note, updatedAt: new Date().toISOString() } }, pokemon: {
...s.pokemon,
[id]: { ...current, note, updatedAt: new Date().toISOString() },
},
}; };
}); });
} }
/** Merge in seen/caught species lists (e.g. from a save-file import). Never clears. */
export function importFlags({ seen = [], caught = [] } = {}) {
selection.set((s) => {
const pokemon = { ...s.pokemon };
const stamp = new Date().toISOString();
const bump = (id, patch) => {
pokemon[id] = { ...(pokemon[id] || BLANK), ...patch, updatedAt: stamp };
};
for (const id of seen) bump(id, { seen: true });
for (const id of caught) bump(id, { seen: true, caught: true });
return { ...s, pokemon };
});
}
/** /**
* Merge seen/caught lists into one game's bucket (a save-file import). * Set seen/caught to exactly the given lists favourites and notes are
* Never clears. `game` defaults to the selected one. * kept, everything else's seen/caught is cleared. For "replace with this
* save" imports.
*/ */
export function importFlags({ seen = [], caught = [], game } = {}) { export function replaceFlags({ seen = [], caught = [] } = {}) {
const vgKey = game || curGame();
selection.set((s) => { selection.set((s) => {
const stamp = new Date().toISOString(); const stamp = new Date().toISOString();
const bucket = { ...(s.games[vgKey] || {}) }; const pokemon = {};
for (const id of seen) bucket[id] = { ...(bucket[id] || GBLANK), seen: true, updatedAt: stamp }; for (const [id, e] of Object.entries(s.pokemon)) {
for (const id of caught) bucket[id] = { ...(bucket[id] || GBLANK), seen: true, caught: true, updatedAt: stamp }; if (e.favorite || (e.note && e.note.trim())) {
return { ...s, games: { ...s.games, [vgKey]: bucket } }; pokemon[id] = { seen: false, caught: false, favorite: !!e.favorite, note: e.note || '', updatedAt: e.updatedAt || stamp };
}
}
const bump = (id, patch) => {
pokemon[id] = { ...(pokemon[id] || BLANK), ...patch, updatedAt: stamp };
};
for (const id of seen) bump(id, { seen: true });
for (const id of caught) bump(id, { seen: true, caught: true });
return { ...s, pokemon };
}); });
} }
/** Set one game's bucket to exactly these lists (a "replace with this save" import). */ /** Aggregate seen/caught counts over an arbitrary list of national ids. */
export function replaceFlags({ seen = [], caught = [], game } = {}) { export function stats(speciesIds) {
const vgKey = game || curGame(); const p = selection.get().pokemon;
selection.set((s) => {
const stamp = new Date().toISOString();
const bucket = {};
for (const id of seen) bucket[id] = { seen: true, caught: false, updatedAt: stamp };
for (const id of caught) bucket[id] = { seen: true, caught: true, updatedAt: stamp };
return { ...s, games: { ...s.games, [vgKey]: bucket } };
});
}
/** Aggregate seen/caught over a species list for one game (union when 'all'). */
export function stats(speciesIds, vgKey = curGame()) {
const get =
vgKey === 'all'
? unionEntry
: (id) => selection.get().games[vgKey]?.[id] || GBLANK;
let seen = 0; let seen = 0;
let caught = 0; let caught = 0;
for (const id of speciesIds) { for (const id of speciesIds) {
const e = get(id); const e = p[id];
if (!e) continue;
if (e.seen || e.caught) seen++; if (e.seen || e.caught) seen++;
if (e.caught) caught++; if (e.caught) caught++;
} }
return { seen, caught, total: speciesIds.length }; return { seen, caught, total: speciesIds.length };
} }
/** Caught/seen across every game — the true National Dex tally. */
export function statsNational(speciesIds) {
return stats(speciesIds, 'all');
}

View File

@ -112,53 +112,45 @@
color: var(--text-dim); color: var(--text-dim);
} }
/* Team toolbar: primary mode switch on the left, reference shortcuts on the /* Row of shortcut chips under a view header (Team → Natures / Type chart / …) */
right. One band so the reference links read as part of the view's chrome .toollinks {
rather than a strip pasted above it. */
.teamview__bar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; gap: 8px;
justify-content: space-between; margin: -6px 0 18px;
gap: 8px 16px;
margin-bottom: 16px;
} }
.teamview__bar .seg { .toollink {
margin-bottom: 0;
}
.teamview__refs {
display: flex;
flex-wrap: wrap;
gap: 2px;
}
.reflink {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 5px; gap: 6px;
padding: 6px 10px; padding: 7px 13px 7px 10px;
border-radius: 8px; border: 1px solid var(--border);
color: var(--text-dim); border-radius: 999px;
font-size: 0.8rem; background: var(--surface);
color: var(--text);
font-size: 0.82rem;
font-weight: 600; font-weight: 600;
text-decoration: none; text-decoration: none;
line-height: 1; line-height: 1;
transition: color 0.12s ease, background 0.12s ease; transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease;
} }
.reflink:hover { .toollink:hover {
color: var(--text); border-color: var(--accent);
background: var(--surface-2); background: color-mix(in srgb, var(--accent) 10%, var(--surface));
} }
.reflink:active { .toollink:active {
transform: scale(0.96); transform: scale(0.96);
} }
.reflink__ico { .toollink__ico {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 50%;
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--accent); color: var(--accent);
font-size: 0.82rem; font-size: 0.72rem;
}
.teamcompare__note {
margin: 0 0 10px;
font-size: 0.82rem;
color: var(--text-dim);
} }
.link { .link {

View File

@ -3,7 +3,7 @@ import { loadSnapshot } from '../data/snapshot.js';
import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js'; import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js';
import { settings } from '../store/settings.js'; import { settings } from '../store/settings.js';
import { ui } from '../store/ui.js'; import { ui } from '../store/ui.js';
import { selection, stats, entry, gameEntry } from '../store/selection.js'; import { selection, stats } from '../store/selection.js';
import { Card } from '../components/Card.js'; import { Card } from '../components/Card.js';
import { ProgressRing } from '../components/ProgressRing.js'; import { ProgressRing } from '../components/ProgressRing.js';
import { Sprite } from '../components/Sprite.js'; import { Sprite } from '../components/Sprite.js';
@ -56,7 +56,7 @@ function sortValue(row, key) {
case 'weight': return sp.weight ?? 0; case 'weight': return sp.weight ?? 0;
case 'baseExp': return sp.baseExp ?? 0; case 'baseExp': return sp.baseExp ?? 0;
case 'catchrate': return sp.captureRate ?? 0; case 'catchrate': return sp.captureRate ?? 0;
case 'caught': return gameEntry(sp.id).updatedAt || ''; case 'caught': return selection.get().pokemon[sp.id]?.updatedAt || '';
default: return 0; default: return 0;
} }
} }
@ -404,16 +404,17 @@ export async function DexGrid() {
); );
function counts() { function counts() {
const p = selection.get().pokemon;
let caught = 0; let caught = 0;
let favorite = 0; let favorite = 0;
let legendary = 0; let legendary = 0;
let notes = 0; let notes = 0;
for (const { species } of rows) { for (const { species } of rows) {
const e = entry(species.id); const e = p[species.id];
if (e.caught) caught++; if (e?.caught) caught++;
if (e.favorite) favorite++; if (e?.favorite) favorite++;
if (species.isLegendary || species.isMythical) legendary++; if (species.isLegendary || species.isMythical) legendary++;
if (e.note && e.note.trim()) notes++; if (e?.note && e.note.trim()) notes++;
} }
return { all: rows.length, caught, missing: rows.length - caught, favorite, legendary, notes }; return { all: rows.length, caught, missing: rows.length - caught, favorite, legendary, notes };
} }
@ -428,6 +429,7 @@ export async function DexGrid() {
function paintGrid() { function paintGrid() {
const st = settings.get(); const st = settings.get();
const pokemonState = selection.get().pokemon;
const gen = snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9; const gen = snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9;
const boxed = st.spriteStyle === 'game' && gen <= 2; const boxed = st.spriteStyle === 'game' && gen <= 2;
const filterTest = FILTERS.find((f) => f.key === filterKey).test; const filterTest = FILTERS.find((f) => f.key === filterKey).test;
@ -448,7 +450,8 @@ export async function DexGrid() {
if (filterEggGroup && !(species.eggGroups || []).includes(filterEggGroup)) return false; if (filterEggGroup && !(species.eggGroups || []).includes(filterEggGroup)) return false;
if (filterMinBst && (species.bst || 0) < filterMinBst) return false; if (filterMinBst && (species.bst || 0) < filterMinBst) return false;
if (filterFullyEvolved && !isFullyEvolved(species)) return false; if (filterFullyEvolved && !isFullyEvolved(species)) return false;
return filterTest(entry(species.id), species); const e = pokemonState[species.id] || { seen: false, caught: false, favorite: false };
return filterTest(e, species);
}); });
const dir = sortDesc ? -1 : 1; const dir = sortDesc ? -1 : 1;

View File

@ -1,7 +1,7 @@
import { el, clear, onTeardown } from '../lib/dom.js'; import { el, clear, onTeardown } from '../lib/dom.js';
import { loadSnapshot } from '../data/snapshot.js'; import { loadSnapshot } from '../data/snapshot.js';
import { settings } from '../store/settings.js'; import { settings } from '../store/settings.js';
import { selection, stats, statsNational } from '../store/selection.js'; import { selection, stats } from '../store/selection.js';
import { ui } from '../store/ui.js'; import { ui } from '../store/ui.js';
import { playedGames, isPlayed } from '../store/playedGames.js'; import { playedGames, isPlayed } from '../store/playedGames.js';
import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js'; import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js';
@ -31,7 +31,7 @@ export async function ProgressView() {
}; };
function gameRow(vg, { played }) { function gameRow(vg, { played }) {
const s = stats(speciesIdsFor(vg.key), vg.key); const s = stats(speciesIdsFor(vg.key));
return el( return el(
'button', 'button',
{ {
@ -56,8 +56,8 @@ export async function ProgressView() {
const anyPlayed = gens.some(({ versionGroups }) => versionGroups.some((vg) => isPlayed(vg.key))); const anyPlayed = gens.some(({ versionGroups }) => versionGroups.some((vg) => isPlayed(vg.key)));
const showAll = ui.get().progShowAll || !anyPlayed; const showAll = ui.get().progShowAll || !anyPlayed;
// National total — caught in any game. // National total — always shown.
const nat = statsNational(snap.species.map((s) => s.id)); const nat = stats(snap.species.map((s) => s.id));
list.append( list.append(
el( el(
'div', 'div',
@ -120,7 +120,7 @@ export async function ProgressView() {
'header', 'header',
{ class: 'view__header' }, { class: 'view__header' },
el('h1', {}, 'Progress by game'), el('h1', {}, 'Progress by game'),
el('p', {}, 'What youve caught in each game. Tap one to switch to it.'), el('p', {}, 'Your one unified caught record, counted against each games dex. Games you havent played are tucked away. Tap a game to switch to it.'),
), ),
el( el(
'p', 'p',

View File

@ -1,7 +1,6 @@
import { el, onTeardown } from '../lib/dom.js'; import { el, onTeardown } from '../lib/dom.js';
import { settings, applyTheme } from '../store/settings.js'; import { settings, applyTheme } from '../store/settings.js';
import { selection, importFlags, replaceFlags, normalizeSelection } from '../store/selection.js'; import { selection, importFlags, replaceFlags } from '../store/selection.js';
import { prettify } from '../data/pokedex-resolver.js';
import { chooseDialog } from '../lib/dialog.js'; import { chooseDialog } from '../lib/dialog.js';
import { team } from '../store/team.js'; import { team } from '../store/team.js';
import { formTracking } from '../store/formTracking.js'; import { formTracking } from '../store/formTracking.js';
@ -145,30 +144,23 @@ export async function SettingsView() {
return; return;
} }
const who = dex.trainer || dex.game; const who = dex.trainer || dex.game;
// Which game bucket this save's catches belong to. The parser can't
// always tell (an RSE save could be Ruby or Emerald) — prefer the
// game the user currently has selected if it's one of the candidates.
const cands = dex.versionGroups || [];
const target = cands.includes(settings.get().versionGroup)
? settings.get().versionGroup
: cands[0];
const choice = await chooseDialog({ const choice = await chooseDialog({
title: `${dex.game}${dex.trainer ? ` · ${dex.trainer}` : ''}`, title: `${dex.game}${dex.trainer ? ` · ${dex.trainer}` : ''}`,
body: `${dex.seen.length} seen · ${dex.caught.length} caught in this save.`, body: `${dex.seen.length} seen · ${dex.caught.length} caught in this save.`,
choices: [ choices: [
{ key: 'merge', label: `Add to ${prettify(target)}` }, { key: 'merge', label: 'Add to my Pokédex' },
{ key: 'replace', label: `Replace ${prettify(target)} with this`, class: 'button--danger' }, { key: 'replace', label: 'Replace mine with this', class: 'button--danger' },
{ key: null, label: 'Cancel', class: 'button--ghost' }, { key: null, label: 'Cancel', class: 'button--ghost' },
], ],
}); });
if (choice === 'merge') { if (choice === 'merge') {
importFlags({ seen: dex.seen, caught: dex.caught, game: target }); importFlags({ seen: dex.seen, caught: dex.caught });
markPlayed(...cands); markPlayed(...(dex.versionGroups || []));
saveNote.textContent = `Added ${dex.caught.length} caught / ${dex.seen.length} seen to ${prettify(target)}.`; saveNote.textContent = `Added ${dex.caught.length} caught / ${dex.seen.length} seen from ${who}.`;
} else if (choice === 'replace') { } else if (choice === 'replace') {
replaceFlags({ seen: dex.seen, caught: dex.caught, game: target }); replaceFlags({ seen: dex.seen, caught: dex.caught });
markPlayed(...cands); markPlayed(...(dex.versionGroups || []));
saveNote.textContent = `Set ${prettify(target)}'s Pokédex from ${who}.`; saveNote.textContent = `Replaced tracking with ${who}'s Pokédex (favourites and notes kept).`;
} else { } else {
saveNote.textContent = ''; saveNote.textContent = '';
} }
@ -186,7 +178,7 @@ export async function SettingsView() {
try { try {
const data = JSON.parse(await file.text()); const data = JSON.parse(await file.text());
if (data.settings) settings.replace(data.settings); if (data.settings) settings.replace(data.settings);
if (data.selection) selection.replace(normalizeSelection(data.selection)); if (data.selection) selection.replace(data.selection);
if (data.team) team.replace(data.team); if (data.team) team.replace(data.team);
if (data.formTracking) formTracking.replace(data.formTracking); if (data.formTracking) formTracking.replace(data.formTracking);
if (data.shinyHunts) shinyHunts.replace(data.shinyHunts); if (data.shinyHunts) shinyHunts.replace(data.shinyHunts);
@ -276,7 +268,7 @@ export async function SettingsView() {
type: 'button', type: 'button',
onclick: () => { onclick: () => {
if (confirm('Clear all seen/caught/favorite tracking? This cannot be undone.')) { if (confirm('Clear all seen/caught/favorite tracking? This cannot be undone.')) {
selection.replace({ version: 3, pokemon: {}, games: {} }); selection.replace({ version: 2, pokemon: {} });
} }
}, },
}, 'Clear tracking data'), }, 'Clear tracking data'),

View File

@ -67,19 +67,16 @@ export async function TeamView() {
{ class: 'view__header' }, { class: 'view__header' },
el('h1', {}, 'Team'), el('h1', {}, 'Team'),
el('p', {}, `Up to ${MAX_TEAM} Pokémon — check weaknesses, coverage and stats.`), el('p', {}, `Up to ${MAX_TEAM} Pokémon — check weaknesses, coverage and stats.`),
),
el(
'div',
{ class: 'teamview__bar' },
seg,
el( el(
'nav', 'nav',
{ class: 'teamview__refs', 'aria-label': 'Reference tools' }, { class: 'toollinks' },
el('a', { class: 'reflink', href: '#/natures' }, el('span', { class: 'reflink__ico', 'aria-hidden': 'true' }, '✦'), 'Natures'), el('a', { class: 'toollink', href: '#/natures' }, el('span', { class: 'toollink__ico', 'aria-hidden': 'true' }, '✦'), 'Natures'),
el('a', { class: 'reflink', href: '#/types' }, el('span', { class: 'reflink__ico', 'aria-hidden': 'true' }, '▦'), 'Type chart'), el('a', { class: 'toollink', href: '#/types' }, el('span', { class: 'toollink__ico', 'aria-hidden': 'true' }, '▦'), 'Type chart'),
el('a', { class: 'reflink', href: '#/breeding' }, el('span', { class: 'reflink__ico', 'aria-hidden': 'true' }, '⬡'), 'Breeding'), el('a', { class: 'toollink', href: '#/compare' }, el('span', { class: 'toollink__ico', 'aria-hidden': 'true' }, '⇄'), 'Compare'),
el('a', { class: 'toollink', href: '#/breeding' }, el('span', { class: 'toollink__ico', 'aria-hidden': 'true' }, '⬡'), 'Breeding'),
), ),
), ),
seg,
lineup, lineup,
body, body,
); );
@ -358,17 +355,7 @@ export async function TeamView() {
function compare(mem) { function compare(mem) {
const vg = snap.versionGroupByKey.get(settings.get().versionGroup); const vg = snap.versionGroupByKey.get(settings.get().versionGroup);
const gen = vg ? vg.generation : 9; const gen = vg ? vg.generation : 9;
return el( return CompareTable(mem, { gen, style: settings.get().spriteStyle });
'div',
{ class: 'teamcompare' },
el(
'p',
{ class: 'teamcompare__note' },
'Your six side by side. ',
el('a', { class: 'link', href: '#/compare' }, 'Compare any Pokémon →'),
),
CompareTable(mem, { gen, style: settings.get().spriteStyle }),
);
} }
// ---- Damage calculator ------------------------------------------ // ---- Damage calculator ------------------------------------------