Center card sprite; ghost number to top:2px; scroll-position restore

- Card sprite sits centred in the upper area (flex:1 + place-items:center)
  instead of a forced margin; sprite-style / boxed sizing moved onto the
  img so it stays centred
- .card__ghost number nudged to top: 2px
- Feed Back-navigation restores the raw scroll offset again (dropped the
  card-anchor approach); keeps manual scrollRestoration + retry-on-reflow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
chris 2026-08-28 08:25:23 -04:00
parent 23272b99a7
commit 491d1102cc
4 changed files with 22 additions and 37 deletions

View File

@ -13,7 +13,7 @@ applyTheme();
// Own the scroll position ourselves (views restore it deliberately).
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
// Navigation scroll memory is per-session — start a cold launch at the top.
ui.set({ feedScroll: 0, feedAnchor: '', searchScroll: 0 });
ui.set({ feedScroll: 0, searchScroll: 0 });
// React to preference changes. The dex feed updates itself in place, but the
// Pokémon detail page is built once per visit — rebuild it when the selected

View File

@ -10,7 +10,6 @@ export const ui = createStore('pdx.ui', {
searchScroll: 0,
searchTab: 'pokemon',
feedScroll: 0,
feedAnchor: '',
sort: 'dex',
sortDesc: false,
filter: 'all',

View File

@ -431,7 +431,7 @@
}
.card__ghost {
position: absolute;
top: 10px;
top: 2px;
right: 8px;
z-index: 0;
font-size: 3rem;
@ -460,13 +460,14 @@
.card__art {
position: relative;
z-index: 1;
width: 120px;
height: 120px;
margin-top: 10px; /* flex column grows the card — no overlap with the body */
flex: 1 1 auto;
min-height: 124px;
display: grid;
place-items: center; /* centre the sprite in the card's upper area */
}
.card__art .sprite {
width: 100%;
height: 100%;
width: 116px;
height: 116px;
object-fit: contain;
filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.28));
}
@ -474,21 +475,19 @@
.card[data-sprite="game"] .card__art .sprite {
image-rendering: pixelated;
}
.card[data-sprite="game"] .card__art {
width: 96px;
height: 96px;
.card[data-sprite="game"] .card__art .sprite {
width: 92px;
height: 92px;
}
/* Gen 12 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;
.card[data-boxed] .card__art .sprite {
width: 74px;
height: 74px;
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 {

View File

@ -71,13 +71,6 @@ export async function DexGrid() {
const snap = await loadSnapshot();
const grid = el('div', { class: 'grid' });
// Remember which card was opened so Back can land on it exactly.
grid.addEventListener('click', (e) => {
const card = e.target.closest('.card');
if (card && !e.target.closest('.card__caught')) {
ui.set({ feedAnchor: card.getAttribute('href') });
}
});
let query = '';
let filterKey = ui.get().filter || 'all';
let sortKey = ui.get().sort || 'dex';
@ -160,26 +153,20 @@ export async function DexGrid() {
// Changing the list order/contents makes the old scroll offset meaningless.
function resetScroll() {
ui.set({ feedScroll: 0, feedAnchor: '' });
ui.set({ feedScroll: 0 });
window.scrollTo(0, 0);
}
// Prefer landing on the exact card that was opened — robust against the
// grid's estimated off-screen card heights and late header reflow (webfont
// load). Re-correct a few times unless the user has already scrolled away.
// Return to the saved scroll offset. Retry a few times — the grid's height
// isn't final until the webfont loads and off-screen cards settle — but
// stop once we've reached the target or the user starts scrolling.
function restoreScroll() {
const anchor = ui.get().feedAnchor;
const target = anchor && grid.querySelector(`.card[href="${anchor}"]`);
if (!target) {
window.scrollTo(0, ui.get().feedScroll || 0);
return;
}
const y = ui.get().feedScroll || 0;
if (!y) return;
let applied = null;
const go = () => {
if (applied != null && Math.abs(window.scrollY - applied) > 8) return; // user moved
const headH = document.querySelector('.feed-head')?.getBoundingClientRect().height || 0;
target.style.scrollMarginTop = `${Math.round(headH) + 16}px`;
target.scrollIntoView({ block: 'start' });
window.scrollTo(0, y);
applied = Math.round(window.scrollY);
};
go();
@ -310,7 +297,7 @@ export async function DexGrid() {
const offSettings = settings.subscribe((s) => {
if (s.versionGroup !== mountedVG) {
mountedVG = s.versionGroup;
ui.set({ feedScroll: 0, feedAnchor: '' });
ui.set({ feedScroll: 0 });
}
rebuild();
});