An interactive hero section with animated particles that connect when close and react to mouse movement.
Harness the power of machine learning to create stunning, responsive interfaces. Move your mouse to interact with the particle field.
"use client"
import { useEffect, useRef } from "react"
interface Particle {
x: number
y: number
vx: number
vy: number
radius: number
color: string
}
export function HeroParticlesSection() {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")
if (!ctx) return
const resizeCanvas = () => {
canvas.width = canvas.offsetWidth * window.devicePixelRatio
canvas.height = canvas.offsetHeight * window.devicePixelRatio
ctx.scale(window.devicePixelRatio, window.devicePixelRatio)
}
resizeCanvas()
window.addEventListener("resize", resizeCanvas)
const colors = ["#6366f1", "#8b5cf6", "#d946ef", "#06b6d4"]
const particles: Particle[] = []
const particleCount = 80
let mouseX = 0
let mouseY = 0
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * canvas.offsetWidth,
y: Math.random() * canvas.offsetHeight,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
radius: Math.random() * 2 + 1,
color: colors[Math.floor(Math.random() * colors.length)],
})
}
const handleMouseMove = (e: MouseEvent) => {
const rect = canvas.getBoundingClientRect()
mouseX = e.clientX - rect.left
mouseY = e.clientY - rect.top
}
canvas.addEventListener("mousemove", handleMouseMove)
const animate = () => {
ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight)
particles.forEach((particle, i) => {
particle.x += particle.vx
particle.y += particle.vy
if (particle.x < 0 || particle.x > canvas.offsetWidth) particle.vx *= -1
if (particle.y < 0 || particle.y > canvas.offsetHeight) particle.vy *= -1
ctx.beginPath()
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2)
ctx.fillStyle = particle.color
ctx.fill()
particles.slice(i + 1).forEach((other) => {
const dx = particle.x - other.x
const dy = particle.y - other.y
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < 120) {
ctx.beginPath()
ctx.moveTo(particle.x, particle.y)
ctx.lineTo(other.x, other.y)
ctx.strokeStyle = `rgba(99, 102, 241, ${1 - distance / 120})`
ctx.lineWidth = 0.5
ctx.stroke()
}
})
const dx = mouseX - particle.x
const dy = mouseY - particle.y
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < 150) {
particle.x -= dx * 0.02
particle.y -= dy * 0.02
}
})
requestAnimationFrame(animate)
}
animate()
return () => {
window.removeEventListener("resize", resizeCanvas)
canvas.removeEventListener("mousemove", handleMouseMove)
}
}, [])
return (
<section className="relative min-h-screen w-full overflow-hidden bg-zinc-950">
<canvas ref={canvasRef} className="absolute inset-0 h-full w-full" />
<div className="absolute inset-0 bg-gradient-to-b from-zinc-950/80 via-transparent to-zinc-950/80" />
<div className="relative z-10 flex min-h-screen flex-col items-center justify-center px-4 text-center">
{/* Content */}
</div>
</section>
)
}