'use client' import { useEffect, useRef } from 'react' import * as THREE from 'three' export function ThreeBackground() { const canvasRef = useRef(null) useEffect(() => { if (!canvasRef.current) return let animationId: number let cleanup: (() => void) | null = null // Dynamically import Three.js import('three').then((THREE) => { const container = canvasRef.current! const scene = new THREE.Scene() // Use THREE.Timer instead of deprecated THREE.Clock const timer = new (THREE as any).Timer() const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ) const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }) renderer.setSize(window.innerWidth, window.innerHeight) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) container.appendChild(renderer.domElement) // Create particles (matching template: 1500 count, spread 50, size 0.05) const particlesGeometry = new THREE.BufferGeometry() const particlesCount = 1500 const posArray = new Float32Array(particlesCount * 3) const colorsArray = new Float32Array(particlesCount * 3) for (let i = 0; i < particlesCount * 3; i += 3) { posArray[i] = (Math.random() - 0.5) * 50 posArray[i + 1] = (Math.random() - 0.5) * 50 posArray[i + 2] = (Math.random() - 0.5) * 50 // Template particle colors: R: 0.4-0.8, G: 0.2-0.5, B: 0.8-1.0 colorsArray[i] = 0.4 + Math.random() * 0.4 colorsArray[i + 1] = 0.2 + Math.random() * 0.3 colorsArray[i + 2] = 0.8 + Math.random() * 0.2 } particlesGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3)) particlesGeometry.setAttribute('color', new THREE.BufferAttribute(colorsArray, 3)) const particlesMaterial = new THREE.PointsMaterial({ size: 0.05, vertexColors: true, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, }) const particlesMesh = new THREE.Points(particlesGeometry, particlesMaterial) scene.add(particlesMesh) // Create floating geometric shapes (matching template: 15 shapes, spread 40) const geometries = [ new THREE.IcosahedronGeometry(1, 0), new THREE.OctahedronGeometry(1, 0), new THREE.TetrahedronGeometry(1, 0), ] const shapes: THREE.Mesh[] = [] const shapeCount = 15 for (let i = 0; i < shapeCount; i++) { const geometry = geometries[Math.floor(Math.random() * geometries.length)] // Template: HSL hue 0.6-0.8, saturation 0.7, lightness 0.5 const material = new THREE.MeshBasicMaterial({ color: new THREE.Color().setHSL(0.6 + Math.random() * 0.2, 0.7, 0.5), wireframe: true, transparent: true, opacity: 0.3, }) const mesh = new THREE.Mesh(geometry, material) mesh.position.set( (Math.random() - 0.5) * 40, (Math.random() - 0.5) * 40, (Math.random() - 0.5) * 40 ) mesh.rotation.set( Math.random() * Math.PI, Math.random() * Math.PI, 0 ) mesh.userData = { rotationSpeed: { x: (Math.random() - 0.5) * 0.01, y: (Math.random() - 0.5) * 0.01, z: (Math.random() - 0.5) * 0.01, }, floatSpeed: Math.random() * 0.02 + 0.01, floatOffset: Math.random() * Math.PI * 2, } shapes.push(mesh) scene.add(mesh) } // Connection lines between nearby shapes (matching template) const lineMaterial = new THREE.LineBasicMaterial({ color: 0x667eea, transparent: true, opacity: 0.08, }) const lineGeometry = new THREE.BufferGeometry() const linePositions = new Float32Array(shapeCount * shapeCount * 6) lineGeometry.setAttribute('position', new THREE.BufferAttribute(linePositions, 3)) const connectionLines = new THREE.LineSegments(lineGeometry, lineMaterial) scene.add(connectionLines) camera.position.z = 30 // Mouse interaction let mouseX = 0 let mouseY = 0 let touchX: number | null = null let touchY: number | null = null const windowHalfX = window.innerWidth / 2 const windowHalfY = window.innerHeight / 2 const handleMouseMove = (event: MouseEvent) => { mouseX = (event.clientX - windowHalfX) / 100 mouseY = (event.clientY - windowHalfY) / 100 } // Touch interaction for mobile (matching template design) const handleTouchMove = (event: TouchEvent) => { if (event.touches.length > 0) { const touch = event.touches[0] touchX = (touch.clientX - windowHalfX) / 100 touchY = (touch.clientY - windowHalfY) / 100 } } const handleTouchEnd = () => { touchX = null touchY = null } document.addEventListener('mousemove', handleMouseMove) document.addEventListener('touchmove', handleTouchMove, { passive: true }) document.addEventListener('touchend', handleTouchEnd) // Animation loop using THREE.Timer (not deprecated Clock) function animate() { animationId = requestAnimationFrame(animate) // Get delta from Timer const delta = timer.getDelta() const time = timer.elapsedTime // Use touch if available, else mouse const targetX = touchX !== null ? touchX : mouseX const targetY = touchY !== null ? touchY : mouseY // Template: camera lerp factor 0.05, sensitivity 2 camera.position.x += (targetX * 2 - camera.position.x) * 0.05 camera.position.y += (-targetY * 2 - camera.position.y) * 0.05 camera.lookAt(scene.position) // Template: rotation Y speed 0.05, X speed 0.02 particlesMesh.rotation.y += delta * 0.05 particlesMesh.rotation.x += delta * 0.02 shapes.forEach((shape) => { shape.rotation.x += shape.userData.rotationSpeed.x shape.rotation.y += shape.userData.rotationSpeed.y shape.rotation.z += shape.userData.rotationSpeed.z // Template: float amplitude 0.02 shape.position.y += Math.sin( time * shape.userData.floatSpeed + shape.userData.floatOffset ) * 0.02 }) // Update connection lines between nearby shapes const positions = connectionLines.geometry.attributes.position.array as Float32Array let lineIndex = 0 const connectionDistance = 15 for (let i = 0; i < shapes.length; i++) { for (let j = i + 1; j < shapes.length; j++) { const dist = shapes[i].position.distanceTo(shapes[j].position) if (dist < connectionDistance) { positions[lineIndex++] = shapes[i].position.x positions[lineIndex++] = shapes[i].position.y positions[lineIndex++] = shapes[i].position.z positions[lineIndex++] = shapes[j].position.x positions[lineIndex++] = shapes[j].position.y positions[lineIndex++] = shapes[j].position.z } } } // Zero out unused positions for (let i = lineIndex; i < positions.length; i++) { positions[i] = 0 } connectionLines.geometry.attributes.position.needsUpdate = true // Particle wave animation (matching template: wave speed 0.5, amplitude 0.02, frequency 0.1) const particlePositions = particlesGeometry.attributes.position.array as Float32Array for (let i = 0; i < particlesCount * 3; i += 3) { particlePositions[i + 1] += Math.sin(time * 0.5 + particlePositions[i] * 0.1) * 0.02 } particlesGeometry.attributes.position.needsUpdate = true renderer.render(scene, camera) } animate() // Handle resize const handleResize = () => { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } window.addEventListener('resize', handleResize) // Cleanup function cleanup = () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('touchmove', handleTouchMove) document.removeEventListener('touchend', handleTouchEnd) window.removeEventListener('resize', handleResize) cancelAnimationFrame(animationId) if (container.contains(renderer.domElement)) { container.removeChild(renderer.domElement) } renderer.dispose() particlesGeometry.dispose() particlesMaterial.dispose() shapes.forEach((shape) => { shape.geometry.dispose() ;(shape.material as THREE.Material).dispose() }) lineGeometry.dispose() lineMaterial.dispose() } }) return () => { cleanup?.() } }, []) return (
) }