Initial commit

This commit is contained in:
2026-05-18 22:26:10 +02:00
commit db92ad2f88
34 changed files with 2158 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import { useEffect, useRef } from 'react'
import { useReducedMotion } from '../hooks/useReducedMotion'
interface Particle {
x: number
y: number
size: number
speedX: number
speedY: number
opacity: number
color: string
}
export default function FloatingParticles() {
const canvasRef = useRef<HTMLCanvasElement>(null)
const reducedMotion = useReducedMotion()
useEffect(() => {
if (reducedMotion) return
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
let animationId: number
let particles: Particle[] = []
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
resize()
window.addEventListener('resize', resize)
const colors = ['rgba(255, 45, 122, 0.15)', 'rgba(255, 107, 157, 0.12)', 'rgba(255, 182, 193, 0.1)']
const createParticles = () => {
particles = []
const count = Math.min(Math.floor(window.innerWidth / 15), 80)
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 3 + 1,
speedX: (Math.random() - 0.5) * 0.3,
speedY: (Math.random() - 0.5) * 0.3,
opacity: Math.random() * 0.5 + 0.1,
color: colors[Math.floor(Math.random() * colors.length)],
})
}
}
createParticles()
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
particles.forEach((p) => {
p.x += p.speedX
p.y += p.speedY
if (p.x < 0) p.x = canvas.width
if (p.x > canvas.width) p.x = 0
if (p.y < 0) p.y = canvas.height
if (p.y > canvas.height) p.y = 0
ctx.beginPath()
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2)
ctx.fillStyle = p.color
ctx.fill()
})
animationId = requestAnimationFrame(animate)
}
animate()
return () => {
cancelAnimationFrame(animationId)
window.removeEventListener('resize', resize)
}
}, [reducedMotion])
if (reducedMotion) return null
return (
<canvas
ref={canvasRef}
className="fixed inset-0 z-0 pointer-events-none"
style={{ opacity: 0.6 }}
/>
)
}