1- import React , { useState , useEffect , useRef } from ' react' ;
1+ import React , { useState , useEffect , useRef } from " react" ;
22
33const SimpleCarnivalCursor = ( { children } ) => {
4- const [ mousePos , setMousePos ] = useState ( { x : 0 , y : 0 } ) ;
5- const [ isHovering , setIsHovering ] = useState ( false ) ;
6- const [ isClicking , setIsClicking ] = useState ( false ) ;
7- const [ isMobile , setIsMobile ] = useState ( false ) ;
8- const [ sparks , setSparks ] = useState ( [ ] ) ;
9- const sparksRef = useRef ( [ ] ) ;
10- const animationRef = useRef ( ) ;
11-
12- class Spark {
13- constructor ( x , y ) {
14- this . id = Math . random ( ) ;
15- this . x = x + ( Math . random ( ) - 0.5 ) * 20 ;
16- this . y = y + ( Math . random ( ) - 0.5 ) * 20 ;
17- this . vx = ( Math . random ( ) - 0.5 ) * 3 ;
18- this . vy = ( Math . random ( ) - 0.5 ) * 3 ;
19- this . life = 1 ;
20- this . decay = Math . random ( ) * 0.02 + 0.02 ;
21- this . size = Math . random ( ) * 3 + 2 ;
22- this . hue = Math . random ( ) * 60 + 15 ; // Yellow to orange range
4+ const [ mousePos , setMousePos ] = useState ( { x : 0 , y : 0 } ) ;
5+ const [ isHovering , setIsHovering ] = useState ( false ) ;
6+ const [ isClicking , setIsClicking ] = useState ( false ) ;
7+ const [ isMobile , setIsMobile ] = useState ( false ) ;
8+ const [ sparks , setSparks ] = useState ( [ ] ) ;
9+ const sparksRef = useRef ( [ ] ) ;
10+ const animationRef = useRef ( ) ;
11+
12+ class Spark {
13+ constructor ( x , y ) {
14+ this . id = Math . random ( ) ;
15+ this . x = x + ( Math . random ( ) - 0.5 ) * 20 ;
16+ this . y = y + ( Math . random ( ) - 0.5 ) * 20 ;
17+ this . vx = ( Math . random ( ) - 0.5 ) * 3 ;
18+ this . vy = ( Math . random ( ) - 0.5 ) * 3 ;
19+ this . life = 1 ;
20+ this . decay = Math . random ( ) * 0.02 + 0.02 ;
21+ this . size = Math . random ( ) * 3 + 2 ;
22+ this . hue = Math . random ( ) * 60 + 15 ; // Yellow to orange range
23+ }
24+
25+ update ( ) {
26+ this . x += this . vx ;
27+ this . y += this . vy ;
28+ this . life -= this . decay ;
29+ this . vx *= 0.99 ;
30+ this . vy *= 0.99 ;
31+ }
2332 }
2433
25- update ( ) {
26- this . x += this . vx ;
27- this . y += this . vy ;
28- this . life -= this . decay ;
29- this . vx *= 0.99 ;
30- this . vy *= 0.99 ;
34+ useEffect ( ( ) => {
35+ // Check if it's a mobile device
36+ setIsMobile ( "ontouchstart" in window ) ;
37+
38+ if ( isMobile ) return ;
39+
40+ const handleMouseMove = ( e ) => {
41+ setMousePos ( { x : e . clientX , y : e . clientY } ) ;
42+
43+ // Add sparks on mouse move
44+ if ( Math . random ( ) < 0.7 ) {
45+ sparksRef . current . push ( new Spark ( e . clientX , e . clientY ) ) ;
46+ }
47+ } ;
48+
49+ const handleMouseOver = ( e ) => {
50+ const isInteractive = e . target . closest (
51+ 'button, a, input, textarea, select, [role="button"]'
52+ ) ;
53+ setIsHovering ( ! ! isInteractive ) ;
54+ } ;
55+
56+ const handleMouseDown = ( ) => {
57+ setIsClicking ( true ) ;
58+ // Add more sparks on click
59+ for ( let i = 0 ; i < 8 ; i ++ ) {
60+ sparksRef . current . push ( new Spark ( mousePos . x , mousePos . y ) ) ;
61+ }
62+ } ;
63+
64+ const handleMouseUp = ( ) => setIsClicking ( false ) ;
65+
66+ const animateSparks = ( ) => {
67+ sparksRef . current = sparksRef . current . filter ( ( spark ) => {
68+ spark . update ( ) ;
69+ return spark . life > 0 ;
70+ } ) ;
71+
72+ setSparks ( [ ...sparksRef . current ] ) ;
73+ animationRef . current = requestAnimationFrame ( animateSparks ) ;
74+ } ;
75+
76+ document . addEventListener ( "mousemove" , handleMouseMove ) ;
77+ document . addEventListener ( "mouseover" , handleMouseOver ) ;
78+ document . addEventListener ( "mousedown" , handleMouseDown ) ;
79+ document . addEventListener ( "mouseup" , handleMouseUp ) ;
80+
81+ animateSparks ( ) ;
82+
83+ return ( ) => {
84+ document . removeEventListener ( "mousemove" , handleMouseMove ) ;
85+ document . removeEventListener ( "mouseover" , handleMouseOver ) ;
86+ document . removeEventListener ( "mousedown" , handleMouseDown ) ;
87+ document . removeEventListener ( "mouseup" , handleMouseUp ) ;
88+ if ( animationRef . current ) {
89+ cancelAnimationFrame ( animationRef . current ) ;
90+ }
91+ } ;
92+ } , [ isMobile , mousePos . x , mousePos . y ] ) ;
93+
94+ if ( isMobile ) {
95+ return < div > { children } </ div > ;
3196 }
32- }
33-
34- useEffect ( ( ) => {
35- // Check if it's a mobile device
36- setIsMobile ( 'ontouchstart' in window ) ;
37-
38- if ( isMobile ) return ;
39-
40- const handleMouseMove = ( e ) => {
41- setMousePos ( { x : e . clientX , y : e . clientY } ) ;
42-
43- // Add sparks on mouse move
44- if ( Math . random ( ) < 0.7 ) {
45- sparksRef . current . push ( new Spark ( e . clientX , e . clientY ) ) ;
46- }
47- } ;
48-
49- const handleMouseOver = ( e ) => {
50- const isInteractive = e . target . closest ( 'button, a, input, textarea, select, [role="button"]' ) ;
51- setIsHovering ( ! ! isInteractive ) ;
52- } ;
53-
54- const handleMouseDown = ( ) => {
55- setIsClicking ( true ) ;
56- // Add more sparks on click
57- for ( let i = 0 ; i < 8 ; i ++ ) {
58- sparksRef . current . push ( new Spark ( mousePos . x , mousePos . y ) ) ;
59- }
60- } ;
61-
62- const handleMouseUp = ( ) => setIsClicking ( false ) ;
63-
64- const animateSparks = ( ) => {
65- sparksRef . current = sparksRef . current . filter ( spark => {
66- spark . update ( ) ;
67- return spark . life > 0 ;
68- } ) ;
69-
70- setSparks ( [ ...sparksRef . current ] ) ;
71- animationRef . current = requestAnimationFrame ( animateSparks ) ;
72- } ;
73-
74- document . addEventListener ( 'mousemove' , handleMouseMove ) ;
75- document . addEventListener ( 'mouseover' , handleMouseOver ) ;
76- document . addEventListener ( 'mousedown' , handleMouseDown ) ;
77- document . addEventListener ( 'mouseup' , handleMouseUp ) ;
78-
79- animateSparks ( ) ;
80-
81- return ( ) => {
82- document . removeEventListener ( 'mousemove' , handleMouseMove ) ;
83- document . removeEventListener ( 'mouseover' , handleMouseOver ) ;
84- document . removeEventListener ( 'mousedown' , handleMouseDown ) ;
85- document . removeEventListener ( 'mouseup' , handleMouseUp ) ;
86- if ( animationRef . current ) {
87- cancelAnimationFrame ( animationRef . current ) ;
88- }
89- } ;
90- } , [ isMobile , mousePos . x , mousePos . y ] ) ;
91-
92- if ( isMobile ) {
93- return < div > { children } </ div > ;
94- }
95-
96- return (
97- < >
98- { /* Hide default cursor completely */ }
99- < style jsx > { `
97+
98+ return (
99+ < >
100+ { /* Hide default cursor completely */ }
101+ < style > { `
100102 *, *::before, *::after {
101103 cursor: none !important;
102104 }
103105 ` } </ style >
104-
105- < div className = "cursor-none" >
106- { children }
107- </ div >
108-
109- { /* Sparks */ }
110- { sparks . map ( spark => (
111- < div
112- key = { spark . id }
113- className = "fixed pointer-events-none z-[9998] rounded-full"
114- style = { {
115- left : spark . x - spark . size / 2 ,
116- top : spark . y - spark . size / 2 ,
117- width : spark . size ,
118- height : spark . size ,
119- opacity : spark . life ,
120- background : `hsl(${ spark . hue } , 100%, 60%)` ,
121- boxShadow : `0 0 ${ spark . size * 2 } px hsl(${ spark . hue } , 100%, 60%)` ,
122- transform : `scale(${ spark . life } )` ,
123- } }
124- />
125- ) ) }
126-
127- { /* Main cursor with glow effect */ }
128- < div
129- className = "fixed pointer-events-none z-[9999] transition-transform duration-100 ease-out"
130- style = { {
131- left : mousePos . x - 8 ,
132- top : mousePos . y - 8 ,
133- transform : `scale(${ isClicking ? 1.5 : isHovering ? 1.2 : 1 } )` ,
134- } }
135- >
136- < div
137- className = { `
106+
107+ < div className = "cursor-none" > { children } </ div >
108+
109+ { /* Sparks */ }
110+ { sparks . map ( ( spark ) => (
111+ < div
112+ key = { spark . id }
113+ className = "fixed pointer-events-none z-[9998] rounded-full"
114+ style = { {
115+ left : spark . x - spark . size / 2 ,
116+ top : spark . y - spark . size / 2 ,
117+ width : spark . size ,
118+ height : spark . size ,
119+ opacity : spark . life ,
120+ background : `hsl(${ spark . hue } , 100%, 60%)` ,
121+ boxShadow : `0 0 ${ spark . size * 2 } px hsl(${
122+ spark . hue
123+ } , 100%, 60%)`,
124+ transform : `scale(${ spark . life } )` ,
125+ } }
126+ />
127+ ) ) }
128+
129+ { /* Main cursor with glow effect */ }
130+ < div
131+ className = "fixed pointer-events-none z-[9999] transition-transform duration-100 ease-out"
132+ style = { {
133+ left : mousePos . x - 8 ,
134+ top : mousePos . y - 8 ,
135+ transform : `scale(${
136+ isClicking ? 1.5 : isHovering ? 1.2 : 1
137+ } )`,
138+ } }
139+ >
140+ < div
141+ className = { `
138142 w-4 h-4 rounded-full
139143 transition-all duration-200 ease-out
140- ${ isHovering ? ' animate-pulse' : '' }
141- ${ isClicking ? ' animate-ping' : '' }
144+ ${ isHovering ? " animate-pulse" : "" }
145+ ${ isClicking ? " animate-ping" : "" }
142146 ` }
143- style = { {
144- background : isClicking
145- ? ' radial-gradient(circle, #ffffff 0%, #ffff00 50%, #ff6600 100%)'
146- : isHovering
147- ? ' radial-gradient(circle, #ffffff 0%, #ffaa00 70%, #ff4400 100%)'
148- : ' radial-gradient(circle, #ffffff 0%, #ffdd00 60%, #ff8800 100%)' ,
149- boxShadow : `
147+ style = { {
148+ background : isClicking
149+ ? " radial-gradient(circle, #ffffff 0%, #ffff00 50%, #ff6600 100%)"
150+ : isHovering
151+ ? " radial-gradient(circle, #ffffff 0%, #ffaa00 70%, #ff4400 100%)"
152+ : " radial-gradient(circle, #ffffff 0%, #ffdd00 60%, #ff8800 100%)" ,
153+ boxShadow : `
150154 0 0 20px rgba(255, 255, 255, 0.8),
151155 0 0 40px rgba(255, 200, 0, 0.6),
152156 0 0 60px rgba(255, 100, 0, 0.4)
153- ${ isClicking ? ' , 0 0 80px rgba(255, 255, 255, 0.9)' : '' }
157+ ${ isClicking ? " , 0 0 80px rgba(255, 255, 255, 0.9)" : "" }
154158 ` ,
155- } }
156- />
157- </ div >
158- </ >
159- ) ;
159+ } }
160+ />
161+ </ div >
162+ </ >
163+ ) ;
160164} ;
161165
162- export default SimpleCarnivalCursor ;
166+ export default SimpleCarnivalCursor ;
0 commit comments