mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
ac2ad8aac2
Implements pure CSS card stack animations for BookDate recommendations, including smooth exit and advance transitions. Adds local caching of library cover thumbnails during scans, updates database schema and API to serve cached covers, and enhances BookDate to support 'favorites' scope with a book picker modal. Updates admin settings validation logic for Prowlarr, improves indexer state management, and documents new features and backend changes.
7.9 KiB
7.9 KiB
BookDate Card Stack Animations
Status: ✅ Implemented | Pure CSS card stack with smooth exit/advance animations
Overview
Visual card stack (3 visible cards) with GPU-accelerated animations. Top card swipes away, remaining cards advance forward smoothly.
Key Components
CardStack.tsx
- Location:
src/components/bookdate/CardStack.tsx - Purpose: Orchestrates 3-card stack rendering and animation lifecycle
- Props:
recommendations: any[]- Full recommendations arraycurrentIndex: number- Index of current top cardonSwipe: (action, markedAsKnown?) => void- Swipe handler (API call)onSwipeComplete: () => void- Called after animations finish
Animation Flow:
- User swipes →
handleSwipeStarttriggered - Exit animation starts (400ms) → API call
- Exit completes →
visibleCardsarray updated to exclude exited card - Advance animation starts (350ms) → Cards move from positions 1,2,3 to 0,1,2
- Advance completes →
onSwipeCompletecalled →currentIndexincremented
State Management:
isExiting: boolean- Exit animation in progressexitDirection: 'left' | 'right' | 'up'- Which exit animation to playisAdvancing: boolean- Advance animation in progress
Visible Cards Logic:
- Normal: Shows cards at
[currentIndex, currentIndex+1, currentIndex+2]withstackPosition0, 1, 2 - During Advance: Shows cards at
[currentIndex+1, currentIndex+2, currentIndex+3]withstackPosition0, 1, 2 andfromPosition1, 2, 3- This excludes the exited card and prevents snapping
fromPositiondetermines which advance animation to apply
RecommendationCard.tsx Updates
- New Props:
stackPosition?: number- 0=top, 1=middle, 2=bottom (default: 0)isAnimating?: boolean- Disables gestures during animations (default: false)isDraggable?: boolean- Only top card accepts input (default: true)
Behavior:
- Swipe handlers disabled when
!isDraggable || isAnimating - Desktop buttons hidden when
stackPosition !== 0 - Drag offset only updates for top card
page.tsx Updates
- Changed: Import
CardStackinstead ofRecommendationCard - Added:
handleSwipeComplete()callback - Modified:
handleSwipe()no longer incrementscurrentIndex(delegated tohandleSwipeComplete)
CSS Animations
Location: src/app/globals.css
Exit Animations (400ms, ease-in-out)
.animate-exit-left /* translate(-150%, 50px) rotate(-25deg) */
.animate-exit-right /* translate(150%, 50px) rotate(25deg) */
.animate-exit-up /* translate(0, -120%) scale(0.8) */
Advance Animations (350ms, bounce easing)
.animate-advance-to-top /* scale(0.95→1.0), translateY(-12px→0) */
.animate-advance-to-middle /* scale(0.90→0.95), translateY(-24px→-12px) */
.animate-enter /* scale(0.85→0.90), translateY(-36px→-24px) */
Stack Position Classes (Static)
.card-stack-position-0 /* z-50, scale(1.0), translateY(0), opacity(1.0) */
.card-stack-position-1 /* z-40, scale(0.95), translateY(-12px), opacity(0.95) */
.card-stack-position-2 /* z-30, scale(0.90), translateY(-24px), opacity(0.90) */
Performance Optimizations
.card-stack-container /* perspective: 1000px, preserve-3d */
.card-stack-item /* will-change: transform, opacity */
Animation Timing
| Phase | Duration | Easing | Description |
|---|---|---|---|
| Exit | 400ms | ease-in-out | Top card swipes away |
| Advance | 350ms | cubic-bezier(0.34, 1.56, 0.64, 1) | Cards move forward (slight bounce) |
| Total | 750ms | - | Full swipe cycle |
Staggering: Advance animations start after exit completes (sequential, not overlapping).
Edge Cases Handled
Rapid Swipes
- Problem: User swipes again during animation
- Solution:
isAnimatingflag blocks gestures and button clicks - Code:
CardStack.handleSwipeStart()checksisExiting || isAdvancing
<3 Cards Remaining
- Problem: Not enough cards to fill stack
- Solution:
CardStackrenders only available cards (0-3) - Behavior: Stack naturally shrinks as user approaches end
Undo Functionality
- Problem: Undo reverses card to top, but animations may be in progress
- Solution:
useEffectinCardStackresets animation states whencurrentIndexchanges externally - Code:
useEffect(() => { setIsExiting(false); ... }, [currentIndex])
Empty State
- Problem: No cards to render
- Solution:
CardStackreturnsnull,page.tsxshows empty state UI - Trigger:
currentIndex >= recommendations.length
Mobile Performance
Target: 60fps on mobile devices
Optimizations:
- GPU-accelerated properties only (
transform,opacity, notleft/top/width) will-change: transform, opacityhints browser to optimizebackface-visibility: hiddenprevents rendering artifacts- No layout shift (cards positioned absolutely)
Tested On:
- Chrome (desktop + mobile)
- Safari (iOS + macOS)
- Firefox
User Experience
Visual Hierarchy:
- Top card: Full size, interactive, clear visuals
- Card 2: 95% scale, 95% opacity, visible but de-emphasized
- Card 3: 90% scale, 90% opacity, subtle depth cue
Swipe Directions:
- Left: Reject (red overlay, rotate left)
- Right: Request (green overlay, confirm toast, rotate right)
- Up: Dismiss (blue overlay, shrink up)
Toast Confirmation:
- Right swipe triggers toast modal
- User chooses: "Request" or "Mark as Liked"
- Card exit animation plays after choice
Integration with Existing Features
Settings Widget
- Status: No changes required
- Behavior: Opens over card stack, gestures disabled when modal open
Undo Button
- Status: Works with stack
- Behavior: Triggers
loadRecommendations()→ Cards re-render from API - Animation: No special animation (instant reset to fresh state)
Progress Indicator
- Status: No changes required
- Display: Shows
currentIndex + 1 / recommendations.length
Desktop Buttons
- Status: Updated to disable during animations
- Code:
disabled={isAnimating}inRecommendationCard.tsx:217-234
Troubleshooting
Cards Not Stacking
- Check: CSS classes applied correctly in
CardStack.tsx - Verify:
card-stack-position-{0,1,2}classes present inglobals.css - Debug: Inspect z-index values (50, 40, 30)
Cards Snapping Instead of Animating
- Root Cause: Exited card still in
visibleCardsarray during advance phase - Fix: During
isAdvancing,visibleCardsstarts fromcurrentIndex + 1(skips exited card) - Verify: Check
CardStack.tsx:71-97- advance branch excludes card atcurrentIndex
Animations Not Playing
- Check: Exit/advance animation classes applied during state transitions
- Verify:
animationClasscomputed correctly based oncard.fromPositionduring advance - Debug: Console log
isExiting,exitDirection,isAdvancing,visibleCards
Gestures Not Working
- Check:
isDraggableprop passed correctly (only true for top card) - Verify:
isAnimatingnot stuck in true state - Debug: Check
CardStackanimation state machine
Performance Issues
- Check: Animations targeting only
transformandopacity - Verify:
will-changeapplied to.card-stack-item - Test: Chrome DevTools Performance tab (60fps target)
Related Files
- Documentation:
documentation/features/bookdate-prd.md(BookDate feature spec) - Components:
src/components/bookdate/LoadingScreen.tsx,SettingsWidget.tsx - API:
src/app/api/bookdate/swipe/route.ts
Future Enhancements (Not Implemented)
- Preload Card 4: Load image for 4th card in stack (currently loads on-demand)
- Spring Physics: Replace CSS easing with spring animations for more natural feel
- Haptic Feedback: Vibrate on swipe (requires Web Vibration API)
- Parallax Effect: Cards shift slightly on device tilt (requires DeviceOrientation API)