11"use client" ;
22
3- import { useEffect , useRef } from "react" ;
4- import { motion , useMotionValue , useSpring , useTransform } from "framer-motion" ;
3+ import { useEffect , useRef , useState } from "react" ;
4+ import { motion , useMotionValue , useSpring } from "framer-motion" ;
55
66interface Particle {
77 id : number ;
@@ -24,22 +24,36 @@ const FloatingParticles = () => {
2424 const mouseYSpring = useSpring ( mouseY , { damping : 25 , stiffness : 150 } ) ;
2525 const scrollYSpring = useSpring ( scrollY , { damping : 30 , stiffness : 100 } ) ;
2626
27- // Create particles array
28- const particles : Particle [ ] = Array . from ( { length : 15 } , ( _ , i ) => ( {
29- id : i ,
30- x : Math . random ( ) * 100 ,
31- y : Math . random ( ) * 100 ,
32- size : Math . random ( ) * 8 + 4 ,
33- color : [
27+ // State for particles - generated only on client
28+ const [ particles , setParticles ] = useState < Particle [ ] > ( [ ] ) ;
29+ const [ isMounted , setIsMounted ] = useState ( false ) ;
30+
31+ // Generate particles on client side only
32+ useEffect ( ( ) => {
33+ const colors = [
3434 "rgba(147, 71, 255, 0.4)" , // Purple
3535 "rgba(2, 255, 226, 0.3)" , // Cyan
3636 "rgba(106, 120, 242, 0.3)" , // Blue
3737 "rgba(255, 255, 255, 0.2)" , // White
38- ] [ Math . floor ( Math . random ( ) * 4 ) ] ,
39- opacity : Math . random ( ) * 0.5 + 0.2 ,
40- speed : Math . random ( ) * 0.5 + 0.2 ,
41- direction : Math . random ( ) * Math . PI * 2 ,
42- } ) ) ;
38+ ] ;
39+
40+ const generatedParticles : Particle [ ] = Array . from ( { length : 15 } , ( _ , i ) => {
41+ const randomColorIndex = Math . floor ( Math . random ( ) * colors . length ) ;
42+ return {
43+ id : i ,
44+ x : Math . random ( ) * 100 ,
45+ y : Math . random ( ) * 100 ,
46+ size : Math . random ( ) * 8 + 4 ,
47+ color : colors [ randomColorIndex ] ! ,
48+ opacity : Math . random ( ) * 0.5 + 0.2 ,
49+ speed : Math . random ( ) * 0.5 + 0.2 ,
50+ direction : Math . random ( ) * Math . PI * 2 ,
51+ } ;
52+ } ) ;
53+
54+ setParticles ( generatedParticles ) ;
55+ setIsMounted ( true ) ;
56+ } , [ ] ) ;
4357
4458 useEffect ( ( ) => {
4559 const handleMouseMove = ( e : MouseEvent ) => {
@@ -65,130 +79,118 @@ const FloatingParticles = () => {
6579 } ;
6680 } , [ mouseX , mouseY , scrollY ] ) ;
6781
82+ // Don't render particles until mounted on client
83+ if ( ! isMounted ) {
84+ return (
85+ < div
86+ ref = { containerRef }
87+ className = "pointer-events-none absolute inset-0 overflow-hidden"
88+ style = { { zIndex : 1 } }
89+ />
90+ ) ;
91+ }
92+
6893 return (
6994 < div
7095 ref = { containerRef }
7196 className = "pointer-events-none absolute inset-0 overflow-hidden"
7297 style = { { zIndex : 1 } }
7398 >
74- { particles . map ( ( particle ) => {
75- const mouseInfluenceX = useTransform (
76- mouseXSpring ,
77- [ 0 , 1 ] ,
78- [ - 20 , 20 ]
79- ) ;
80- const mouseInfluenceY = useTransform (
81- mouseYSpring ,
82- [ 0 , 1 ] ,
83- [ - 20 , 20 ]
84- ) ;
85- const scrollInfluence = useTransform (
86- scrollYSpring ,
87- [ 0 , 1000 ] ,
88- [ 0 , - 100 ]
89- ) ;
90-
91- return (
99+ { particles . map ( ( particle ) => (
100+ < motion . div
101+ key = { particle . id }
102+ className = "absolute rounded-full"
103+ style = { {
104+ width : particle . size ,
105+ height : particle . size ,
106+ backgroundColor : particle . color ,
107+ left : `${ particle . x } %` ,
108+ top : `${ particle . y } %` ,
109+ opacity : particle . opacity ,
110+ } }
111+ animate = { {
112+ x : [ 0 , Math . cos ( particle . direction ) * 30 , 0 ] ,
113+ y : [ 0 , Math . sin ( particle . direction ) * 30 , 0 ] ,
114+ scale : [ 1 , 1.2 , 1 ] ,
115+ opacity : [ particle . opacity , particle . opacity * 0.5 , particle . opacity ] ,
116+ } }
117+ transition = { {
118+ duration : 8 + particle . speed * 4 ,
119+ repeat : Infinity ,
120+ ease : "easeInOut" ,
121+ delay : particle . id * 0.2 ,
122+ } }
123+ >
124+ { /* Inner glow effect */ }
92125 < motion . div
93- key = { particle . id }
94- className = "absolute rounded-full"
126+ className = "absolute inset-0 rounded-full"
95127 style = { {
96- width : particle . size ,
97- height : particle . size ,
98- backgroundColor : particle . color ,
99- left : `${ particle . x } %` ,
100- top : `${ particle . y } %` ,
101- x : mouseInfluenceX ,
102- y : [ mouseInfluenceY , scrollInfluence ] ,
103- opacity : particle . opacity ,
128+ background : `radial-gradient(circle, ${ particle . color } 0%, transparent 70%)` ,
129+ filter : "blur(2px)" ,
104130 } }
105131 animate = { {
106- x : [ 0 , Math . cos ( particle . direction ) * 30 , 0 ] ,
107- y : [ 0 , Math . sin ( particle . direction ) * 30 , 0 ] ,
108- scale : [ 1 , 1.2 , 1 ] ,
109- opacity : [ particle . opacity , particle . opacity * 0.5 , particle . opacity ] ,
132+ scale : [ 1 , 1.5 , 1 ] ,
133+ opacity : [ 0.8 , 0.3 , 0.8 ] ,
110134 } }
111135 transition = { {
112- duration : 8 + particle . speed * 4 ,
136+ duration : 4 + particle . speed * 2 ,
113137 repeat : Infinity ,
114138 ease : "easeInOut" ,
115- delay : particle . id * 0.2 ,
139+ delay : particle . id * 0.1 ,
116140 } }
117- >
118- { /* Inner glow effect */ }
119- < motion . div
120- className = "absolute inset-0 rounded-full"
121- style = { {
122- background : `radial-gradient(circle, ${ particle . color } 0%, transparent 70%)` ,
123- filter : "blur(2px)" ,
124- } }
125- animate = { {
126- scale : [ 1 , 1.5 , 1 ] ,
127- opacity : [ 0.8 , 0.3 , 0.8 ] ,
128- } }
129- transition = { {
130- duration : 4 + particle . speed * 2 ,
131- repeat : Infinity ,
132- ease : "easeInOut" ,
133- delay : particle . id * 0.1 ,
134- } }
135- />
136- </ motion . div >
137- ) ;
138- } ) }
141+ />
142+ </ motion . div >
143+ ) ) }
139144
140145 { /* Additional floating orbs with mouse interaction */ }
141- { Array . from ( { length : 8 } , ( _ , i ) => {
142- const orbMouseX = useTransform (
143- mouseXSpring ,
144- [ 0 , 1 ] ,
145- [ i * 30 - 120 , i * 30 + 120 ]
146- ) ;
147- const orbMouseY = useTransform (
148- mouseYSpring ,
149- [ 0 , 1 ] ,
150- [ i * 20 - 80 , i * 20 + 80 ]
151- ) ;
152-
153- return (
154- < motion . div
155- key = { `orb-${ i } ` }
156- className = "absolute"
146+ { Array . from ( { length : 8 } , ( _ , i ) => (
147+ < motion . div
148+ key = { `orb-${ i } ` }
149+ className = "absolute"
150+ style = { {
151+ left : `${ 20 + i * 10 } %` ,
152+ top : `${ 30 + ( i % 3 ) * 20 } %` ,
153+ } }
154+ animate = { {
155+ x : [ 0 , ( i * 30 - 120 ) , ( i * 30 + 120 ) , 0 ] ,
156+ y : [ 0 , ( i * 20 - 80 ) , ( i * 20 + 80 ) , 0 ] ,
157+ rotate : [ 0 , 360 ] ,
158+ scale : [ 1 , 1.1 , 1 ] ,
159+ } }
160+ transition = { {
161+ x : {
162+ duration : 15 + i * 2 ,
163+ repeat : Infinity ,
164+ ease : "easeInOut" ,
165+ } ,
166+ y : {
167+ duration : 12 + i * 2 ,
168+ repeat : Infinity ,
169+ ease : "easeInOut" ,
170+ } ,
171+ rotate : {
172+ duration : 20 + i * 2 ,
173+ repeat : Infinity ,
174+ ease : "linear" ,
175+ } ,
176+ scale : {
177+ duration : 6 + i ,
178+ repeat : Infinity ,
179+ ease : "easeInOut" ,
180+ } ,
181+ } }
182+ >
183+ < div
184+ className = "h-3 w-3 rounded-full"
157185 style = { {
158- left : `${ 20 + i * 10 } %` ,
159- top : `${ 30 + ( i % 3 ) * 20 } %` ,
160- x : orbMouseX ,
161- y : orbMouseY ,
162- } }
163- animate = { {
164- rotate : [ 0 , 360 ] ,
165- scale : [ 1 , 1.1 , 1 ] ,
166- } }
167- transition = { {
168- rotate : {
169- duration : 20 + i * 2 ,
170- repeat : Infinity ,
171- ease : "linear" ,
172- } ,
173- scale : {
174- duration : 6 + i ,
175- repeat : Infinity ,
176- ease : "easeInOut" ,
177- } ,
186+ background : `radial-gradient(circle, ${
187+ [ "rgba(147, 71, 255, 0.6)" , "rgba(2, 255, 226, 0.4)" ] [ i % 2 ]
188+ } 0%, transparent 70%)`,
189+ filter : "blur(1px)" ,
178190 } }
179- >
180- < div
181- className = "h-3 w-3 rounded-full"
182- style = { {
183- background : `radial-gradient(circle, ${
184- [ "rgba(147, 71, 255, 0.6)" , "rgba(2, 255, 226, 0.4)" ] [ i % 2 ]
185- } 0%, transparent 70%)`,
186- filter : "blur(1px)" ,
187- } }
188- />
189- </ motion . div >
190- ) ;
191- } ) }
191+ />
192+ </ motion . div >
193+ ) ) }
192194
193195 { /* Ambient light orbs */ }
194196 < motion . div
0 commit comments