Modernise the dex feed; gate shiny to Gen 2+
- Feed header: large title, tappable game pill, circular progress ring, icon-in-field search, filter chips with live counts (All/Caught/Missing/Favorites) - Cards: left-aligned info, number chip, corner ghost number (no longer hidden behind the sprite), top-down type gradient, staggered entrance animation; caught toggle moved to top-left - Detail: hide the shiny toggle for Gen 1 games (shinies are Gen 2+); add Category (genus) to the About tab Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b4f2460341
commit
9216a53932
@ -5,14 +5,14 @@ import { entry, toggle } from '../store/selection.js';
|
||||
import { typeHex } from '../lib/type-color.js';
|
||||
|
||||
/**
|
||||
* One Pokémon in the dex grid. Tinted by its primary type: a soft gradient,
|
||||
* a type-coloured spotlight behind the sprite, and a big translucent number.
|
||||
* The card is a link to the detail route; the caught toggle is a real button
|
||||
* layered on top that updates itself so toggling never repaints the grid.
|
||||
* One Pokémon in the dex feed. Tinted by its primary type: a soft top-down
|
||||
* 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 } = {}) {
|
||||
const state = entry(species.id);
|
||||
const mainType = (species.types || [])[0] || 'normal';
|
||||
const num = String(number ?? species.id).padStart(3, '0');
|
||||
|
||||
const caughtBtn = el('button', {
|
||||
class: 'card__caught',
|
||||
@ -37,13 +37,21 @@ export function Card(species, number, { spriteStyle = 'official', versionGroup }
|
||||
dataset: { type: mainType, sprite: spriteStyle },
|
||||
style: `--type-main:${typeHex(mainType)}`,
|
||||
},
|
||||
el('span', { class: 'card__bignum' }, String(number ?? species.id).padStart(3, '0')),
|
||||
el('span', { class: 'card__ghost', 'aria-hidden': 'true' }, num),
|
||||
el('span', { class: 'card__spot', 'aria-hidden': 'true' }),
|
||||
Sprite(species.id, { style: spriteStyle, versionGroup, alt: species.name, size: 112 }),
|
||||
el('span', { class: 'card__num' }, `#${String(number ?? species.id).padStart(4, '0')}`),
|
||||
el('span', { class: 'card__name' }, species.name.replace(/-/g, ' ')),
|
||||
el('span', { class: 'card__types' }, ...(species.types || []).map(TypeChip)),
|
||||
el(
|
||||
'div',
|
||||
{ class: 'card__art' },
|
||||
Sprite(species.id, { style: spriteStyle, versionGroup, alt: species.name, size: 116 }),
|
||||
),
|
||||
caughtBtn,
|
||||
el(
|
||||
'div',
|
||||
{ class: 'card__body' },
|
||||
el('span', { class: 'card__num' }, `#${num}`),
|
||||
el('span', { class: 'card__name' }, species.name.replace(/-/g, ' ')),
|
||||
el('span', { class: 'card__types' }, ...(species.types || []).map(TypeChip)),
|
||||
),
|
||||
);
|
||||
|
||||
return card;
|
||||
|
||||
48
src/components/ProgressRing.js
Normal file
48
src/components/ProgressRing.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { el } from '../lib/dom.js';
|
||||
|
||||
const NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
function circle(r, cls) {
|
||||
const c = document.createElementNS(NS, 'circle');
|
||||
c.setAttribute('cx', '26');
|
||||
c.setAttribute('cy', '26');
|
||||
c.setAttribute('r', String(r));
|
||||
c.setAttribute('class', cls);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact circular progress used in the dex feed header. Returns
|
||||
* { node, update } — update({ caught, seen, total }) re-draws the arcs and
|
||||
* the centre label without rebuilding.
|
||||
*/
|
||||
export function ProgressRing() {
|
||||
const R = 22;
|
||||
const CIRC = 2 * Math.PI * R;
|
||||
|
||||
const track = circle(R, 'ring__track');
|
||||
const seenArc = circle(R, 'ring__seen');
|
||||
const caughtArc = circle(R, 'ring__caught');
|
||||
for (const arc of [seenArc, caughtArc]) {
|
||||
arc.setAttribute('stroke-dasharray', String(CIRC));
|
||||
arc.setAttribute('stroke-dashoffset', String(CIRC));
|
||||
arc.setAttribute('transform', 'rotate(-90 26 26)');
|
||||
}
|
||||
|
||||
const svg = document.createElementNS(NS, 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 52 52');
|
||||
svg.setAttribute('class', 'ring__svg');
|
||||
svg.append(track, seenArc, caughtArc);
|
||||
|
||||
const value = el('span', { class: 'ring__value' });
|
||||
const node = el('div', { class: 'ring' }, svg, el('div', { class: 'ring__label' }, value));
|
||||
|
||||
function update({ caught = 0, seen = 0, total = 1 }) {
|
||||
const safeTotal = Math.max(total, 1);
|
||||
caughtArc.setAttribute('stroke-dashoffset', String(CIRC * (1 - caught / safeTotal)));
|
||||
seenArc.setAttribute('stroke-dashoffset', String(CIRC * (1 - Math.max(seen, caught) / safeTotal)));
|
||||
value.replaceChildren(String(caught), el('small', {}, `/${total}`));
|
||||
}
|
||||
|
||||
return { node, update };
|
||||
}
|
||||
@ -196,66 +196,162 @@
|
||||
.type-chip[data-type="steel"] { background: var(--type-steel); }
|
||||
.type-chip[data-type="fairy"] { background: var(--type-fairy); color: #3d1f3b; }
|
||||
|
||||
/* ---------- Dex grid ---------------------------------------------- */
|
||||
.progress-header {
|
||||
/* ---------- Dex feed header ------------------------------------- */
|
||||
.feed-head {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--bg);
|
||||
padding: 8px 0 12px;
|
||||
margin: -8px 0 18px;
|
||||
padding: 14px 0 12px;
|
||||
background: color-mix(in srgb, var(--bg) 82%, transparent);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
.progress-header__titles h1 {
|
||||
.feed-head__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
.feed-head__id h1 {
|
||||
margin: 0;
|
||||
font-size: 1.7rem;
|
||||
font-size: 1.9rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.progress-header__sub,
|
||||
.progress-header__counts {
|
||||
margin: 2px 0 0;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.progress-header__dot {
|
||||
margin: 0 6px;
|
||||
}
|
||||
.progress-header__track {
|
||||
margin-top: 12px;
|
||||
height: 8px;
|
||||
.game-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
padding: 5px 8px 5px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-2);
|
||||
overflow: hidden;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.progress-header__bar {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--good), color-mix(in srgb, var(--good) 60%, #74c0fc));
|
||||
transition: width 0.4s var(--ease-spring);
|
||||
.game-pill:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
.game-pill__chev {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dexgrid__controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 8px 0 16px;
|
||||
.ring {
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
flex: none;
|
||||
}
|
||||
.dexgrid__search,
|
||||
.ring__svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ring__track {
|
||||
fill: none;
|
||||
stroke: var(--surface-2);
|
||||
stroke-width: 5;
|
||||
}
|
||||
.ring__seen {
|
||||
fill: none;
|
||||
stroke: color-mix(in srgb, var(--good) 40%, var(--surface-2));
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.5s var(--ease-spring);
|
||||
}
|
||||
.ring__caught {
|
||||
fill: none;
|
||||
stroke: var(--good);
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.5s var(--ease-spring);
|
||||
}
|
||||
.ring__label {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.ring__value small {
|
||||
font-size: 0.6rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.field-search {
|
||||
position: relative;
|
||||
margin: 14px 0 12px;
|
||||
}
|
||||
.field-search__icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-dim);
|
||||
display: flex;
|
||||
}
|
||||
.field-search__input,
|
||||
.search__input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
padding: 12px 16px 12px 44px;
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
.dexgrid__search:focus,
|
||||
.search__input {
|
||||
padding-left: 16px;
|
||||
}
|
||||
.field-search__input:focus,
|
||||
.search__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.feed-chips {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.feed-chips::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.feed-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: none;
|
||||
appearance: none;
|
||||
border: 1.5px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--text-dim);
|
||||
padding: 7px 14px;
|
||||
border-radius: 999px;
|
||||
font: inherit;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.feed-chip.is-active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-text);
|
||||
}
|
||||
.feed-chip__count {
|
||||
font-size: 0.72rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
|
||||
@ -283,92 +379,109 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 16px 12px 14px;
|
||||
padding: 16px 14px 14px;
|
||||
border-radius: var(--radius);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
165deg,
|
||||
color-mix(in srgb, var(--type-main) 26%, var(--surface)) 0%,
|
||||
var(--surface) 60%
|
||||
180deg,
|
||||
color-mix(in srgb, var(--type-main) 30%, var(--surface)) 0%,
|
||||
color-mix(in srgb, var(--type-main) 9%, var(--surface)) 52%,
|
||||
var(--surface) 100%
|
||||
);
|
||||
border: 1px solid color-mix(in srgb, var(--type-main) 22%, var(--border));
|
||||
border: 1px solid color-mix(in srgb, var(--type-main) 20%, var(--border));
|
||||
box-shadow: var(--shadow);
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 232px;
|
||||
contain-intrinsic-size: auto 236px;
|
||||
transition: transform 0.14s var(--ease-spring), box-shadow 0.14s ease;
|
||||
animation: cardIn 0.4s var(--ease-spring) backwards;
|
||||
animation-delay: calc(var(--i, 0) * 18ms);
|
||||
}
|
||||
.card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@keyframes cardIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.card__ghost {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: 6px;
|
||||
z-index: 0;
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.03em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: color-mix(in srgb, var(--type-main) 34%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
.card__spot {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
top: 14px;
|
||||
left: 50%;
|
||||
width: 118px;
|
||||
height: 118px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
color-mix(in srgb, var(--type-main) 42%, transparent) 0%,
|
||||
transparent 68%
|
||||
color-mix(in srgb, var(--type-main) 46%, transparent) 0%,
|
||||
transparent 66%
|
||||
);
|
||||
}
|
||||
.card__bignum {
|
||||
position: absolute;
|
||||
top: 74px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 0;
|
||||
font-size: 4.2rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: color-mix(in srgb, var(--type-main) 24%, transparent);
|
||||
pointer-events: none;
|
||||
.card__art {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
.card .sprite {
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
.card__art .sprite {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.22));
|
||||
filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.28));
|
||||
}
|
||||
.card[data-sprite="default"] .sprite,
|
||||
.card[data-sprite="game"] .sprite {
|
||||
.card[data-sprite="default"] .card__art .sprite,
|
||||
.card[data-sprite="game"] .card__art .sprite {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.card__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-self: stretch;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.card__num {
|
||||
font-size: 0.72rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-dim);
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.card__name {
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.98rem;
|
||||
text-transform: capitalize;
|
||||
text-align: center;
|
||||
line-height: 1.15;
|
||||
}
|
||||
.card__types {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.card__caught {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
left: 10px;
|
||||
z-index: 3;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
@ -378,7 +491,7 @@
|
||||
place-items: center;
|
||||
font-size: 0.8rem;
|
||||
color: #333;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
.card__caught[aria-pressed="true"] {
|
||||
@ -394,6 +507,11 @@
|
||||
.card.is-caught {
|
||||
border-color: color-mix(in srgb, var(--good) 45%, var(--border));
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.card {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Game picker ------------------------------------------- */
|
||||
.gamepicker__subdex {
|
||||
|
||||
@ -4,15 +4,18 @@ import { resolvePokedex, dexRows, prettify } from '../data/pokedex-resolver.js';
|
||||
import { settings } from '../store/settings.js';
|
||||
import { selection, stats } from '../store/selection.js';
|
||||
import { Card } from '../components/Card.js';
|
||||
import { ProgressHeader } from '../components/ProgressHeader.js';
|
||||
import { ProgressRing } from '../components/ProgressRing.js';
|
||||
|
||||
const FILTERS = [
|
||||
{ key: 'all', label: 'All', test: () => true },
|
||||
{ key: 'caught', label: 'Caught', test: (e) => e.caught },
|
||||
{ key: 'uncaught', label: 'Not caught', test: (e) => !e.caught },
|
||||
{ key: 'missing', label: 'Missing', test: (e) => !e.caught },
|
||||
{ key: 'favorite', label: 'Favorites', test: (e) => e.favorite },
|
||||
];
|
||||
|
||||
const SEARCH_ICON =
|
||||
'<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>';
|
||||
|
||||
export async function DexGrid() {
|
||||
const view = el('section', { class: 'view dexgrid' });
|
||||
const snap = await loadSnapshot();
|
||||
@ -20,9 +23,21 @@ export async function DexGrid() {
|
||||
const grid = el('div', { class: 'grid' });
|
||||
let query = '';
|
||||
let filterKey = 'all';
|
||||
let rows = [];
|
||||
let ids = [];
|
||||
|
||||
const ring = ProgressRing();
|
||||
|
||||
const title = el('h1', {});
|
||||
const gamePill = el(
|
||||
'a',
|
||||
{ class: 'game-pill', href: '#/games' },
|
||||
el('span', {}),
|
||||
el('span', { class: 'game-pill__chev', 'aria-hidden': 'true' }, '⌄'),
|
||||
);
|
||||
|
||||
const search = el('input', {
|
||||
class: 'dexgrid__search',
|
||||
class: 'field-search__input',
|
||||
type: 'search',
|
||||
placeholder: 'Filter this Pokédex…',
|
||||
oninput: (e) => {
|
||||
@ -31,37 +46,69 @@ export async function DexGrid() {
|
||||
},
|
||||
});
|
||||
|
||||
const filterBar = el(
|
||||
'div',
|
||||
{ class: 'chips' },
|
||||
...FILTERS.map((f) =>
|
||||
el(
|
||||
'button',
|
||||
{
|
||||
class: `chip${f.key === filterKey ? ' is-active' : ''}`,
|
||||
type: 'button',
|
||||
onclick: () => {
|
||||
filterKey = f.key;
|
||||
[...filterBar.children].forEach((c, i) =>
|
||||
c.classList.toggle('is-active', FILTERS[i].key === filterKey),
|
||||
);
|
||||
paintGrid();
|
||||
},
|
||||
const filterBar = el('div', { class: 'feed-chips' });
|
||||
const chipEls = FILTERS.map((f) => {
|
||||
const count = el('span', { class: 'feed-chip__count' });
|
||||
const btn = el(
|
||||
'button',
|
||||
{
|
||||
class: 'feed-chip',
|
||||
type: 'button',
|
||||
onclick: () => {
|
||||
filterKey = f.key;
|
||||
chipEls.forEach((c, i) => c.btn.classList.toggle('is-active', FILTERS[i].key === f.key));
|
||||
paintGrid();
|
||||
},
|
||||
f.label,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
el('span', {}, f.label),
|
||||
count,
|
||||
);
|
||||
filterBar.append(btn);
|
||||
return { btn, count };
|
||||
});
|
||||
chipEls[0].btn.classList.add('is-active');
|
||||
|
||||
const header = ProgressHeader({ title: '', subtitle: '' });
|
||||
view.append(
|
||||
header.node,
|
||||
el('div', { class: 'dexgrid__controls' }, search, filterBar),
|
||||
el(
|
||||
'header',
|
||||
{ class: 'feed-head' },
|
||||
el(
|
||||
'div',
|
||||
{ class: 'feed-head__top' },
|
||||
el('div', { class: 'feed-head__id' }, title, gamePill),
|
||||
ring.node,
|
||||
),
|
||||
el(
|
||||
'div',
|
||||
{ class: 'field-search' },
|
||||
el('span', { class: 'field-search__icon', html: SEARCH_ICON, 'aria-hidden': 'true' }),
|
||||
search,
|
||||
),
|
||||
filterBar,
|
||||
),
|
||||
grid,
|
||||
);
|
||||
|
||||
let rows = [];
|
||||
let ids = [];
|
||||
function counts() {
|
||||
const p = selection.get().pokemon;
|
||||
let caught = 0;
|
||||
let favorite = 0;
|
||||
for (const id of ids) {
|
||||
const e = p[id];
|
||||
if (!e) continue;
|
||||
if (e.caught) caught++;
|
||||
if (e.favorite) favorite++;
|
||||
}
|
||||
return { all: ids.length, caught, missing: ids.length - caught, favorite };
|
||||
}
|
||||
|
||||
function refreshMeta() {
|
||||
ring.update(stats(ids));
|
||||
const c = counts();
|
||||
chipEls.forEach(({ count }, i) => {
|
||||
count.textContent = c[FILTERS[i].key];
|
||||
});
|
||||
}
|
||||
|
||||
function paintGrid() {
|
||||
const st = settings.get();
|
||||
@ -79,27 +126,23 @@ export async function DexGrid() {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const e = pokemonState[species.id] || {
|
||||
seen: false,
|
||||
caught: false,
|
||||
favorite: false,
|
||||
};
|
||||
const e = pokemonState[species.id] || { seen: false, caught: false, favorite: false };
|
||||
if (!filterTest(e)) continue;
|
||||
frag.append(
|
||||
Card(species, number, {
|
||||
spriteStyle: st.spriteStyle,
|
||||
versionGroup: st.versionGroup,
|
||||
}),
|
||||
);
|
||||
const card = Card(species, number, {
|
||||
spriteStyle: st.spriteStyle,
|
||||
versionGroup: st.versionGroup,
|
||||
});
|
||||
if (shown < 30) card.style.setProperty('--i', String(shown));
|
||||
frag.append(card);
|
||||
shown++;
|
||||
}
|
||||
|
||||
clear(grid);
|
||||
if (shown === 0) {
|
||||
grid.append(el('p', { class: 'grid__empty' }, 'Nothing matches those filters.'));
|
||||
} else {
|
||||
grid.append(frag);
|
||||
}
|
||||
grid.append(
|
||||
shown === 0
|
||||
? el('p', { class: 'grid__empty' }, 'Nothing matches those filters.')
|
||||
: frag,
|
||||
);
|
||||
}
|
||||
|
||||
function rebuild() {
|
||||
@ -108,28 +151,19 @@ export async function DexGrid() {
|
||||
rows = dexRows(snap, dex);
|
||||
ids = rows.map((r) => r.species.id);
|
||||
|
||||
header.setTitles(
|
||||
prettify(dex.name || dex.key),
|
||||
el(
|
||||
'span',
|
||||
{},
|
||||
`${prettify(st.versionGroup)} · `,
|
||||
el('a', { href: '#/games', class: 'link' }, 'change game'),
|
||||
),
|
||||
);
|
||||
header.update(stats(ids));
|
||||
title.textContent = prettify(dex.name || dex.key);
|
||||
gamePill.firstChild.textContent = prettify(st.versionGroup);
|
||||
refreshMeta();
|
||||
paintGrid();
|
||||
}
|
||||
|
||||
rebuild();
|
||||
|
||||
const offSettings = settings.subscribe(rebuild);
|
||||
// Selection changes only affect counts + which cards pass the filter.
|
||||
const offSelection = selection.subscribe(() => {
|
||||
header.update(stats(ids));
|
||||
refreshMeta();
|
||||
if (filterKey !== 'all') paintGrid();
|
||||
});
|
||||
|
||||
onTeardown(view, () => {
|
||||
offSettings();
|
||||
offSelection();
|
||||
|
||||
@ -77,19 +77,23 @@ export async function PokemonDetail(nationalId) {
|
||||
view.style.setProperty('--type-2', typeHex(types[1] || mainType));
|
||||
view.dataset.type = mainType;
|
||||
|
||||
// ---- Artwork + shiny ----------------------------------------------
|
||||
// ---- Artwork + shiny --------------------------------------------
|
||||
// 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;
|
||||
let shiny = st.showShiny;
|
||||
let shiny = shinyAvailable && st.showShiny;
|
||||
const artHolder = el('div', { class: 'phero__art' });
|
||||
const shinyBtn = el('button', {
|
||||
class: 'phero__shiny',
|
||||
type: 'button',
|
||||
title: 'Toggle shiny',
|
||||
onclick: () => {
|
||||
shiny = !shiny;
|
||||
paintArt();
|
||||
},
|
||||
});
|
||||
const shinyBtn = shinyAvailable
|
||||
? el('button', {
|
||||
class: 'phero__shiny',
|
||||
type: 'button',
|
||||
title: 'Toggle shiny',
|
||||
onclick: () => {
|
||||
shiny = !shiny;
|
||||
paintArt();
|
||||
},
|
||||
})
|
||||
: null;
|
||||
function paintArt() {
|
||||
artHolder.replaceChildren(
|
||||
Sprite(nationalId, {
|
||||
@ -100,8 +104,10 @@ export async function PokemonDetail(nationalId) {
|
||||
size: 320,
|
||||
}),
|
||||
);
|
||||
shinyBtn.textContent = shiny ? '✦ shiny' : '✧ shiny';
|
||||
shinyBtn.classList.toggle('is-on', shiny);
|
||||
if (shinyBtn) {
|
||||
shinyBtn.textContent = shiny ? '✦ shiny' : '✧ shiny';
|
||||
shinyBtn.classList.toggle('is-on', shiny);
|
||||
}
|
||||
}
|
||||
paintArt();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user