From 78b0c7b84cea6ed813e2f34f17f22596f798027b Mon Sep 17 00:00:00 2001 From: dev_agent Date: Wed, 7 Oct 2026 01:54:58 -0400 Subject: [PATCH] Add the swipe gesture to the Discover deck's top card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The top card follows a pointer drag per handoff Motion §4: captured with setPointerCapture under touch-action none, it moves to translate(dx, dy*0.3) rotate(dx/18 deg) with the "Try it!" and "Pass" stamps at clamp(±dx/100). A release past 110 px right throws it from where it was let go (340 ms, no wiggle); past 110 px left folds it from the drag transform; anything shorter springs back over 420 ms and resets the stamps. Swipe decisions go through the same decide handler as the buttons and ← / → keys, so the busy lock and reduced-motion fade apply unchanged; only the top card is draggable and input is ignored while busy. motion.ts gains dragTransform, follow, unfollow and snapBack beside the exits; DeckCard marks its Pass stamp as a part. Maestro task 54c12e9e-5f36-4595-babb-e54107bb473b. Co-Authored-By: Claude Opus 5.5 --- web/src/discover/DeckCard.tsx | 16 +- web/src/discover/DiscoverPage.module.css | 6 + web/src/discover/DiscoverPage.test.tsx | 338 +++++++++++++++++++++++ web/src/discover/DiscoverPage.tsx | 96 ++++++- web/src/discover/motion.test.ts | 51 +++- web/src/discover/motion.ts | 42 ++- 6 files changed, 528 insertions(+), 21 deletions(-) diff --git a/web/src/discover/DeckCard.tsx b/web/src/discover/DeckCard.tsx index 6c2cb2d..3c6dbbc 100644 --- a/web/src/discover/DeckCard.tsx +++ b/web/src/discover/DeckCard.tsx @@ -1,3 +1,4 @@ +import type { DOMAttributes } from 'react' import { DIFFICULTIES, type Difficulty } from '../dinners/api.ts' import { Photo } from '../ui/Photo.tsx' import type { SideQuest } from './api.ts' @@ -8,25 +9,32 @@ export const DECK_DEPTH = [0, 1, 2] as const const DIFFICULTY_LABEL: Record = { easy: 'Easy', medium: 'Medium', hard: 'Hard' } +// SwipeHandlers are the pointer handlers that drag the top card. +export type SwipeHandlers = Pick, 'onPointerDown' | 'onPointerMove' | 'onPointerUp' | 'onPointerCancel'> + export type DeckCardProps = { quest: SideQuest // 0 is the top card; 1 and 2 stack behind it. depth: number + // swipe makes the card draggable; only the top card gets it. + swipe?: SwipeHandlers } // DeckCard is one Side quest in the deck. Only the top card is read out and // reachable; the cards behind it are the stack's edge, kept rendered so they // can be promoted. Its "Try it!" and "Pass" stamps stay hidden until a -// decision shows them, and its shade until Pass folds it (see motion.ts). -export function DeckCard({ quest, depth }: DeckCardProps) { +// decision or a drag shows them, and its shade until Pass folds it (see +// motion.ts). +export function DeckCard({ quest, depth, swipe }: DeckCardProps) { const top = depth === 0 const titleId = `side-quest-${quest.id}` return (
@@ -41,7 +49,7 @@ export function DeckCard({ quest, depth }: DeckCardProps) { -