- client/ANIMATIONS.md: Full documentation of the CardAnimations API, timing config, CSS rules, and common patterns - CLAUDE.md: Project context for AI assistants with architecture overview and development guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
Card Animation System
This document describes the unified animation system for the Golf card game client.
Architecture
All card animations use anime.js. There are no CSS transitions on card elements.
| What | How |
|---|---|
| Card movements | anime.js |
| Card flips | anime.js |
| Swap animations | anime.js |
| Pulse/glow effects | anime.js |
| Hover states | CSS :hover only |
| Show/hide | CSS .hidden class only |
Why anime.js?
- Consistent timing and easing across all animations
- Coordinated multi-element sequences via timelines
- Proper animation cancellation via
activeAnimationstracking - No conflicts between CSS and JS animation systems
Core Files
| File | Purpose |
|---|---|
card-animations.js |
Unified CardAnimations class - all animation logic |
timing-config.js |
Centralized timing/easing configuration |
style.css |
Static styles only (no transitions on cards) |
CardAnimations Class API
Global instance available at window.cardAnimations.
Draw Animations
// Draw from deck - lift, move to hold area, flip to reveal
cardAnimations.animateDrawDeck(cardData, onComplete)
// Draw from discard - quick grab, no flip
cardAnimations.animateDrawDiscard(cardData, onComplete)
// For opponent draw-then-discard - deck to discard with flip
cardAnimations.animateDeckToDiscard(card, onComplete)
Flip Animations
// Generic flip animation on any card element
cardAnimations.animateFlip(element, cardData, onComplete)
// Initial flip at game start (local player)
cardAnimations.animateInitialFlip(cardElement, cardData, onComplete)
// Opponent card flip (fire-and-forget)
cardAnimations.animateOpponentFlip(cardElement, cardData, rotation)
Swap Animations
// Player swaps drawn card with hand card
cardAnimations.animateSwap(position, oldCard, newCard, handCardElement, onComplete)
// Opponent swap (fire-and-forget)
cardAnimations.animateOpponentSwap(playerId, position, discardCard, sourceCardElement, rotation, wasFaceUp)
Discard Animations
// Animate held card swooping to discard pile
cardAnimations.animateDiscard(heldCardElement, targetCard, onComplete)
Ambient Effects (Looping)
// "Your turn to draw" shake effect
cardAnimations.startTurnPulse(element)
cardAnimations.stopTurnPulse(element)
// CPU thinking glow
cardAnimations.startCpuThinking(element)
cardAnimations.stopCpuThinking(element)
// Initial flip phase - clickable cards glow
cardAnimations.startInitialFlipPulse(element)
cardAnimations.stopInitialFlipPulse(element)
cardAnimations.stopAllInitialFlipPulses()
One-Shot Effects
// Pulse when card lands on discard
cardAnimations.pulseDiscard()
// Pulse effect on face-up swap
cardAnimations.pulseSwap(element)
// Pop-in when element appears (use sparingly)
cardAnimations.popIn(element)
// Gold ring expanding effect before draw
cardAnimations.startDrawPulse(element)
Utility Methods
// Check if animation is in progress
cardAnimations.isBusy()
// Cancel all running animations
cardAnimations.cancel()
cardAnimations.cancelAll()
// Clean up animation elements
cardAnimations.cleanup()
Animation Overlay Pattern
For complex animations (flips, swaps), the system:
- Creates a temporary overlay element (
.draw-anim-card) - Positions it exactly over the source card
- Hides the original card (
opacity: 0or.swap-out) - Animates the overlay
- Removes overlay and reveals updated original card
This ensures smooth animations without modifying the DOM structure of game cards.
Timing Configuration
All timing values are in timing-config.js and exposed as window.TIMING.
Key Durations
| Animation | Duration | Notes |
|---|---|---|
| Flip | 245ms | 3D rotateY animation |
| Deck lift | 63ms | Before moving to hold |
| Deck move | 105ms | To hold position |
| Discard lift | 25ms | Quick grab |
| Discard move | 76ms | To hold position |
| Swap pulse | 400ms | Scale + brightness |
| Turn shake | 400ms | Every 3 seconds |
Easing Functions
window.TIMING.anime.easing = {
flip: 'easeInOutQuad', // Smooth acceleration/deceleration
move: 'easeOutCubic', // Fast start, gentle settle
lift: 'easeOutQuad', // Quick lift
pulse: 'easeInOutSine', // Smooth oscillation
}
CSS Rules
What CSS Does
- Static card appearance (colors, borders, sizing)
- Layout and positioning
- Hover states (
:hoverscale/shadow) - Show/hide via
.hiddenclass
What CSS Does NOT Do
- No
transitionon any card element - No
@keyframesfor card animations - No
.flipped,.moving,.flippingtransition triggers
Important Classes
| Class | Purpose |
|---|---|
.draw-anim-card |
Temporary overlay during animation |
.draw-anim-inner |
3D flip container |
.swap-out |
Hides original during swap animation |
.hidden |
Opacity 0, no display change |
.draw-pulse |
Gold ring expanding effect |
Common Patterns
Preventing Premature UI Updates
The isDrawAnimating flag in app.js prevents the held card from appearing before the draw animation completes:
// In renderGame()
if (!this.isDrawAnimating && /* other conditions */) {
// Show held card
}
Animation Sequencing
Use anime.js timelines for coordinated sequences:
const timeline = anime.timeline({
easing: 'easeOutQuad',
complete: () => { /* cleanup */ }
});
timeline.add({ targets: el, translateY: -15, duration: 100 });
timeline.add({ targets: el, left: x, top: y, duration: 200 });
timeline.add({ targets: inner, rotateY: 0, duration: 245 });
Fire-and-Forget Animations
For opponent/CPU animations that don't block game flow:
// No onComplete callback needed
cardAnimations.animateOpponentFlip(cardElement, cardData);
Debugging
Check Active Animations
console.log(window.cardAnimations.activeAnimations);
Force Cleanup
window.cardAnimations.cancelAll();
Animation Not Working?
- Check that anime.js is loaded before card-animations.js
- Verify element exists and is visible
- Check for CSS transitions that might conflict
- Look for errors in console