diff --git a/src/components/Card.js b/src/components/Card.js index d08940a..8a71245 100644 --- a/src/components/Card.js +++ b/src/components/Card.js @@ -9,10 +9,12 @@ import { typeHex } from '../lib/type-color.js'; * gradient, a type-coloured spotlight behind an oversized sprite that lifts * above the card, a number chip, and a big ghost number in the corner. */ -export function Card(species, number, { spriteStyle = 'official', versionGroup } = {}) { +export function Card(species, number, { spriteStyle = 'official', versionGroup, boxed = false } = {}) { const state = entry(species.id); const mainType = (species.types || [])[0] || 'normal'; const num = String(number ?? species.id).padStart(3, '0'); + const data = { type: mainType, sprite: spriteStyle }; + if (boxed) data.boxed = '1'; const caughtBtn = el('button', { class: 'card__caught', @@ -34,7 +36,7 @@ export function Card(species, number, { spriteStyle = 'official', versionGroup } { class: `card${state.caught ? ' is-caught' : ''}`, href: `#/pokemon/${species.id}`, - dataset: { type: mainType, sprite: spriteStyle }, + dataset: data, style: `--type-main:${typeHex(mainType)}`, }, el('span', { class: 'card__ghost', 'aria-hidden': 'true' }, num), diff --git a/src/data/type-chart.js b/src/data/type-chart.js index 37b4317..21495b8 100644 --- a/src/data/type-chart.js +++ b/src/data/type-chart.js @@ -70,7 +70,10 @@ export function multiplier(attacking, defTypes, gen = 9) { */ export function defensiveMatchups(defTypes, gen = 9) { const buckets = { '4': [], '2': [], '0.5': [], '0.25': [], '0': [] }; - const atkTypes = gen < 6 ? TYPES.filter((t) => t !== 'fairy') : TYPES; + // Types that didn't exist yet can't be attacking: Steel/Dark before Gen 2, + // Fairy before Gen 6. + const absent = new Set(gen < 2 ? ['steel', 'dark', 'fairy'] : gen < 6 ? ['fairy'] : []); + const atkTypes = TYPES.filter((t) => !absent.has(t)); for (const atk of atkTypes) { const m = multiplier(atk, defTypes, gen); if (m === 1) continue; diff --git a/src/styles/layout.css b/src/styles/layout.css index f829eaf..c12234e 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -450,6 +450,23 @@ .card[data-sprite="game"] .card__art .sprite { image-rendering: pixelated; } +.card[data-sprite="game"] .card__art { + width: 96px; + height: 96px; +} +/* Gen 1–2 game sprites ship with an opaque white background — show them as + a small framed tile rather than a box that swallows the card. */ +.card[data-boxed] .card__art { + width: 78px; + height: 78px; + padding: 5px; + border-radius: 12px; + background: #f7f7f5; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.14); +} +.card[data-boxed] .card__art .sprite { + filter: none; +} .card__body { position: relative; z-index: 1; @@ -703,6 +720,18 @@ height: 100%; object-fit: contain; } +.phero__art--boxed { + width: min(180px, 46vw); + height: min(180px, 46vw); + padding: 14px; + border-radius: 18px; + background: #f7f7f5; + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22), inset 0 0 0 1px rgba(0, 0, 0, 0.08); +} +.phero__art--boxed img { + image-rendering: pixelated; + filter: none; +} @keyframes pop { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } diff --git a/src/views/DexGrid.js b/src/views/DexGrid.js index 55b740f..817c1a4 100644 --- a/src/views/DexGrid.js +++ b/src/views/DexGrid.js @@ -113,6 +113,10 @@ export async function DexGrid() { function paintGrid() { const st = settings.get(); const pokemonState = selection.get().pokemon; + // Gen 1–2 game-era sprites have opaque white backgrounds — frame them. + const boxed = + st.spriteStyle === 'game' && + (snap.versionGroupByKey.get(st.versionGroup)?.generation ?? 9) <= 2; const filterTest = FILTERS.find((f) => f.key === filterKey).test; const frag = document.createDocumentFragment(); let shown = 0; @@ -131,6 +135,7 @@ export async function DexGrid() { const card = Card(species, number, { spriteStyle: st.spriteStyle, versionGroup: st.versionGroup, + boxed, }); if (shown < 30) card.style.setProperty('--i', String(shown)); frag.append(card); diff --git a/src/views/PokemonDetail.js b/src/views/PokemonDetail.js index 95bb4b0..146068e 100644 --- a/src/views/PokemonDetail.js +++ b/src/views/PokemonDetail.js @@ -15,6 +15,16 @@ import { typeHex } from '../lib/type-color.js'; const idFromUrl = (u) => Number(u.replace(/\/$/, '').split('/').pop()); +/** A Pokémon's typing as it was in the selected game's generation. */ +function typesForGeneration(pokemon, gen) { + const past = (pokemon.past_types || []) + .map((p) => ({ types: p.types, _gen: idFromUrl(p.generation.url) })) + .sort((a, b) => a._gen - b._gen); + const era = past.find((p) => p._gen >= gen); + const list = era ? era.types : pokemon.types; + return list.slice().sort((a, b) => a.slot - b.slot).map((t) => t.type.name); +} + const STAT_NAMES = [ 'hp', 'attack', @@ -68,10 +78,7 @@ export async function PokemonDetail(nationalId) { return view; } - const types = pokemon.types - .slice() - .sort((a, b) => a.slot - b.slot) - .map((t) => t.type.name); + const types = typesForGeneration(pokemon, vgGen); const mainType = types[0]; view.style.setProperty('--type-main', typeHex(mainType)); view.style.setProperty('--type-2', typeHex(types[1] || mainType)); @@ -81,8 +88,11 @@ export async function PokemonDetail(nationalId) { // Shiny Pokémon were introduced in Gen 2 — no toggle for Gen 1 games. const shinyAvailable = vgGen >= 2; const artStyle = st.spriteStyle === 'default' ? 'official' : st.spriteStyle; + const boxedArt = artStyle === 'game' && vgGen <= 2; let shiny = shinyAvailable && st.showShiny; - const artHolder = el('div', { class: 'phero__art' }); + const artHolder = el('div', { + class: `phero__art${boxedArt ? ' phero__art--boxed' : ''}`, + }); const shinyBtn = shinyAvailable ? el('button', { class: 'phero__shiny',