class Starfield { constructor() { this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); this.stars = []; this.starLayers = []; this.animationFrameId = null; this.init(); } init() { // Setup renderer this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.renderer.setClearColor(0x000000, 1); // Append to stars container const container = document.getElementById('stars-container'); if (container) { container.appendChild(this.renderer.domElement); } else { document.body.insertBefore(this.renderer.domElement, document.body.firstChild); } // Create star layers (farther stars move slower) this.createStarLayer(500, 0.15, 0xffffff, 2.5); // Far, dim stars this.createStarLayer(300, 0.4, 0xbbbbff, 2); // Medium stars this.createStarLayer(150, 0.8, 0x8888ff, 1.5); // Close, bright stars // Position camera this.camera.position.z = 30; // Handle window resize window.addEventListener('resize', () => this.onWindowResize(), false); // Start animation this.animate(); } createStarLayer(count, size, color, speed) { const geometry = new THREE.BufferGeometry(); const positions = []; const sizes = []; for (let i = 0; i < count; i++) { // Random position in a sphere const radius = 100 + Math.random() * 400; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const x = radius * Math.sin(phi) * Math.cos(theta); const y = radius * Math.sin(phi) * Math.sin(theta); const z = radius * Math.cos(phi); positions.push(x, y, z); sizes.push(Math.random() * size + size * 0.5); } geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1)); const material = new THREE.PointsMaterial({ color: color, size: 1, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, sizeAttenuation: true }); const starField = new THREE.Points(geometry, material); starField.userData = { speed: speed }; this.scene.add(starField); this.starLayers.push(starField); } onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } animate() { this.animationFrameId = requestAnimationFrame(() => this.animate()); // Rotate each star layer at different speeds this.starLayers.forEach(layer => { layer.rotation.x += 0.0001 * layer.userData.speed; layer.rotation.y += 0.0002 * layer.userData.speed; }); // Add subtle pulsing effect const time = Date.now() * 0.001; this.camera.position.z = 30 + Math.sin(time * 0.5) * 5; this.renderer.render(this.scene, this.camera); } destroy() { if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); } window.removeEventListener('resize', this.onWindowResize); if (this.renderer.domElement.parentNode) { this.renderer.domElement.parentNode.removeChild(this.renderer.domElement); } } } // Initialize when the page loads let starfield; function initStars() { if (!starfield) { starfield = new Starfield(); } } // Handle window resize with debounce let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (starfield) { starfield.onWindowResize(); } }, 200); }); // Clean up on page unload to prevent memory leaks window.addEventListener('beforeunload', () => { if (starfield) { starfield.destroy(); } }); // Start the effect when the page loads if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(initStars, 1); } else { window.addEventListener('DOMContentLoaded', initStars); }