Skip to content

Commit f05ff6a

Browse files
committed
Add interactive bg
1 parent 3951154 commit f05ff6a

2 files changed

Lines changed: 88 additions & 88 deletions

File tree

src/components/InteractiveBackground.tsx

Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
import { 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+
513
const 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
}

src/components/ProjectsSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ const ProjectsSection = () => {
115115
</motion.div>
116116

117117
<motion.div
118-
className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-6 auto-rows-[250px]"
118+
className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-6 auto-rows-[minmax(280px,auto)]"
119119
variants={containerVariants}
120120
initial="hidden"
121121
animate={isInView ? "visible" : "hidden"}

0 commit comments

Comments
 (0)