Compare commits
2 Commits
83cb89aedd
...
c1f6007b25
| Author | SHA1 | Date | |
|---|---|---|---|
| c1f6007b25 | |||
| 31a00fdf52 |
@ -9,7 +9,7 @@ framework.
|
||||
| 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`. |
|
||||
| **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`). |
|
||||
| **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`). |
|
||||
|
||||
## Data & storage split
|
||||
|
||||
|
||||
@ -1,98 +1,160 @@
|
||||
import { createStore } from './createStore.js';
|
||||
import { settings } from './settings.js';
|
||||
|
||||
/**
|
||||
* The "unified Pokémon selection".
|
||||
* Per-game Pokémon tracking.
|
||||
*
|
||||
* One record per Pokémon, keyed by NATIONAL dex id. That key is what makes
|
||||
* it unified: mark Pikachu once and every game's view — Kanto, Paldea,
|
||||
* National, search results — reflects it, because each view computes its
|
||||
* own progress by intersecting its species list with this single map.
|
||||
* Seen / caught are stored *per game* (PokéAPI version-group key) — catching
|
||||
* Pikachu in Blue says nothing about your Gold save. `favorite` and `note`
|
||||
* are the exception: those describe the Pokémon itself, so they're global,
|
||||
* one record keyed by National dex id.
|
||||
*
|
||||
* 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', {
|
||||
version: 2,
|
||||
version: 3,
|
||||
pokemon: {},
|
||||
games: {},
|
||||
});
|
||||
|
||||
const BLANK = { seen: false, caught: false, favorite: false, note: '' };
|
||||
const GBLANK = { seen: false, caught: false };
|
||||
const PBLANK = { favorite: false, note: '' };
|
||||
|
||||
export function entry(id) {
|
||||
return selection.get().pokemon[id] || BLANK;
|
||||
/** Convert any older (flat, one-record) shape to the current per-game one. */
|
||||
export function normalizeSelection(raw) {
|
||||
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 } : {} };
|
||||
}
|
||||
|
||||
export function toggle(id, field) {
|
||||
// One-time migration of an existing v2 store on this device.
|
||||
{
|
||||
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) => {
|
||||
const current = s.pokemon[id] || BLANK;
|
||||
const next = {
|
||||
...current,
|
||||
[field]: !current[field],
|
||||
updatedAt: new Date().toISOString(),
|
||||
const stamp = new Date().toISOString();
|
||||
if (field === 'favorite') {
|
||||
const cur = s.pokemon[id] || PBLANK;
|
||||
return {
|
||||
...s,
|
||||
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;
|
||||
return { ...s, pokemon: { ...s.pokemon, [id]: next } };
|
||||
return { ...s, games: { ...s.games, [vgKey]: { ...bucket, [id]: next } } };
|
||||
});
|
||||
}
|
||||
|
||||
export function setNote(id, note) {
|
||||
selection.set((s) => {
|
||||
const current = s.pokemon[id] || BLANK;
|
||||
const cur = s.pokemon[id] || PBLANK;
|
||||
return {
|
||||
...s,
|
||||
pokemon: {
|
||||
...s.pokemon,
|
||||
[id]: { ...current, note, updatedAt: new Date().toISOString() },
|
||||
},
|
||||
pokemon: { ...s.pokemon, [id]: { ...cur, 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 };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set seen/caught to exactly the given lists — favourites and notes are
|
||||
* kept, everything else's seen/caught is cleared. For "replace with this
|
||||
* save" imports.
|
||||
* Merge seen/caught lists into one game's bucket (a save-file import).
|
||||
* Never clears. `game` defaults to the selected one.
|
||||
*/
|
||||
export function replaceFlags({ seen = [], caught = [] } = {}) {
|
||||
export function importFlags({ seen = [], caught = [], game } = {}) {
|
||||
const vgKey = game || curGame();
|
||||
selection.set((s) => {
|
||||
const stamp = new Date().toISOString();
|
||||
const pokemon = {};
|
||||
for (const [id, e] of Object.entries(s.pokemon)) {
|
||||
if (e.favorite || (e.note && e.note.trim())) {
|
||||
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 };
|
||||
const bucket = { ...(s.games[vgKey] || {}) };
|
||||
for (const id of seen) bucket[id] = { ...(bucket[id] || GBLANK), seen: true, updatedAt: stamp };
|
||||
for (const id of caught) bucket[id] = { ...(bucket[id] || GBLANK), seen: true, caught: true, updatedAt: stamp };
|
||||
return { ...s, games: { ...s.games, [vgKey]: bucket } };
|
||||
});
|
||||
}
|
||||
|
||||
/** Aggregate seen/caught counts over an arbitrary list of national ids. */
|
||||
export function stats(speciesIds) {
|
||||
const p = selection.get().pokemon;
|
||||
/** Set one game's bucket to exactly these lists (a "replace with this save" import). */
|
||||
export function replaceFlags({ seen = [], caught = [], game } = {}) {
|
||||
const vgKey = game || curGame();
|
||||
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 caught = 0;
|
||||
for (const id of speciesIds) {
|
||||
const e = p[id];
|
||||
if (!e) continue;
|
||||
const e = get(id);
|
||||
if (e.seen || e.caught) seen++;
|
||||
if (e.caught) caught++;
|
||||
}
|
||||
return { seen, caught, total: speciesIds.length };
|
||||
}
|
||||
|
||||
/** Caught/seen across every game — the true National Dex tally. */
|
||||
export function statsNational(speciesIds) {
|
||||
return stats(speciesIds, 'all');
|
||||
}
|
||||
|
||||
@ -112,45 +112,53 @@
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* Row of shortcut chips under a view header (Team → Natures / Type chart / …) */
|
||||
.toollinks {
|
||||
/* Team toolbar: primary mode switch on the left, reference shortcuts on the
|
||||
right. One band so the reference links read as part of the view's chrome
|
||||
rather than a strip pasted above it. */
|
||||
.teamview__bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: -6px 0 18px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.toollink {
|
||||
.teamview__bar .seg {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.teamview__refs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
.reflink {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 13px 7px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
font-size: 0.82rem;
|
||||
gap: 5px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease;
|
||||
transition: color 0.12s ease, background 0.12s ease;
|
||||
}
|
||||
.toollink:hover {
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 10%, var(--surface));
|
||||
.reflink:hover {
|
||||
color: var(--text);
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.toollink:active {
|
||||
.reflink:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
.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);
|
||||
.reflink__ico {
|
||||
color: var(--accent);
|
||||
font-size: 0.72rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.teamcompare__note {
|
||||
margin: 0 0 10px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.link {
|
||||
|
||||
@ -3,7 +3,7 @@ import { loadSnapshot } from '../data/snapshot.js';
|
||||
import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js';
|
||||
import { settings } from '../store/settings.js';
|
||||
import { ui } from '../store/ui.js';
|
||||
import { selection, stats } from '../store/selection.js';
|
||||
import { selection, stats, entry, gameEntry } from '../store/selection.js';
|
||||
import { Card } from '../components/Card.js';
|
||||
import { ProgressRing } from '../components/ProgressRing.js';
|
||||
import { Sprite } from '../components/Sprite.js';
|
||||
@ -56,7 +56,7 @@ function sortValue(row, key) {
|
||||
case 'weight': return sp.weight ?? 0;
|
||||
case 'baseExp': return sp.baseExp ?? 0;
|
||||
case 'catchrate': return sp.captureRate ?? 0;
|
||||
case 'caught': return selection.get().pokemon[sp.id]?.updatedAt || '';
|
||||
case 'caught': return gameEntry(sp.id).updatedAt || '';
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@ -404,17 +404,16 @@ export async function DexGrid() {
|
||||
);
|
||||
|
||||
function counts() {
|
||||
const p = selection.get().pokemon;
|
||||
let caught = 0;
|
||||
let favorite = 0;
|
||||
let legendary = 0;
|
||||
let notes = 0;
|
||||
for (const { species } of rows) {
|
||||
const e = p[species.id];
|
||||
if (e?.caught) caught++;
|
||||
if (e?.favorite) favorite++;
|
||||
const e = entry(species.id);
|
||||
if (e.caught) caught++;
|
||||
if (e.favorite) favorite++;
|
||||
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 };
|
||||
}
|
||||
@ -429,7 +428,6 @@ export async function DexGrid() {
|
||||
|
||||
function paintGrid() {
|
||||
const st = settings.get();
|
||||
const pokemonState = selection.get().pokemon;
|
||||
const gen = snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9;
|
||||
const boxed = st.spriteStyle === 'game' && gen <= 2;
|
||||
const filterTest = FILTERS.find((f) => f.key === filterKey).test;
|
||||
@ -450,8 +448,7 @@ export async function DexGrid() {
|
||||
if (filterEggGroup && !(species.eggGroups || []).includes(filterEggGroup)) return false;
|
||||
if (filterMinBst && (species.bst || 0) < filterMinBst) return false;
|
||||
if (filterFullyEvolved && !isFullyEvolved(species)) return false;
|
||||
const e = pokemonState[species.id] || { seen: false, caught: false, favorite: false };
|
||||
return filterTest(e, species);
|
||||
return filterTest(entry(species.id), species);
|
||||
});
|
||||
|
||||
const dir = sortDesc ? -1 : 1;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { el, clear, onTeardown } from '../lib/dom.js';
|
||||
import { loadSnapshot } from '../data/snapshot.js';
|
||||
import { settings } from '../store/settings.js';
|
||||
import { selection, stats } from '../store/selection.js';
|
||||
import { selection, stats, statsNational } from '../store/selection.js';
|
||||
import { ui } from '../store/ui.js';
|
||||
import { playedGames, isPlayed } from '../store/playedGames.js';
|
||||
import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js';
|
||||
@ -31,7 +31,7 @@ export async function ProgressView() {
|
||||
};
|
||||
|
||||
function gameRow(vg, { played }) {
|
||||
const s = stats(speciesIdsFor(vg.key));
|
||||
const s = stats(speciesIdsFor(vg.key), vg.key);
|
||||
return el(
|
||||
'button',
|
||||
{
|
||||
@ -56,8 +56,8 @@ export async function ProgressView() {
|
||||
const anyPlayed = gens.some(({ versionGroups }) => versionGroups.some((vg) => isPlayed(vg.key)));
|
||||
const showAll = ui.get().progShowAll || !anyPlayed;
|
||||
|
||||
// National total — always shown.
|
||||
const nat = stats(snap.species.map((s) => s.id));
|
||||
// National total — caught in any game.
|
||||
const nat = statsNational(snap.species.map((s) => s.id));
|
||||
list.append(
|
||||
el(
|
||||
'div',
|
||||
@ -120,7 +120,7 @@ export async function ProgressView() {
|
||||
'header',
|
||||
{ class: 'view__header' },
|
||||
el('h1', {}, 'Progress by game'),
|
||||
el('p', {}, 'Your one unified caught record, counted against each game’s dex. Games you haven’t played are tucked away. Tap a game to switch to it.'),
|
||||
el('p', {}, 'What you’ve caught in each game. Tap one to switch to it.'),
|
||||
),
|
||||
el(
|
||||
'p',
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { el, onTeardown } from '../lib/dom.js';
|
||||
import { settings, applyTheme } from '../store/settings.js';
|
||||
import { selection, importFlags, replaceFlags } from '../store/selection.js';
|
||||
import { selection, importFlags, replaceFlags, normalizeSelection } from '../store/selection.js';
|
||||
import { prettify } from '../data/pokedex-resolver.js';
|
||||
import { chooseDialog } from '../lib/dialog.js';
|
||||
import { team } from '../store/team.js';
|
||||
import { formTracking } from '../store/formTracking.js';
|
||||
@ -144,23 +145,30 @@ export async function SettingsView() {
|
||||
return;
|
||||
}
|
||||
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({
|
||||
title: `${dex.game}${dex.trainer ? ` · ${dex.trainer}` : ''}`,
|
||||
body: `${dex.seen.length} seen · ${dex.caught.length} caught in this save.`,
|
||||
choices: [
|
||||
{ key: 'merge', label: 'Add to my Pokédex' },
|
||||
{ key: 'replace', label: 'Replace mine with this', class: 'button--danger' },
|
||||
{ key: 'merge', label: `Add to ${prettify(target)}` },
|
||||
{ key: 'replace', label: `Replace ${prettify(target)} with this`, class: 'button--danger' },
|
||||
{ key: null, label: 'Cancel', class: 'button--ghost' },
|
||||
],
|
||||
});
|
||||
if (choice === 'merge') {
|
||||
importFlags({ seen: dex.seen, caught: dex.caught });
|
||||
markPlayed(...(dex.versionGroups || []));
|
||||
saveNote.textContent = `Added ${dex.caught.length} caught / ${dex.seen.length} seen from ${who}.`;
|
||||
importFlags({ seen: dex.seen, caught: dex.caught, game: target });
|
||||
markPlayed(...cands);
|
||||
saveNote.textContent = `Added ${dex.caught.length} caught / ${dex.seen.length} seen to ${prettify(target)}.`;
|
||||
} else if (choice === 'replace') {
|
||||
replaceFlags({ seen: dex.seen, caught: dex.caught });
|
||||
markPlayed(...(dex.versionGroups || []));
|
||||
saveNote.textContent = `Replaced tracking with ${who}'s Pokédex (favourites and notes kept).`;
|
||||
replaceFlags({ seen: dex.seen, caught: dex.caught, game: target });
|
||||
markPlayed(...cands);
|
||||
saveNote.textContent = `Set ${prettify(target)}'s Pokédex from ${who}.`;
|
||||
} else {
|
||||
saveNote.textContent = '';
|
||||
}
|
||||
@ -178,7 +186,7 @@ export async function SettingsView() {
|
||||
try {
|
||||
const data = JSON.parse(await file.text());
|
||||
if (data.settings) settings.replace(data.settings);
|
||||
if (data.selection) selection.replace(data.selection);
|
||||
if (data.selection) selection.replace(normalizeSelection(data.selection));
|
||||
if (data.team) team.replace(data.team);
|
||||
if (data.formTracking) formTracking.replace(data.formTracking);
|
||||
if (data.shinyHunts) shinyHunts.replace(data.shinyHunts);
|
||||
@ -268,7 +276,7 @@ export async function SettingsView() {
|
||||
type: 'button',
|
||||
onclick: () => {
|
||||
if (confirm('Clear all seen/caught/favorite tracking? This cannot be undone.')) {
|
||||
selection.replace({ version: 2, pokemon: {} });
|
||||
selection.replace({ version: 3, pokemon: {}, games: {} });
|
||||
}
|
||||
},
|
||||
}, 'Clear tracking data'),
|
||||
|
||||
@ -67,16 +67,19 @@ export async function TeamView() {
|
||||
{ class: 'view__header' },
|
||||
el('h1', {}, 'Team'),
|
||||
el('p', {}, `Up to ${MAX_TEAM} Pokémon — check weaknesses, coverage and stats.`),
|
||||
),
|
||||
el(
|
||||
'div',
|
||||
{ class: 'teamview__bar' },
|
||||
seg,
|
||||
el(
|
||||
'nav',
|
||||
{ class: 'toollinks' },
|
||||
el('a', { class: 'toollink', href: '#/natures' }, el('span', { class: 'toollink__ico', 'aria-hidden': 'true' }, '✦'), 'Natures'),
|
||||
el('a', { class: 'toollink', href: '#/types' }, el('span', { class: 'toollink__ico', 'aria-hidden': 'true' }, '▦'), 'Type chart'),
|
||||
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'),
|
||||
{ class: 'teamview__refs', 'aria-label': 'Reference tools' },
|
||||
el('a', { class: 'reflink', href: '#/natures' }, el('span', { class: 'reflink__ico', 'aria-hidden': 'true' }, '✦'), 'Natures'),
|
||||
el('a', { class: 'reflink', href: '#/types' }, el('span', { class: 'reflink__ico', 'aria-hidden': 'true' }, '▦'), 'Type chart'),
|
||||
el('a', { class: 'reflink', href: '#/breeding' }, el('span', { class: 'reflink__ico', 'aria-hidden': 'true' }, '⬡'), 'Breeding'),
|
||||
),
|
||||
),
|
||||
seg,
|
||||
lineup,
|
||||
body,
|
||||
);
|
||||
@ -355,7 +358,17 @@ export async function TeamView() {
|
||||
function compare(mem) {
|
||||
const vg = snap.versionGroupByKey.get(settings.get().versionGroup);
|
||||
const gen = vg ? vg.generation : 9;
|
||||
return CompareTable(mem, { gen, style: settings.get().spriteStyle });
|
||||
return el(
|
||||
'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 ------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user