Progress: manage/reset played games; Lighthouse polish
Progress view "Your games" now has an Edit mode: tap a game to open a dialog to **reset** its caught/seen (keeps it listed) or **remove** it from the list (also clears its bucket). The currently-selected game can't be removed (it'd re-add on next launch) — reset only. New `clearGame(vgKey)` in the selection store. Lighthouse pass (headless, local): SEO 100, a11y 96, best-practices 96. - add public/robots.txt (SEO 92 → 100) - ProgressRing: keep the aria-label in sync with the visible count (fixes label-content-name-mismatch) - .type-chip: text-shadow for white-on-bright-type-colour legibility, 0.68 → 0.7rem Still open (design calls, see notes): type-chip colour contrast is ~2–3.7:1 for the lighter types (white on the canonical type colours); some sub-12px labels; performance is dominated by the ~1 MB snapshot and the ~1000-card grid. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
parent
3a54cdcc53
commit
edef6bf879
@ -125,7 +125,8 @@ selected game. Query, scroll, tab and filters persist.
|
||||
Every game's seen/caught tally against its own dex, from its own per-game
|
||||
bucket. Leads with the games you've played (selected at least once, or
|
||||
imported a save for); the rest sit behind a toggle. The National Dex row is
|
||||
the union across all games.
|
||||
the union across all games. **Edit** the "Your games" list to reset a
|
||||
game's caught/seen or drop it from the list.
|
||||
|
||||
### Shiny hunt tracker (`#/shiny`)
|
||||
|
||||
|
||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
@ -62,6 +62,9 @@ export function ProgressRing() {
|
||||
String(CIRC * (1 - Math.max(seen, caught) / safeTotal)),
|
||||
);
|
||||
tail.textContent = `/${total}`;
|
||||
// Keep the accessible name in sync with the visible count (avoids an
|
||||
// aria-label / visible-text mismatch).
|
||||
node.setAttribute('aria-label', `Progress by game: ${caught} of ${total} caught`);
|
||||
countUp(num, caught, { from: shown, duration: first ? 700 : 350 });
|
||||
shown = caught;
|
||||
};
|
||||
|
||||
@ -139,6 +139,16 @@ export function replaceFlags({ seen = [], caught = [], game } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Drop one game's seen/caught bucket entirely (its own progress reset). */
|
||||
export function clearGame(vgKey) {
|
||||
selection.set((s) => {
|
||||
if (!s.games[vgKey]) return s;
|
||||
const games = { ...s.games };
|
||||
delete games[vgKey];
|
||||
return { ...s, games };
|
||||
});
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
@ -221,10 +221,13 @@
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.68rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
color: #fff;
|
||||
/* The bright type colours are light; a soft shadow keeps the white text
|
||||
readable on fire / grass / electric etc. */
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
|
||||
background: var(--type-normal);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
@ -3275,6 +3278,31 @@
|
||||
color: var(--text-dim);
|
||||
margin: 12px 0 2px;
|
||||
}
|
||||
.prog__genrow {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.prog__edit {
|
||||
border: none;
|
||||
background: none;
|
||||
font: inherit;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
.prog__row--manage {
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
}
|
||||
.prog__manage {
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.prog__row {
|
||||
display: grid;
|
||||
grid-template-columns: 8.5rem 1fr 3.6rem;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { el, clear, onTeardown } from '../lib/dom.js';
|
||||
import { loadSnapshot } from '../data/snapshot.js';
|
||||
import { settings } from '../store/settings.js';
|
||||
import { selection, stats, statsNational } from '../store/selection.js';
|
||||
import { selection, stats, statsNational, clearGame } from '../store/selection.js';
|
||||
import { ui } from '../store/ui.js';
|
||||
import { playedGames, isPlayed } from '../store/playedGames.js';
|
||||
import { playedGames, isPlayed, unmarkPlayed } from '../store/playedGames.js';
|
||||
import { dexesForVersionGroup, versionGroupsByGeneration } from '../data/pokedex-resolver.js';
|
||||
import { chooseDialog } from '../lib/dialog.js';
|
||||
|
||||
function bar(seen, caught, total) {
|
||||
const pc = total ? (caught / total) * 100 : 0;
|
||||
@ -30,21 +31,56 @@ export async function ProgressView() {
|
||||
return [...ids];
|
||||
};
|
||||
|
||||
let editing = false;
|
||||
|
||||
async function manageGame(vg) {
|
||||
const g = stats(speciesIdsFor(vg.key), vg.key);
|
||||
const isActive = settings.get().versionGroup === vg.key;
|
||||
const choices = [{ key: 'reset', label: 'Reset caught / seen for this game' }];
|
||||
if (!isActive) {
|
||||
choices.push({ key: 'remove', label: 'Remove from your games', class: 'button--danger' });
|
||||
}
|
||||
choices.push({ key: null, label: 'Cancel', class: 'button--ghost' });
|
||||
|
||||
const pick = await chooseDialog({
|
||||
title: vg.name,
|
||||
body:
|
||||
g.caught || g.seen
|
||||
? `${g.caught} caught · ${g.seen} seen tracked here.` +
|
||||
(isActive ? ' This is your selected game.' : '')
|
||||
: 'Nothing tracked here yet.' + (isActive ? ' This is your selected game.' : ''),
|
||||
choices,
|
||||
});
|
||||
if (pick === 'reset') {
|
||||
clearGame(vg.key);
|
||||
render();
|
||||
} else if (pick === 'remove') {
|
||||
clearGame(vg.key);
|
||||
unmarkPlayed(vg.key);
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
function gameRow(vg, { played }) {
|
||||
const s = stats(speciesIdsFor(vg.key), vg.key);
|
||||
const manage = played && editing;
|
||||
return el(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
class: `prog__row${played ? ' prog__row--played' : ''}`,
|
||||
onclick: () => {
|
||||
class: `prog__row${played ? ' prog__row--played' : ''}${manage ? ' prog__row--manage' : ''}`,
|
||||
onclick: manage
|
||||
? () => manageGame(vg)
|
||||
: () => {
|
||||
settings.set({ versionGroup: vg.key, pokedex: null });
|
||||
location.hash = '#/';
|
||||
},
|
||||
},
|
||||
el('span', { class: 'prog__name' }, vg.name),
|
||||
bar(s.seen, s.caught, s.total),
|
||||
el('span', { class: 'prog__count' }, `${s.caught} / ${s.total}`),
|
||||
manage
|
||||
? el('span', { class: 'prog__manage', 'aria-hidden': 'true' }, '⋯')
|
||||
: el('span', { class: 'prog__count' }, `${s.caught} / ${s.total}`),
|
||||
);
|
||||
}
|
||||
|
||||
@ -72,12 +108,32 @@ export async function ProgressView() {
|
||||
|
||||
// Games you've played (imported a save for, or opened at least once).
|
||||
if (anyPlayed) {
|
||||
list.append(el('h3', { class: 'prog__gen' }, 'Your games'));
|
||||
list.append(
|
||||
el(
|
||||
'div',
|
||||
{ class: 'prog__genrow' },
|
||||
el('h3', { class: 'prog__gen' }, 'Your games'),
|
||||
el(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
class: 'prog__edit',
|
||||
onclick: () => {
|
||||
editing = !editing;
|
||||
render();
|
||||
},
|
||||
},
|
||||
editing ? 'Done' : 'Edit',
|
||||
),
|
||||
),
|
||||
);
|
||||
for (const { versionGroups } of gens) {
|
||||
for (const vg of versionGroups) {
|
||||
if (isPlayed(vg.key)) list.append(gameRow(vg, { played: true }));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editing = false;
|
||||
}
|
||||
|
||||
// Everything else — behind a toggle, since a single unified caught
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user