feat: add CSS animations and visual feedback system
Added complete animation system with Panda CSS: 1. Panda CSS Keyframe Animations (panda.config.ts): - shake: Horizontal oscillation for errors (-5px to 5px) - successPulse: Gentle scale for correct answers (1 to 1.05) - errorShake: Stronger shake for errors (-10px to 10px) - pulse: Continuous breathing effect (1 to 1.05) - bounce: Vertical bounce (0 to -10px) - bounceIn: Entry animation with scale and rotate - glow: Expanding box shadow effect All animations match exact timing and easing from original Python implementation (web_generator.py lines 1996-6274) 2. Visual Feedback Integration (GameDisplay.tsx): - Trigger successPulse animation on correct answers - Trigger errorShake animation on incorrect answers - Auto-clear animations after 500ms (matching duration) - Applied to answer input element for clear visual feedback - Synchronized with sound effects for multi-sensory feedback Animations enhance user experience by providing immediate visual confirmation of answer correctness alongside audio cues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,60 @@ export default defineConfig({
|
||||
shadows: {
|
||||
card: { value: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)' },
|
||||
modal: { value: '0 25px 50px -12px rgba(0, 0, 0, 0.25)' }
|
||||
},
|
||||
animations: {
|
||||
// Shake animation for errors (web_generator.py line 3419)
|
||||
shake: { value: 'shake 0.5s ease-in-out' },
|
||||
// Pulse animation for success feedback (line 2004)
|
||||
successPulse: { value: 'successPulse 0.5s ease' },
|
||||
pulse: { value: 'pulse 2s infinite' },
|
||||
// Error shake with larger amplitude (line 2009)
|
||||
errorShake: { value: 'errorShake 0.5s ease' },
|
||||
// Bounce animations (line 6271, 5065)
|
||||
bounce: { value: 'bounce 1s infinite alternate' },
|
||||
bounceIn: { value: 'bounceIn 1s ease-out' },
|
||||
// Glow animation (line 6260)
|
||||
glow: { value: 'glow 1s ease-in-out infinite alternate' }
|
||||
}
|
||||
},
|
||||
keyframes: {
|
||||
// Shake - horizontal oscillation for errors (line 3419)
|
||||
shake: {
|
||||
'0%, 100%': { transform: 'translateX(0)' },
|
||||
'25%': { transform: 'translateX(-5px)' },
|
||||
'75%': { transform: 'translateX(5px)' }
|
||||
},
|
||||
// Success pulse - gentle scale for correct answers (line 2004)
|
||||
successPulse: {
|
||||
'0%, 100%': { transform: 'scale(1)' },
|
||||
'50%': { transform: 'scale(1.05)' }
|
||||
},
|
||||
// Pulse - continuous breathing effect (line 6255)
|
||||
pulse: {
|
||||
'0%, 100%': { transform: 'scale(1)' },
|
||||
'50%': { transform: 'scale(1.05)' }
|
||||
},
|
||||
// Error shake - stronger horizontal oscillation (line 2009)
|
||||
errorShake: {
|
||||
'0%, 100%': { transform: 'translateX(0)' },
|
||||
'25%': { transform: 'translateX(-10px)' },
|
||||
'75%': { transform: 'translateX(10px)' }
|
||||
},
|
||||
// Bounce - vertical oscillation (line 6271)
|
||||
bounce: {
|
||||
'0%, 100%': { transform: 'translateY(0)' },
|
||||
'50%': { transform: 'translateY(-10px)' }
|
||||
},
|
||||
// Bounce in - entry animation with scale and rotate (line 6265)
|
||||
bounceIn: {
|
||||
'0%': { transform: 'scale(0.3) rotate(-10deg)', opacity: '0' },
|
||||
'50%': { transform: 'scale(1.1) rotate(5deg)' },
|
||||
'100%': { transform: 'scale(1) rotate(0deg)', opacity: '1' }
|
||||
},
|
||||
// Glow - expanding box shadow (line 6260)
|
||||
glow: {
|
||||
'0%': { boxShadow: '0 0 5px rgba(255, 255, 255, 0.5)' },
|
||||
'100%': { boxShadow: '0 0 20px rgba(255, 255, 255, 0.8), 0 0 30px rgba(255, 255, 255, 0.6)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useComplementRace } from '../context/ComplementRaceContext'
|
||||
import { useAIRacers } from '../hooks/useAIRacers'
|
||||
import { useAdaptiveDifficulty } from '../hooks/useAdaptiveDifficulty'
|
||||
@@ -12,12 +12,25 @@ import { SteamTrainJourney } from './RaceTrack/SteamTrainJourney'
|
||||
import { RouteCelebration } from './RouteCelebration'
|
||||
import { generatePassengers } from '../lib/passengerGenerator'
|
||||
|
||||
type FeedbackAnimation = 'correct' | 'incorrect' | null
|
||||
|
||||
export function GameDisplay() {
|
||||
const { state, dispatch } = useComplementRace()
|
||||
useAIRacers() // Activate AI racer updates (not used in sprint mode)
|
||||
const { trackPerformance, getAdaptiveFeedbackMessage } = useAdaptiveDifficulty()
|
||||
const { boostMomentum } = useSteamJourney()
|
||||
const { playSound } = useSoundEffects()
|
||||
const [feedbackAnimation, setFeedbackAnimation] = useState<FeedbackAnimation>(null)
|
||||
|
||||
// Clear feedback animation after it plays (line 1996, 2001)
|
||||
useEffect(() => {
|
||||
if (feedbackAnimation) {
|
||||
const timer = setTimeout(() => {
|
||||
setFeedbackAnimation(null)
|
||||
}, 500) // Match animation duration
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [feedbackAnimation])
|
||||
|
||||
// Show adaptive feedback with auto-hide
|
||||
useEffect(() => {
|
||||
@@ -70,6 +83,9 @@ export function GameDisplay() {
|
||||
dispatch({ type: 'SUBMIT_ANSWER', answer })
|
||||
trackPerformance(true, responseTime)
|
||||
|
||||
// Trigger correct answer animation (line 1996)
|
||||
setFeedbackAnimation('correct')
|
||||
|
||||
// Play appropriate sound based on performance (from web_generator.py lines 11530-11542)
|
||||
const newStreak = state.streak + 1
|
||||
if (newStreak > 0 && newStreak % 5 === 0) {
|
||||
@@ -117,6 +133,9 @@ export function GameDisplay() {
|
||||
// Incorrect answer
|
||||
trackPerformance(false, responseTime)
|
||||
|
||||
// Trigger incorrect answer animation (line 2001)
|
||||
setFeedbackAnimation('incorrect')
|
||||
|
||||
// Play incorrect sound (from web_generator.py line 11589)
|
||||
playSound('incorrect')
|
||||
|
||||
@@ -319,7 +338,12 @@ export function GameDisplay() {
|
||||
padding: '16px 36px',
|
||||
boxShadow: '0 4px 20px rgba(59, 130, 246, 0.3)',
|
||||
textAlign: 'center',
|
||||
minWidth: '160px'
|
||||
minWidth: '160px',
|
||||
animation: feedbackAnimation === 'correct'
|
||||
? 'successPulse 0.5s ease'
|
||||
: feedbackAnimation === 'incorrect'
|
||||
? 'errorShake 0.5s ease'
|
||||
: undefined
|
||||
}}>
|
||||
<div data-element="input-value" style={{
|
||||
fontSize: '60px',
|
||||
|
||||
Reference in New Issue
Block a user