feat(preview): fanned deck of cards on multi-select

When `selection.ids.size >= 2`, the inline preview swaps the single-
photo view for a SelectionDeck: each selected thumbnail renders as
an absolutely-positioned card with a translate + rotate computed
from its index in the deck, so the spread reads as a fan. CSS
transition-transform handles the reflow as the deck grows or
shrinks; `in:fly` lands new cards from above, `out:scale` shrinks
removals into the stack. Hash resolution walks the existing
TanStack caches (per-photo + photos-infinite envelope) so the deck
is side-effect-free — no fetches just to render thumbs.

Drop the now-redundant Maximize hover affordance on PhotoTile and
the `onOpenPreview` plumbing through PhotoGrid / +page.svelte:
single-click already places a tile into the inline preview pane,
so the dedicated "open preview" button no longer has a job.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 22:50:40 +02:00
parent e36f1939c6
commit 24c449f475
5 changed files with 132 additions and 51 deletions

View File

@@ -665,13 +665,6 @@
setAnchor(uid);
}
function onTileOpenPreview(uid: string) {
selection.ids.clear();
selection.ids.add(uid);
setFocused(uid);
setAnchor(uid);
}
// Scroll root for the infinite-scroll IntersectionObserver. Bound by
// the <main> element below; the sentinel's `root` references this so
// the observer measures intersections relative to the timeline pane
@@ -905,7 +898,6 @@
selected={sel}
onClick={(e) => onTileClick(e, photo.UID)}
onDblclick={(e) => onTileDblclick(e, photo.UID)}
onOpenPreview={() => onTileOpenPreview(photo.UID)}
/>
{/if}
</div>