22
33import { useEffect , useRef } from 'react'
44
5+ interface Particle {
6+ x : number
7+ y : number
8+ vx : number
9+ vy : number
10+ radius : number
11+ }
12+
513const InteractiveBackground = ( ) => {
614 const canvasRef = useRef < HTMLCanvasElement > ( null )
715
@@ -24,107 +32,99 @@ const InteractiveBackground = () => {
2432 }
2533 window . addEventListener ( 'resize' , resize )
2634
27- // Configuration for the 3D mesh
28- const cols = 40
29- const rows = 40
30- const scale = 40 // space between points
31- const meshWidth = cols * scale
32- const meshHeight = rows * scale
35+ // Configuration
36+ const particleCount = Math . floor ( ( width * height ) / 15000 )
37+ const connectionDistance = 150
38+ const particles : Particle [ ] = [ ]
3339
3440 // Mouse interaction
35- let mouseX = width / 2
36- let mouseY = height / 2
37- let targetMouseX = width / 2
38- let targetMouseY = height / 2
39-
41+ let mouse = { x : - 1000 , y : - 1000 }
4042 const handleMouseMove = ( e : MouseEvent ) => {
41- targetMouseX = e . clientX
42- targetMouseY = e . clientY
43+ mouse . x = e . clientX
44+ mouse . y = e . clientY
45+ }
46+ const handleMouseLeave = ( ) => {
47+ mouse . x = - 1000
48+ mouse . y = - 1000
4349 }
4450 window . addEventListener ( 'mousemove' , handleMouseMove )
45-
46- let time = 0
47-
48- // Simple pseudo-random noise function using trig
49- const noise = ( x : number , y : number , t : number ) => {
50- return Math . sin ( x * 0.05 + t ) * Math . cos ( y * 0.05 + t * 0.8 ) * 40 +
51- Math . sin ( x * 0.02 - t * 0.5 ) * Math . cos ( y * 0.03 + t ) * 60
51+ document . addEventListener ( 'mouseleave' , handleMouseLeave )
52+
53+ // Init particles
54+ for ( let i = 0 ; i < particleCount ; i ++ ) {
55+ particles . push ( {
56+ x : Math . random ( ) * width ,
57+ y : Math . random ( ) * height ,
58+ vx : ( Math . random ( ) - 0.5 ) * 0.5 ,
59+ vy : ( Math . random ( ) - 0.5 ) * 0.5 ,
60+ radius : Math . random ( ) * 1.5 + 0.5
61+ } )
5262 }
5363
5464 const animate = ( ) => {
55- ctx . fillStyle = '#000000'
65+ ctx . clearRect ( 0 , 0 , width , height )
66+
67+ // Draw a subtle gradient background
68+ const bgGradient = ctx . createRadialGradient ( width / 2 , height / 2 , 0 , width / 2 , height / 2 , width )
69+ bgGradient . addColorStop ( 0 , '#0a0a0f' )
70+ bgGradient . addColorStop ( 1 , '#000000' )
71+ ctx . fillStyle = bgGradient
5672 ctx . fillRect ( 0 , 0 , width , height )
5773
58- // Smooth mouse interpolation
59- mouseX += ( targetMouseX - mouseX ) * 0.05
60- mouseY += ( targetMouseY - mouseY ) * 0.05
61-
62- time += 0.015
63-
64- // Center the mesh horizontally and position it at the bottom
65- const startX = ( width - meshWidth ) / 2
66- const startY = height * 0.3 // Perspective shift
67-
68- ctx . strokeStyle = 'rgba(0, 229, 255, 0.15)' // Electric Cyan with low opacity
69- ctx . lineWidth = 1
70-
71- // Calculate 3D points
72- const points : { x : number , y : number , z : number } [ ] [ ] = [ ]
73-
74- for ( let y = 0 ; y < rows ; y ++ ) {
75- points [ y ] = [ ]
76- for ( let x = 0 ; x < cols ; x ++ ) {
77- // Calculate base elevation from pseudo-noise
78- let elevation = noise ( x * scale , y * scale , time )
79-
80- // Mouse interaction (repel/create a crater)
81- // Map mouse coordinates to grid space approximately
82- const gridMouseX = ( mouseX - startX ) / scale
83- const gridMouseY = ( ( mouseY - startY ) / scale ) * 2 // Perspective factor
84-
85- const dx = x - gridMouseX
86- const dy = y - gridMouseY
87- const dist = Math . sqrt ( dx * dx + dy * dy )
88-
89- if ( dist < 10 ) {
90- elevation -= ( 10 - dist ) * 15 // Sink down when mouse is near
91- }
92-
93- // Apply pseudo-isometric 3D projection
94- const px = startX + x * scale
95- // Compress the Y axis to simulate viewing angle, subtract elevation
96- const py = startY + ( y * scale ) * 0.4 - elevation + y * 10
97-
98- points [ y ] [ x ] = { x : px , y : py , z : elevation }
74+ // Update and draw particles
75+ for ( let i = 0 ; i < particles . length ; i ++ ) {
76+ const p = particles [ i ]
77+
78+ // Move
79+ p . x += p . vx
80+ p . y += p . vy
81+
82+ // Bounce off edges
83+ if ( p . x < 0 || p . x > width ) p . vx *= - 1
84+ if ( p . y < 0 || p . y > height ) p . vy *= - 1
85+
86+ // Mouse repulsion
87+ const dx = mouse . x - p . x
88+ const dy = mouse . y - p . y
89+ const dist = Math . sqrt ( dx * dx + dy * dy )
90+ if ( dist < 100 ) {
91+ const force = ( 100 - dist ) / 100
92+ p . x -= ( dx / dist ) * force * 2
93+ p . y -= ( dy / dist ) * force * 2
9994 }
100- }
10195
102- // Draw the wireframe grid
103- for ( let y = 0 ; y < rows - 1 ; y ++ ) {
96+ // Draw particle
10497 ctx . beginPath ( )
105- for ( let x = 0 ; x < cols ; x ++ ) {
106- const p = points [ y ] [ x ]
107-
108- // Dynamic color based on elevation
109- const colorIntensity = Math . max ( 0.1 , Math . min ( 1 , ( p . z + 100 ) / 150 ) )
110-
111- if ( x === 0 ) {
98+ ctx . arc ( p . x , p . y , p . radius , 0 , Math . PI * 2 )
99+ ctx . fillStyle = 'rgba(0, 229, 255, 0.8)'
100+ ctx . fill ( )
101+
102+ // Connect particles
103+ for ( let j = i + 1 ; j < particles . length ; j ++ ) {
104+ const p2 = particles [ j ]
105+ const dx2 = p . x - p2 . x
106+ const dy2 = p . y - p2 . y
107+ const dist2 = Math . sqrt ( dx2 * dx2 + dy2 * dy2 )
108+
109+ if ( dist2 < connectionDistance ) {
110+ ctx . beginPath ( )
112111 ctx . moveTo ( p . x , p . y )
113- } else {
114- ctx . lineTo ( p . x , p . y )
112+ ctx . lineTo ( p2 . x , p2 . y )
113+ const opacity = 1 - ( dist2 / connectionDistance )
114+ ctx . strokeStyle = `rgba(0, 229, 255, ${ opacity * 0.2 } )`
115+ ctx . lineWidth = 1
116+ ctx . stroke ( )
115117 }
116-
117- // Draw vertical connections
118- const pBottom = points [ y + 1 ] [ x ]
119- ctx . moveTo ( p . x , p . y )
120- ctx . lineTo ( pBottom . x , pBottom . y )
121-
122- // Only stroke periodically or with a gradient to save performance
123- // We apply stroke outside the loop for massive performance gain,
124- // but we can't change color per line segment this way.
125- // For simplicity and speed, we use a global stroke.
126118 }
127- ctx . stroke ( )
119+ }
120+
121+ // Add a large glowing aura around the mouse
122+ if ( mouse . x !== - 1000 ) {
123+ const mouseGlow = ctx . createRadialGradient ( mouse . x , mouse . y , 0 , mouse . x , mouse . y , 200 )
124+ mouseGlow . addColorStop ( 0 , 'rgba(0, 229, 255, 0.05)' )
125+ mouseGlow . addColorStop ( 1 , 'transparent' )
126+ ctx . fillStyle = mouseGlow
127+ ctx . fillRect ( 0 , 0 , width , height )
128128 }
129129
130130 requestAnimationFrame ( animate )
@@ -135,14 +135,14 @@ const InteractiveBackground = () => {
135135 return ( ) => {
136136 window . removeEventListener ( 'resize' , resize )
137137 window . removeEventListener ( 'mousemove' , handleMouseMove )
138+ document . removeEventListener ( 'mouseleave' , handleMouseLeave )
138139 }
139140 } , [ ] )
140141
141142 return (
142143 < canvas
143144 ref = { canvasRef }
144- className = "fixed inset-0 -z-10 pointer-events-none opacity-60"
145- style = { { filter : 'blur(0.5px)' } } // Adds a slight glowing softness
145+ className = "fixed inset-0 -z-10 pointer-events-none"
146146 />
147147 )
148148}
0 commit comments