525fe0fe76
Illegal drops previously snapped each dragged card to its origin slot and ran a horizontal ShakeAnim wiggle for negative feedback — which read as punitive on every misclick. The rejection now plays a 150 ms quintic ease-out glide from the drop location back to the resting slot. The audio cue (card_invalid.wav) still fires so the player gets clear "no" feedback; the visual is just gentler. Both rejection paths in input_plugin (mouse end_drag and touch end_drag) construct a CardAnimation::slide(drag_pos → target_pos) with MotionCurve::Responsive — the curve module's own docs recommend Responsive specifically for invalid snap-back because its zero overshoot reads forgiving rather than jittery. card_plugin's update_card_entity gates its snap path on CardAnimation absence so the StateChangedEvent that follows a rejection no longer fights the in-flight tween. Mirrors how resize_cards_in_place already drops in-flight tweens during a window resize. ShakeAnim itself stays in feedback_anim_plugin — the right-click invalid-target and double-click in-place rejection paths still use it because there's no movement to interpolate, just a "no" wiggle. Only the drag-rejection path swaps to the smooth tween. Six new rejection-tween tests pin the contract: CardAnimation is inserted on every dragged card, start/end positions and z values match the drag-to-resting transition, duration matches the new MOTION_DRAG_REJECT_SECS token, and the curve is Responsive. The two legacy ShakeAnim drag-rejection tests are removed since their contract is intentionally inverted by this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>