| import React, { useEffect } from 'react'; | |
| import { StyleSheet, Text, View } from 'react-native'; | |
| import Animated, { | |
| useSharedValue, | |
| useAnimatedStyle, | |
| withSpring, | |
| withDelay, | |
| interpolate, | |
| } from 'react-native-reanimated'; | |
| import { COLORS } from '../constants/colors'; | |
| interface ScoreFlyoutProps { | |
| points: number; | |
| isCorrect: boolean; | |
| combo: number; | |
| visible: boolean; | |
| } | |
| export const ScoreFlyout: React.FC<ScoreFlyoutProps> = ({ | |
| points, | |
| isCorrect, | |
| combo, | |
| visible, | |
| }) => { | |
| const translateY = useSharedValue(0); | |
| const opacity = useSharedValue(0); | |
| const scale = useSharedValue(0.5); | |
| useEffect(() => { | |
| if (visible && points > 0) { | |
| translateY.value = 0; | |
| opacity.value = 0; | |
| scale.value = 0.5; | |
| opacity.value = withSpring(1, { damping: 10, stiffness: 200 }); | |
| scale.value = withSpring(1, { damping: 8, stiffness: 200 }); | |
| translateY.value = withDelay( | |
| 400, | |
| withSpring(-60, { damping: 12, stiffness: 60 }) | |
| ); | |
| opacity.value = withDelay(600, withSpring(0, { damping: 10 })); | |
| } | |
| }, [visible, points]); | |
| const animStyle = useAnimatedStyle(() => ({ | |
| opacity: opacity.value, | |
| transform: [ | |
| { translateY: translateY.value }, | |
| { scale: scale.value }, | |
| ], | |
| })); | |
| if (!visible || points === 0) return null; | |
| return ( | |
| <Animated.View style={[styles.container, animStyle]}> | |
| <Text style={[styles.points, { color: isCorrect ? COLORS.primary : COLORS.error }]}> | |
| {isCorrect ? `+${points}` : 'WRONG'} | |
| </Text> | |
| {isCorrect && combo > 1 && ( | |
| <Text style={styles.combo}>🔥 {combo}x COMBO</Text> | |
| )} | |
| </Animated.View> | |
| ); | |
| }; | |
| const styles = StyleSheet.create({ | |
| container: { | |
| position: 'absolute', | |
| top: '30%', | |
| alignSelf: 'center', | |
| alignItems: 'center', | |
| zIndex: 100, | |
| }, | |
| points: { | |
| fontSize: 36, | |
| fontWeight: '900', | |
| textShadowColor: 'rgba(0,0,0,0.5)', | |
| textShadowOffset: { width: 0, height: 2 }, | |
| textShadowRadius: 4, | |
| }, | |
| combo: { | |
| fontSize: 14, | |
| fontWeight: '800', | |
| color: COLORS.accent, | |
| marginTop: 4, | |
| letterSpacing: 1, | |
| }, | |
| }); | |