22
33import { useEffect , useState , useCallback , useRef } from "react" ;
44import { motion , AnimatePresence } from "framer-motion" ;
5- import { useIsMobile } from "@/hooks" ;
5+ import { usePerformanceOptimizations , useHapticFeedback } from "@/hooks" ;
66
77interface Particle {
88 id : number ;
@@ -40,10 +40,14 @@ export default function ClickExplosion() {
4040 const [ explosions , setExplosions ] = useState < Explosion [ ] > ( [ ] ) ;
4141 const [ mounted , setMounted ] = useState ( false ) ;
4242 const [ isWarmedUp , setIsWarmedUp ] = useState ( false ) ;
43- const isMobile = useIsMobile ( ) ;
43+ const perf = usePerformanceOptimizations ( ) ;
44+ const haptic = useHapticFeedback ( ) ;
4445 // Track if we've done the initial "warm-up" animation
4546 const warmupDoneRef = useRef ( false ) ;
4647
48+ // Determine if we're in a low-performance mode
49+ const isLowPerformance = perf . level === "low" || perf . level === "ultra-low" || perf . level === "medium" ;
50+
4751 useEffect ( ( ) => {
4852 setMounted ( true ) ;
4953
@@ -60,28 +64,34 @@ export default function ClickExplosion() {
6064 } , [ ] ) ;
6165
6266 const createExplosion = useCallback ( ( x : number , y : number ) => {
63- // Significantly fewer particles on mobile (6-8 vs 12-20)
64- const particleCount = isMobile
65- ? 6 + Math . floor ( Math . random ( ) * 3 ) // 6-8 particles on mobile
66- : 12 + Math . floor ( Math . random ( ) * 8 ) ; // 12-20 particles on desktop
67+ // Skip explosions entirely on ultra-low performance
68+ if ( perf . level === "ultra-low" ) return ;
69+
70+ // Trigger haptic feedback on click
71+ haptic . tap ( ) ;
72+
73+ // Adjust particle count based on performance level
74+ const particleCount = isLowPerformance
75+ ? 6 + Math . floor ( Math . random ( ) * 3 ) // 6-8 particles on low performance
76+ : 12 + Math . floor ( Math . random ( ) * 8 ) ; // 12-20 particles on high performance
6777
6878 const particles : Particle [ ] = [ ] ;
69- const colors = isMobile ? PARTICLE_COLORS_MOBILE : PARTICLE_COLORS ;
79+ const colors = isLowPerformance ? PARTICLE_COLORS_MOBILE : PARTICLE_COLORS ;
7080
7181 for ( let i = 0 ; i < particleCount ; i ++ ) {
7282 particles . push ( {
7383 id : i ,
7484 x : 0 ,
7585 y : 0 ,
7686 angle : ( Math . PI * 2 * i ) / particleCount + ( Math . random ( ) - 0.5 ) * 0.5 ,
77- // Shorter distance on mobile for snappier feel
78- velocity : isMobile
79- ? 50 + Math . random ( ) * 60 // 50-110px on mobile
80- : 80 + Math . random ( ) * 120 , // 80-200px on desktop
81- // Slightly smaller particles on mobile
82- size : isMobile
83- ? 2 + Math . random ( ) * 2 // 2-4px on mobile
84- : 2 + Math . random ( ) * 4 , // 2-6px on desktop
87+ // Shorter distance on low performance for snappier feel
88+ velocity : isLowPerformance
89+ ? 50 + Math . random ( ) * 60 // 50-110px on low performance
90+ : 80 + Math . random ( ) * 120 , // 80-200px on high performance
91+ // Slightly smaller particles on low performance
92+ size : isLowPerformance
93+ ? 2 + Math . random ( ) * 2 // 2-4px on low performance
94+ : 2 + Math . random ( ) * 4 , // 2-6px on high performance
8595 color : colors [ Math . floor ( Math . random ( ) * colors . length ) ] ,
8696 } ) ;
8797 }
@@ -95,11 +105,11 @@ export default function ClickExplosion() {
95105
96106 setExplosions ( prev => [ ...prev , explosion ] ) ;
97107
98- // Remove explosion after animation (shorter on mobile )
108+ // Remove explosion after animation (shorter on low performance )
99109 setTimeout ( ( ) => {
100110 setExplosions ( prev => prev . filter ( e => e . id !== explosion . id ) ) ;
101- } , isMobile ? 400 : 600 ) ;
102- } , [ isMobile ] ) ;
111+ } , isLowPerformance ? 400 : 600 ) ;
112+ } , [ perf . level , isLowPerformance , haptic ] ) ;
103113
104114 useEffect ( ( ) => {
105115 if ( ! mounted ) return ;
@@ -141,29 +151,29 @@ export default function ClickExplosion() {
141151 willChange : "transform" ,
142152 } }
143153 >
144- { /* Central flash - smaller on mobile */ }
154+ { /* Central flash - smaller on low performance */ }
145155 < motion . div
146156 className = "absolute -translate-x-1/2 -translate-y-1/2 rounded-full"
147157 style = { {
148158 willChange : "width, height, opacity" ,
149159 transform : "translateZ(0)" ,
150160 } }
151161 initial = { {
152- width : isMobile ? 8 : 10 ,
153- height : isMobile ? 8 : 10 ,
162+ width : isLowPerformance ? 8 : 10 ,
163+ height : isLowPerformance ? 8 : 10 ,
154164 opacity : 1 ,
155165 background : "radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(255,69,0,0.8) 50%, transparent 100%)"
156166 } }
157167 animate = { {
158- width : isMobile ? 25 : 40 ,
159- height : isMobile ? 25 : 40 ,
168+ width : isLowPerformance ? 25 : 40 ,
169+ height : isLowPerformance ? 25 : 40 ,
160170 opacity : 0
161171 } }
162172 exit = { { opacity : 0 } }
163- transition = { { duration : isMobile ? 0.2 : 0.3 , ease : "easeOut" } }
173+ transition = { { duration : isLowPerformance ? 0.2 : 0.3 , ease : "easeOut" } }
164174 />
165175
166- { /* Particles - no boxShadow on mobile for better performance */ }
176+ { /* Particles - no boxShadow on low performance for better performance */ }
167177 { explosion . particles . map ( particle => (
168178 < motion . div
169179 key = { particle . id }
@@ -172,8 +182,8 @@ export default function ClickExplosion() {
172182 width : particle . size ,
173183 height : particle . size ,
174184 backgroundColor : particle . color ,
175- // Skip expensive boxShadow on mobile
176- boxShadow : isMobile ? undefined : `0 0 ${ particle . size * 2 } px ${ particle . color } ` ,
185+ // Skip expensive boxShadow on low performance
186+ boxShadow : perf . enableBoxShadow ? `0 0 ${ particle . size * 2 } px ${ particle . color } ` : undefined ,
177187 left : - particle . size / 2 ,
178188 top : - particle . size / 2 ,
179189 willChange : "transform, opacity" ,
@@ -194,28 +204,28 @@ export default function ClickExplosion() {
194204 } }
195205 exit = { { opacity : 0 } }
196206 transition = { {
197- // Shorter, more consistent duration on mobile
198- duration : isMobile ? 0.25 : 0.4 + Math . random ( ) * 0.2 ,
207+ // Shorter, more consistent duration on low performance
208+ duration : isLowPerformance ? 0.25 : 0.4 + Math . random ( ) * 0.2 ,
199209 ease : [ 0.25 , 0.46 , 0.45 , 0.94 ] , // easeOutQuad
200210 } }
201211 />
202212 ) ) }
203213
204- { /* Ring expand effect - smaller on mobile */ }
214+ { /* Ring expand effect - smaller on low performance */ }
205215 < motion . div
206216 className = "absolute -translate-x-1/2 -translate-y-1/2 rounded-full border border-signal/50"
207217 style = { {
208218 willChange : "width, height, opacity" ,
209219 transform : "translateZ(0)" ,
210220 } }
211- initial = { { width : 0 , height : 0 , opacity : isMobile ? 0.6 : 0.8 } }
212- animate = { { width : isMobile ? 40 : 60 , height : isMobile ? 40 : 60 , opacity : 0 } }
221+ initial = { { width : 0 , height : 0 , opacity : isLowPerformance ? 0.6 : 0.8 } }
222+ animate = { { width : isLowPerformance ? 40 : 60 , height : isLowPerformance ? 40 : 60 , opacity : 0 } }
213223 exit = { { opacity : 0 } }
214- transition = { { duration : isMobile ? 0.25 : 0.4 , ease : "easeOut" } }
224+ transition = { { duration : isLowPerformance ? 0.25 : 0.4 , ease : "easeOut" } }
215225 />
216226
217- { /* Spark lines - skip on mobile for better performance */ }
218- { ! isMobile && [ 0 , 1 , 2 , 3 ] . map ( i => (
227+ { /* Spark lines - skip on low performance for better performance */ }
228+ { perf . enableComplexAnimations && [ 0 , 1 , 2 , 3 ] . map ( i => (
219229 < motion . div
220230 key = { `spark-${ i } ` }
221231 className = "absolute bg-signal/80"
0 commit comments