7a77c66f6d
When a drag was rejected, ShakeAnim was inserted on each dragged card with origin_x = transform.translation.x — the drop-location X, not the origin pile slot's X. tick_shake_anim restores translation.x to origin_x at the end of the 0.3s shake, which fights the sync_cards slide that StateChangedEvent triggers and pins the card at the drop location. The visible symptom (reported during the 2026-04-29 smoke test) was "the card returns to the slot beside the pile". Compute the target X using the existing card_position() helper against the origin pile and the card's stack_index, then save that as ShakeAnim::origin_x. The shake now ends with the card at its correct resting slot. Apply the same fix to both the mouse path (end_drag) and the touch path (touch_end_drag), and update the existing Task #57 test to reflect the new contract (origin_x = origin slot X, not drop-location X). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>