Version-exclusive badges on dex cards
build-snapshot: a new pass fetches /pokemon/:id/encounters for every species and records, per game, the versions it has wild encounters in — when that's a non-empty strict subset of the game's versions, species.exclusiveIn[vg] holds them. Wild-only, so gift/trade/evolution exclusives are missed. Card: with a specific game selected, shows a "<Version> only" pill (e.g. Seedot -> "Ruby only", Lotad -> "Sapphire only"). 220 species flagged across all games; nothing shown in National mode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
parent
7f12f08718
commit
2e4d9ab329
@ -75,7 +75,10 @@ Working app with a type-themed UI:
|
||||
filter drawer: type, generation, ability, egg group, minimum BST, and
|
||||
"fully evolved only" (derived client-side from each species' evolves-from
|
||||
link — no extra fetches). A "Notes" chip filters to Pokémon with a note;
|
||||
cards with one get a small 📝 mark.
|
||||
cards with one get a small 📝 mark. When a specific game is selected,
|
||||
cards show a version-exclusive badge ("Ruby only") — derived from the
|
||||
snapshot's baked wild-encounter data, so gift/trade-only exclusives are
|
||||
missed.
|
||||
- **Notes** — a free-text note per Pokémon on its detail page (trade plans,
|
||||
where you caught it, anything), autosaving as you type and flushing
|
||||
immediately on blur or navigation so a quick tab-away never drops a
|
||||
@ -135,8 +138,8 @@ Working app with a type-themed UI:
|
||||
offer (toast + Settings button), manifest shortcuts to Team / Search /
|
||||
Type chart.
|
||||
|
||||
Not yet done: version-exclusive badges, an interactive region map (PokéAPI
|
||||
has no map imagery or coordinates), and a formal Lighthouse pass.
|
||||
Not yet done: an interactive region map (PokéAPI has no map imagery or
|
||||
coordinates), and a formal Lighthouse pass.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@ -320,6 +320,35 @@ async function main() {
|
||||
};
|
||||
});
|
||||
|
||||
// ---- Version exclusivity (from wild encounters) ---------------
|
||||
// For each game, the versions a species has wild encounters in. If that's
|
||||
// a non-empty strict subset of the game's versions, it's version
|
||||
// exclusive there. Wild-only — gift/trade/evolution exclusives are missed.
|
||||
const versionToVg = {};
|
||||
for (const vg of versionGroups) for (const v of vg.versions) versionToVg[v] = vg.key;
|
||||
const vgVersionCount = Object.fromEntries(versionGroups.map((vg) => [vg.key, vg.versions.length]));
|
||||
console.log(`Fetching wild encounters for ${species.length} species …`);
|
||||
await mapLimit(species, CONCURRENCY, async (sp) => {
|
||||
let enc;
|
||||
try {
|
||||
enc = await api(`pokemon/${sp.id}/encounters`);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const byVg = {};
|
||||
for (const area of enc) {
|
||||
for (const vd of area.version_details || []) {
|
||||
const vg = versionToVg[vd.version.name];
|
||||
if (vg) (byVg[vg] ||= new Set()).add(vd.version.name);
|
||||
}
|
||||
}
|
||||
const excl = {};
|
||||
for (const [vg, set] of Object.entries(byVg)) {
|
||||
if (set.size > 0 && set.size < (vgVersionCount[vg] || 1)) excl[vg] = [...set];
|
||||
}
|
||||
if (Object.keys(excl).length) sp.exclusiveIn = excl;
|
||||
});
|
||||
|
||||
// ---- Moves index -------------------------------------------
|
||||
const moveIndex = await api('move?limit=100000');
|
||||
console.log(`Fetching ${moveIndex.results.length} moves …`);
|
||||
|
||||
@ -6,6 +6,7 @@ import { typeHex } from '../lib/type-color.js';
|
||||
import { typesForGen } from '../lib/type-resolve.js';
|
||||
import { prefersReducedMotion } from '../store/settings.js';
|
||||
import { buzz } from '../lib/haptics.js';
|
||||
import { prettify } from '../data/pokedex-resolver.js';
|
||||
|
||||
/**
|
||||
* One Pokémon in the dex feed. Tinted by its primary type: a soft top-down
|
||||
@ -73,6 +74,12 @@ export function Card(species, number, { spriteStyle = 'official', versionGroup,
|
||||
const n = (species.forms?.length || 0) + (species.cosmeticForms?.length || 0);
|
||||
return n ? el('span', { class: 'card__forms' }, `+${n} form${n > 1 ? 's' : ''}`) : null;
|
||||
})(),
|
||||
(() => {
|
||||
const ex = versionGroup && versionGroup !== 'all' && species.exclusiveIn?.[versionGroup];
|
||||
return ex
|
||||
? el('span', { class: 'card__excl', title: 'Version exclusive (wild)' }, `${ex.map(prettify).join(' & ')} only`)
|
||||
: null;
|
||||
})(),
|
||||
metric ? el('span', { class: 'card__metric' }, metric) : null,
|
||||
),
|
||||
);
|
||||
|
||||
@ -2912,6 +2912,18 @@
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--type-main) 55%, var(--text-dim));
|
||||
}
|
||||
.card__excl {
|
||||
align-self: flex-start;
|
||||
margin-top: 4px;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--type-main) 22%, var(--surface-2));
|
||||
color: color-mix(in srgb, var(--type-main) 70%, var(--ink));
|
||||
}
|
||||
|
||||
/* ---- Type chart page ---------------------------------------- */
|
||||
.tchart__scroll {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user