File size: 3,632 Bytes
5c85958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { useEffect, useState } from 'react';
import { useSpring, animated } from '@react-spring/web';

/**
 * Decorative shape component to be placed near specific UI elements
 * Position it relative to a parent container with position: relative
 */
export default function DecorativeShape({ 
  color = '#FF9500',
  size = 80,
  top,
  left,
  right,
  bottom,
  type = 'circle', // circle, square, ring, dot
  rotation = 0,
  opacity = 0.12,
  floatRange = 6,
  floatSpeed = 5000,
  scrollFactor = 0.03,
  zIndex = 0,
}) {
  const [floatOffset, setFloatOffset] = useState(0);
  const [scrollY, setScrollY] = useState(0);

  // Floating animation loop
  useEffect(() => {
    let startTime = Date.now();
    let animationFrame;

    const animate = () => {
      const elapsed = Date.now() - startTime;
      const offset = Math.sin((elapsed / floatSpeed) * Math.PI * 2) * floatRange;
      setFloatOffset(offset);
      animationFrame = requestAnimationFrame(animate);
    };

    animationFrame = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(animationFrame);
  }, [floatSpeed, floatRange]);

  // Scroll listener
  useEffect(() => {
    const handleScroll = () => {
      setScrollY(window.scrollY);
    };

    window.addEventListener('scroll', handleScroll, { passive: true });
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  // Spring animation for smooth movement
  const springProps = useSpring({
    transform: `translateY(${floatOffset + scrollY * scrollFactor}px) rotate(${rotation}deg)`,
    config: {
      mass: 2,
      tension: 120,
      friction: 30,
    },
  });

  const baseStyles = {
    position: 'absolute',
    top,
    left,
    right,
    bottom,
    width: size,
    height: size,
    pointerEvents: 'none',
    zIndex,
  };

  // Shape variants
  const shapeStyles = {
    circle: {
      borderRadius: '50%',
      background: color,
      opacity,
    },
    ring: {
      borderRadius: '50%',
      background: 'transparent',
      border: `2px solid ${color}`,
      opacity: opacity * 1.5,
    },
    square: {
      borderRadius: size * 0.15,
      background: color,
      opacity,
    },
    squareOutline: {
      borderRadius: size * 0.15,
      background: 'transparent',
      border: `2px solid ${color}`,
      opacity: opacity * 1.5,
    },
    dot: {
      borderRadius: '50%',
      background: color,
      opacity: opacity * 2,
    },
  };

  return (
    <animated.div
      style={{
        ...baseStyles,
        ...springProps,
        ...shapeStyles[type],
      }}
    />
  );
}

// Pre-configured shape combinations for common use cases
export function ShapeCluster({ position = 'topRight', colors = ['#FF9500', '#764ba2'] }) {
  const positions = {
    topRight: { top: -20, right: -30 },
    topLeft: { top: -20, left: -30 },
    bottomRight: { bottom: -20, right: -30 },
    bottomLeft: { bottom: -20, left: -30 },
  };

  const pos = positions[position];

  return (
    <>
      <DecorativeShape
        color={colors[0]}
        size={100}
        type="ring"
        opacity={0.1}
        floatRange={8}
        floatSpeed={5000}
        {...pos}
      />
      <DecorativeShape
        color={colors[1]}
        size={40}
        type="circle"
        opacity={0.15}
        floatRange={5}
        floatSpeed={4000}
        top={pos.top !== undefined ? pos.top + 60 : undefined}
        bottom={pos.bottom !== undefined ? pos.bottom + 60 : undefined}
        left={pos.left !== undefined ? pos.left + 80 : undefined}
        right={pos.right !== undefined ? pos.right + 80 : undefined}
      />
    </>
  );
}