A React Native app for the ultimate thinking partner.
1/**
2 * useRainbowAnimation Hook
3 *
4 * Manages the rainbow color cycling animation for the input box.
5 *
6 * Animation triggers when ANY of these conditions are true:
7 * - Streaming in progress (agent is responding)
8 * - Input is focused (user is typing)
9 * - Reasoning blocks are expanded (user viewing agent thoughts)
10 * - No messages yet (empty state / first time experience)
11 *
12 * Returns an Animated.Value that cycles through 6 rainbow colors
13 * over 3 seconds in a continuous loop.
14 *
15 * Color sequence: Red -> Yellow -> Green -> Blue -> Purple -> Red
16 */
17
18import { useEffect, useRef } from 'react';
19import { Animated } from 'react-native';
20
21interface UseRainbowAnimationParams {
22 isStreaming: boolean;
23 isInputFocused: boolean;
24 hasExpandedReasoning: boolean;
25 hasMessages: boolean;
26}
27
28export function useRainbowAnimation({
29 isStreaming,
30 isInputFocused,
31 hasExpandedReasoning,
32 hasMessages,
33}: UseRainbowAnimationParams) {
34 const rainbowAnimValue = useRef(new Animated.Value(0)).current;
35
36 // Determine if animation should be active
37 const shouldAnimate = isStreaming || isInputFocused || hasExpandedReasoning || !hasMessages;
38
39 useEffect(() => {
40 if (shouldAnimate) {
41 // Reset to start position
42 rainbowAnimValue.setValue(0);
43
44 // Create looping animation
45 const animation = Animated.loop(
46 Animated.timing(rainbowAnimValue, {
47 toValue: 1,
48 duration: 3000,
49 useNativeDriver: false, // Color interpolation requires false
50 })
51 );
52
53 animation.start();
54
55 return () => {
56 animation.stop();
57 };
58 } else {
59 // Stop animation when conditions no longer met
60 rainbowAnimValue.stopAnimation();
61 }
62 }, [shouldAnimate, rainbowAnimValue]);
63
64 return {
65 rainbowAnimValue,
66 isAnimating: shouldAnimate,
67 };
68}
69
70/**
71 * Rainbow color interpolation configuration
72 *
73 * Use with Animated.Text or Animated.View:
74 *
75 * color: rainbowAnimValue.interpolate({
76 * inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1],
77 * outputRange: RAINBOW_COLORS
78 * })
79 */
80export const RAINBOW_COLORS = [
81 '#FF6B6B', // Red
82 '#FFD93D', // Yellow
83 '#6BCF7F', // Green
84 '#4D96FF', // Blue
85 '#9D4EDD', // Purple
86 '#FF6B6B', // Red (loop back)
87] as const;