# Album Card Placement Flow - Refactored Design ## Current State Analysis ### Existing Flow (Pre-Refactor): 1. Pending cards spawn face-up in bottom-right slots 2. User drags card to album slot 3. Card placement triggers inventory move 4. Next card spawns ### Problems: - Cards spawn face-up (should be face-down) - No "smart selection" from pending queue based on current album page - No auto-flip to correct album page when card is picked up - States don't support "hold to reveal" behavior --- ## Proposed New Flow ### Visual Journey: ``` [Face-Down Card in Corner] ↓ (user holds/drags) [Card Flips to Reveal] + [Album auto-flips to correct page] ↓ (user drags over album) [Card hovers over slot] ↓ (user releases) [Card snaps to slot] → [Revealed State in slot] ``` ### Technical Implementation: --- ## 1. New Card States ### A. `CardPendingFaceDownState` **Purpose:** Initial state for cards in pending corner slots **Visuals:** - Card back visible (card front hidden) - Small scale (to fit in corner slot) - Idle in corner **Behavior:** - Does NOT respond to clicks - Responds to drag start (OnDragStarted) - On drag start → trigger smart card selection + flip animation **Transitions:** - OnDragStarted → `CardFlippingPendingState` --- ### B. `CardFlippingPendingState` **Purpose:** Transition state while card flips and album navigates **Visuals:** - Flip animation (card back → card front) - Card follows cursor during flip **Behavior:** - Play flip animation (uses CardAnimator.PlayFlip) - Emit event to AlbumViewPage to navigate to card's page - Wait for flip animation complete **Transitions:** - OnFlipComplete → `CardDraggingRevealedState` --- ### C. `CardDraggingRevealedState` **Purpose:** Card is revealed and being dragged around album **Visuals:** - Card front visible - No badges (clean revealed state) - Follow cursor/drag position - Slight scale-up while dragging **Behavior:** - Respond to drag position updates - Detect when hovering over valid AlbumCardSlot - Visual feedback when over valid slot - On drag end → snap to slot if valid, otherwise return to corner **Transitions:** - OnDragEnd (over valid slot) → slot's `PlacedInSlotState` - OnDragEnd (invalid) → `CardPendingFaceDownState` (return to corner, flip back) --- ## 2. Smart Card Selection System ### AlbumViewPage Responsibilities: ```csharp public class AlbumViewPage { private List _pendingQueue; // All pending cards private List _cornerCards; // 3 face-down card GameObjects in corner private int _currentAlbumPageIndex; /// /// When user starts dragging ANY corner card, we pick which pending card to reveal /// private void OnCornerCardDragStarted(Card cornerCard) { // 1. Get current album page's expected cards var currentPageSlots = GetSlotsOnCurrentPage(); var currentPageDefinitions = currentPageSlots .Select(slot => slot.TargetCardDefinition) .ToList(); // 2. Try to find a pending card that belongs on this page CardData selectedCard = _pendingQueue.FirstOrDefault(card => currentPageDefinitions.Any(def => def.Id == card.DefinitionId && def.Rarity == card.Rarity) ); // 3. If none on current page, pick random pending if (selectedCard == null) { selectedCard = _pendingQueue[Random.Range(0, _pendingQueue.Count)]; // Navigate album to the page where this card belongs int targetPage = FindPageForCard(selectedCard); NavigateToPage(targetPage); } // 4. Assign the selected card data to the corner card being dragged cornerCard.Context.SetupCard(selectedCard); // 5. Trigger flip (handled by state) cornerCard.Context.StateMachine.ChangeState("FlippingPendingState"); } } ``` --- ## 3. Card.cs Extensions ### New Setup Method: ```csharp public class Card { /// /// Setup for album pending placement (starts face-down in corner) /// public void SetupForAlbumPending() { // Start with NO card data (will be assigned on drag) SetupCard(null, "PendingFaceDownState"); SetDraggingEnabled(true); // Enable drag immediately } } ``` ### Drag Event Routing: ```csharp // In Card.cs public event Action OnDragStartedEvent; private void OnDragStarted() { OnDragStartedEvent?.Invoke(this); } ``` --- ## 4. AlbumViewPage Modifications ### Spawn Pending Cards (Face-Down): ```csharp private void SpawnPendingCards() { // Spawn up to 3 "blank" face-down cards in corner for (int i = 0; i < MAX_VISIBLE_CARDS; i++) { GameObject cardObj = Instantiate(cardPrefab, bottomRightSlots.transform); var card = cardObj.GetComponent(); if (card != null) { // Setup as pending (no data yet, face-down) card.SetupForAlbumPending(); // Subscribe to drag start card.OnDragStartedEvent += OnCornerCardDragStarted; // Assign to corner slot DraggableSlot slot = FindSlotByIndex(i); card.AssignToSlot(slot, true); _cornerCards.Add(card); } } } ``` ### Handle Drag Start (Smart Selection): ```csharp private void OnCornerCardDragStarted(Card cornerCard) { if (_pendingQueue.Count == 0) return; // Smart selection logic (from section 2) CardData selectedCard = SelectSmartPendingCard(); // Assign data to the dragged corner card cornerCard.Context.SetupCard(selectedCard); // State transition to flipping (handled by state machine) // FlippingPendingState will trigger flip animation + album navigation } ``` ### Navigate to Card's Page: ```csharp public void NavigateToCardPage(CardData card) { int targetPage = FindPageForCard(card); if (targetPage != _currentAlbumPageIndex) { // Trigger book page flip animation bookController.FlipToPage(targetPage); } } ``` --- ## 5. State Implementation Details ### CardPendingFaceDownState.cs ```csharp public class CardPendingFaceDownState : AppleState { private CardContext _context; public override void OnEnterState() { // Show card back, hide card front if (_context.CardDisplay != null) { _context.CardDisplay.gameObject.SetActive(false); // Hide front } var cardBack = GetComponentInChildren(); // Assumes CardBack component exists if (cardBack != null) { cardBack.gameObject.SetActive(true); } // Small scale for corner slot _context.RootTransform.localScale = Vector3.one * 0.8f; } } ``` ### CardFlippingPendingState.cs ```csharp public class CardFlippingPendingState : AppleState { private CardContext _context; public override void OnEnterState() { // Notify album page to navigate var albumPage = FindObjectOfType(); if (albumPage != null) { albumPage.NavigateToCardPage(_context.CardData); } // Play flip animation if (_context.Animator != null) { _context.Animator.PlayFlip( startRotation: Quaternion.Euler(0, 180, 0), // back facing endRotation: Quaternion.identity, // front facing onComplete: OnFlipComplete ); } } private void OnFlipComplete() { _context.StateMachine.ChangeState("DraggingRevealedState"); } } ``` ### CardDraggingRevealedState.cs ```csharp public class CardDraggingRevealedState : AppleState { private CardContext _context; private AlbumCardSlot _hoveredSlot; public override void OnEnterState() { // Card front visible, clean revealed (no badges) if (_context.CardDisplay != null) { _context.CardDisplay.gameObject.SetActive(true); } // Slightly larger while dragging _context.Animator.PlayEnlarge(1.2f); } void Update() { // Detect hover over valid album slots _hoveredSlot = DetectValidSlotUnderCursor(); if (_hoveredSlot != null) { // Visual feedback: highlight slot or card } } public void OnDragEnded() { if (_hoveredSlot != null && _hoveredSlot.CanAcceptCard(_context.CardData)) { // Snap to slot and transition to PlacedInSlotState SnapToSlot(_hoveredSlot); } else { // Return to corner, flip back to face-down ReturnToCorner(); } } } ``` --- ## 6. Required New Components ### CardBack Component ```csharp public class CardBack : MonoBehaviour { [SerializeField] private Image backImage; public void Show() => gameObject.SetActive(true); public void Hide() => gameObject.SetActive(false); } ``` Attach to Card prefab as a sibling to CardDisplay. --- ## 7. Prefab Structure ``` Card (GameObject) ├── StateMachine (AppleMachine) │ ├── PendingFaceDownState │ ├── FlippingPendingState │ ├── DraggingRevealedState │ ├── PlacedInSlotState (existing) │ └── ... (other states) ├── CardContext ├── CardAnimator ├── CardDisplay (front visuals) ├── CardBack (back visuals - NEW) └── DraggableObject ``` --- ## 8. Migration Steps ### Step 1: Create New States - CardPendingFaceDownState.cs - CardFlippingPendingState.cs - CardDraggingRevealedState.cs ### Step 2: Add CardBack Component - Create CardBack.cs script - Add CardBack GameObject to Card prefab - Design card back visual (sprite, frame, etc.) ### Step 3: Update Card.cs - Add SetupForAlbumPending() method - Add OnDragStartedEvent - Wire drag events to state machine ### Step 4: Update AlbumViewPage - Modify SpawnPendingCards() to spawn face-down - Implement smart selection logic - Add NavigateToCardPage() method - Connect to book flip controller ### Step 5: Update CardAnimator - Ensure PlayFlip() can handle arbitrary start/end rotations - Add any needed drag-follow animation helpers ### Step 6: Testing - Test corner card drag → flip → album navigation - Test smart selection (page match prioritization) - Test return-to-corner on invalid drop - Test snap-to-slot on valid drop - Test multiple cards in queue --- ## 9. Edge Cases & Considerations ### No Pending Cards - Don't spawn corner cards if pending queue is empty - Hide corner slots when no cards to place ### Album Page Navigation During Drag - Lock page flipping while dragging (prevent user manual flip) - Queue navigation if flip animation in progress ### Multiple Cards Dragged Simultaneously - Only allow one card to be in FlippingPending/DraggingRevealed at a time - Disable other corner cards while one is being dragged ### Card Returns to Corner - Flip back animation (reverse of reveal) - Re-enter PendingFaceDownState - Unassign CardData (become "blank" again for next drag) ### Invalid Slot Drop - Visual feedback (shake, red highlight) - Smooth return animation to corner --- ## 10. Benefits of This Approach ✅ **Consistent State Architecture** - Uses same state machine pattern as booster flow ✅ **Smart UX** - Auto-navigation to correct album page ✅ **Clean Separation** - States handle visuals/behavior, page handles logic ✅ **Reusable** - States can be reused for other card flows ✅ **Extensible** - Easy to add new behaviors (e.g., card preview on hover) ✅ **Testable** - Each state can be tested independently --- ## Open Questions for Approval 1. **Card Back Design:** Should we use a generic back for all cards, or rarity-specific backs? 2. **Navigation Timing:** Should album flip happen instantly or animated during card flip? 3. **Return Animation:** Fast snap-back or gentle float-back when invalid drop? 4. **Multiple Rarities:** If pending queue has same card at multiple rarities, which to prioritize? 5. **Corner Slot Count:** Keep at 3, or make configurable? --- Ready to implement once approved! 🎉