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
|
filter drawer: type, generation, ability, egg group, minimum BST, and
|
||||||
"fully evolved only" (derived client-side from each species' evolves-from
|
"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;
|
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,
|
- **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
|
where you caught it, anything), autosaving as you type and flushing
|
||||||
immediately on blur or navigation so a quick tab-away never drops a
|
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 /
|
offer (toast + Settings button), manifest shortcuts to Team / Search /
|
||||||
Type chart.
|
Type chart.
|
||||||
|
|
||||||
Not yet done: version-exclusive badges, an interactive region map (PokéAPI
|
Not yet done: an interactive region map (PokéAPI has no map imagery or
|
||||||
has no map imagery or coordinates), and a formal Lighthouse pass.
|
coordinates), and a formal Lighthouse pass.
|
||||||
|
|
||||||
## Notes
|
## 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 -------------------------------------------
|
// ---- Moves index -------------------------------------------
|
||||||
const moveIndex = await api('move?limit=100000');
|
const moveIndex = await api('move?limit=100000');
|
||||||
console.log(`Fetching ${moveIndex.results.length} moves …`);
|
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 { typesForGen } from '../lib/type-resolve.js';
|
||||||
import { prefersReducedMotion } from '../store/settings.js';
|
import { prefersReducedMotion } from '../store/settings.js';
|
||||||
import { buzz } from '../lib/haptics.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
|
* 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);
|
const n = (species.forms?.length || 0) + (species.cosmeticForms?.length || 0);
|
||||||
return n ? el('span', { class: 'card__forms' }, `+${n} form${n > 1 ? 's' : ''}`) : null;
|
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,
|
metric ? el('span', { class: 'card__metric' }, metric) : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2912,6 +2912,18 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: color-mix(in srgb, var(--type-main) 55%, var(--text-dim));
|
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 ---------------------------------------- */
|
/* ---- Type chart page ---------------------------------------- */
|
||||||
.tchart__scroll {
|
.tchart__scroll {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user