golfgame/client/timing-config.js
adlee-was-taken 9fc6b83bba v3.0.0: V3 features, server refactoring, and documentation overhaul
- Extract WebSocket handlers from main.py into handlers.py
- Add V3 feature docs (dealer rotation, dealing animation, round end reveal,
  column pair celebration, final turn urgency, opponent thinking, score tallying,
  card hover/selection, knock early drama, column pair indicator, swap animation
  improvements, draw source distinction, card value tooltips, active rules context,
  discard pile history, realistic card sounds)
- Add V3 refactoring docs (ai.py, main.py/game.py, misc improvements)
- Add installation guide with Docker, systemd, and nginx setup
- Add helper scripts (install.sh, dev-server.sh, docker-build.sh)
- Add animation flow diagrams documentation
- Add test files for handlers, rooms, and V3 features
- Add e2e test specs for V3 features
- Update README with complete project structure and current tech stack
- Update CLAUDE.md with full architecture tree and server layer descriptions
- Update .env.example to reflect PostgreSQL (remove SQLite references)
- Update .gitignore to exclude virtualenv files, .claude/, and .db files
- Remove tracked virtualenv files (bin/, lib64, pyvenv.cfg)
- Remove obsolete game_log.py (SQLite) and games.db

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:03:45 -05:00

156 lines
6.3 KiB
JavaScript

// Centralized timing configuration for all animations and pauses
// Edit these values to tune the feel of card animations and CPU gameplay
const TIMING = {
// Card animations (milliseconds)
card: {
flip: 320, // Card flip duration — readable but snappy
move: 300, // General card movement
lift: 100, // Perceptible lift before travel
settle: 80, // Gentle landing cushion
},
// Pauses - minimal, let animations flow
pause: {
afterFlip: 0, // No pause - flow into next action
afterDiscard: 100, // Brief settle
beforeNewCard: 0, // No pause
afterSwapComplete: 100, // Brief settle
betweenAnimations: 0, // No gaps - continuous flow
beforeFlip: 0, // No pause
},
// Beat timing for animation phases (~1.2 sec with variance)
beat: {
base: 1200, // Base beat duration (longer to see results)
variance: 200, // +/- variance for natural feel
fadeOut: 300, // Fade out duration
fadeIn: 300, // Fade in duration
},
// UI feedback durations (milliseconds)
feedback: {
drawPulse: 375, // Draw pile highlight duration (25% slower for clear sequencing)
discardLand: 375, // Discard land effect duration (25% slower)
cardFlipIn: 300, // Card flip-in effect duration
statusMessage: 2000, // Toast/status message duration
copyConfirm: 2000, // Copy button confirmation duration
discardPickup: 250, // Discard pickup animation duration
},
// CSS animation timing (for reference - actual values in style.css)
css: {
cpuConsidering: 1500, // CPU considering pulse cycle
},
// Anime.js animation configuration
anime: {
easing: {
flip: 'cubicBezier(0.34, 1.2, 0.64, 1)', // Slight overshoot snap
move: 'cubicBezier(0.22, 0.68, 0.35, 1.0)', // Smooth deceleration
lift: 'cubicBezier(0.0, 0.0, 0.2, 1)', // Quick out, soft stop
settle: 'cubicBezier(0.34, 1.05, 0.64, 1)', // Tiny overshoot on landing
arc: 'cubicBezier(0.45, 0, 0.15, 1)', // Smooth S-curve for arcs
pulse: 'easeInOutSine', // Keep for loops
},
loop: {
turnPulse: { duration: 2000 },
cpuThinking: { duration: 1500 },
initialFlipGlow: { duration: 1500 },
}
},
// Card manager specific
cardManager: {
flipDuration: 320, // Card flip animation
moveDuration: 300, // Card move animation
},
// V3_02: Dealing animation
dealing: {
shufflePause: 400, // Pause after shuffle sound
cardFlyTime: 150, // Time for card to fly to destination
cardStagger: 80, // Delay between cards
roundPause: 50, // Pause between deal rounds
discardFlipDelay: 200, // Pause before flipping discard
},
// V3_03: Round end reveal timing
reveal: {
voluntaryWindow: 2000, // Time for players to flip their own cards
initialPause: 250, // Pause before auto-reveals start
cardStagger: 50, // Between cards in same hand
playerPause: 200, // Pause after each player's reveal
highlightDuration: 100, // Player area highlight fade-in
},
// V3_04: Pair celebration
celebration: {
pairDuration: 200, // Celebration animation length
pairDelay: 25, // Slight delay before celebration
},
// V3_07: Score tallying animation
tally: {
initialPause: 100, // After reveal, before tally
cardHighlight: 70, // Duration to show each card value
columnPause: 30, // Between columns
pairCelebration: 200, // Pair cancel effect
playerPause: 50, // Between players
finalScoreReveal: 400, // Final score animation
},
// Opponent initial flip stagger (after dealing)
// All players flip concurrently within this window (not taking turns)
initialFlips: {
windowStart: 500, // Minimum delay before any opponent starts flipping
windowEnd: 2500, // Maximum delay before opponent starts (random in range)
cardStagger: 400, // Delay between an opponent's two card flips
},
// V3_11: Physical swap animation
swap: {
lift: 100, // Time to lift cards — visible pickup
arc: 320, // Time for arc travel
settle: 100, // Time to settle into place — with overshoot easing
},
// Draw animation durations (replaces hardcoded values in card-animations.js)
draw: {
deckLift: 120, // Lift off deck before travel
deckMove: 250, // Travel to holding position
deckRevealPause: 80, // Brief pause before flip (easing does the rest)
deckFlip: 320, // Flip to reveal drawn card
deckViewPause: 120, // Time to see revealed card
discardLift: 80, // Quick grab from discard
discardMove: 200, // Travel to holding position
discardViewPause: 60, // Brief settle after arrival
pulseDelay: 200, // Delay before card appears (pulse visible first)
},
// Player swap animation steps - smooth continuous motion
playerSwap: {
flipToReveal: 400, // Initial flip to show card
pauseAfterReveal: 50, // Tiny beat to register the card
moveToDiscard: 400, // Move old card to discard
pulseBeforeSwap: 0, // No pulse - just flow
completePause: 50, // Tiny settle
},
};
// Helper to get beat duration with variance
function getBeatDuration() {
const base = TIMING.beat.base;
const variance = TIMING.beat.variance;
return base + (Math.random() * variance * 2 - variance);
}
// Export for module systems, also attach to window for direct use
if (typeof module !== 'undefined' && module.exports) {
module.exports = TIMING;
}
if (typeof window !== 'undefined') {
window.TIMING = TIMING;
window.getBeatDuration = getBeatDuration;
}