/ components / NessieAnimation.tsx
NessieAnimation.tsx
  1  // was used in the index but isn't anymore, not used rn
  2  
  3  import React, { useRef, useEffect, useCallback } from 'react';
  4  import { Animated, Easing, View, StyleSheet, ViewStyle } from 'react-native';
  5  
  6  const CYCLE_DURATION = 8000; // 8 seconds for the full cycle
  7  const MAX_RIGHT_DISTANCE = 80; // Maximum 80px to the right from starting position
  8  const MIN_DELAY = 8000; // Minimum 8 seconds delay
  9  const MAX_DELAY = 30000; // Maximum 30 seconds delay
 10  const MIN_WALK_DISTANCE = 80;
 11  const MAX_WALK_DISTANCE = 100;
 12  const BACKFLIP_DURATION = 1000; // 1 second for the backflip
 13  const BACKFLIP_CHANCE = 0.3; // 30% chance of backflip during delay
 14  
 15  interface NessieAnimationProps {
 16    style?: ViewStyle;
 17    imageSize?: number;
 18  }
 19  
 20  export const NessieAnimation: React.FC<NessieAnimationProps> = ({
 21    style,
 22    imageSize = 30,
 23  }) => {
 24    const progress = useRef(new Animated.Value(0)).current;
 25    const backflipRotation = useRef(new Animated.Value(0)).current;
 26    const walkDistance = useRef(0);
 27    const startPosition = useRef(0);
 28  
 29    const generateNewWalkCycle = useCallback(() => {
 30      const newWalkDistance = Math.floor(
 31        Math.random() * (MAX_WALK_DISTANCE - MIN_WALK_DISTANCE + 1) +
 32          MIN_WALK_DISTANCE
 33      );
 34      let newStartPosition = Math.max(
 35        0,
 36        Math.min(startPosition.current, MAX_RIGHT_DISTANCE - newWalkDistance)
 37      );
 38  
 39      walkDistance.current = newWalkDistance;
 40      startPosition.current = newStartPosition;
 41    }, []);
 42  
 43    const performBackflip = useCallback(() => {
 44      Animated.timing(backflipRotation, {
 45        toValue: 1,
 46        duration: BACKFLIP_DURATION,
 47        easing: Easing.linear,
 48        useNativeDriver: true,
 49      }).start(() => {
 50        backflipRotation.setValue(0);
 51      });
 52    }, [backflipRotation]);
 53  
 54    const animateWithDelay = useCallback(() => {
 55      const delay = Math.random() * (MAX_DELAY - MIN_DELAY) + MIN_DELAY;
 56  
 57      Animated.sequence([
 58        Animated.delay(delay),
 59        Animated.timing(progress, {
 60          toValue: 1,
 61          duration: CYCLE_DURATION,
 62          easing: Easing.linear,
 63          useNativeDriver: true,
 64        }),
 65      ]).start(({ finished }) => {
 66        if (finished) {
 67          progress.setValue(0);
 68          generateNewWalkCycle();
 69          if (Math.random() < BACKFLIP_CHANCE) {
 70            performBackflip();
 71          }
 72          animateWithDelay();
 73        }
 74      });
 75    }, [generateNewWalkCycle, progress, performBackflip]);
 76  
 77    useEffect(() => {
 78      generateNewWalkCycle();
 79      animateWithDelay();
 80  
 81      return () => {
 82        progress.stopAnimation();
 83        backflipRotation.stopAnimation();
 84      };
 85    }, [generateNewWalkCycle, animateWithDelay]);
 86  
 87    const translateX = progress.interpolate({
 88      inputRange: [0, 5 / 12, 1 / 2, 11 / 12, 1],
 89      outputRange: [
 90        startPosition.current,
 91        startPosition.current + walkDistance.current,
 92        startPosition.current + walkDistance.current,
 93        startPosition.current,
 94        startPosition.current,
 95      ],
 96    });
 97  
 98    const scaleX = progress.interpolate({
 99      inputRange: [0, 5 / 12, 5 / 12 + 0.001, 11 / 12, 11 / 12 + 0.001, 1],
100      outputRange: [1, 1, -1, -1, 1, 1],
101    });
102  
103    const translateY = progress.interpolate({
104      inputRange: [
105        0,
106        1 / 24,
107        1 / 12,
108        1 / 4,
109        5 / 12,
110        1 / 2,
111        17 / 24,
112        3 / 4,
113        11 / 12,
114        1,
115      ],
116      outputRange: [0, -2, 0, -2, 0, 0, -2, 0, -2, 0],
117      extrapolate: 'clamp',
118    });
119  
120    const rotateY = backflipRotation.interpolate({
121      inputRange: [0, 1],
122      outputRange: ['0deg', '360deg'],
123    });
124  
125    return (
126      <View style={[styles.container, style]}>
127        <Animated.Image
128          source={require('@/assets/images/nessie.png')}
129          style={[
130            styles.image,
131            {
132              width: imageSize,
133              height: imageSize,
134              transform: [
135                { translateX },
136                { translateY },
137                { scaleX },
138                { rotateY },
139              ],
140            },
141          ]}
142        />
143      </View>
144    );
145  };
146  
147  const styles = StyleSheet.create({
148    container: {
149      justifyContent: 'center',
150      alignItems: 'center',
151    },
152    image: {
153      resizeMode: 'contain',
154    },
155  });