// hero-animation.js document.addEventListener('DOMContentLoaded', () => { let scene, camera, renderer, font; const objects = []; const canvas = document.getElementById('hero-canvas'); const heroSection = document.querySelector('.hero'); // Symbols to display const symbols = ['∫', '∂', '∑', 'lim', 'π', '∞', '√', '∇', '≈', '≠', '∈', '∀', '∃', '±', '×', '÷', 'ƒ(x)', 'd/dx', 'α', 'β', 'γ', 'Δ', 'θ']; function init() { // --- Basic Scene Setup --- scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a2b3c); // Dark blue background camera = new THREE.PerspectiveCamera(75, heroSection.offsetWidth / heroSection.offsetHeight, 0.1, 1000); camera.position.z = 50; // Adjust distance as needed renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true }); // Use alpha for potential transparency renderer.setSize(heroSection.offsetWidth, heroSection.offsetHeight); renderer.setPixelRatio(window.devicePixelRatio); // Adjust for high-DPI screens // --- Lighting --- const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); // Soft white light scene.add(ambientLight); const pointLight = new THREE.PointLight(0xffffff, 0.8, 150); pointLight.position.set(10, 20, 70); scene.add(pointLight); // --- Load Font --- const loader = new THREE.FontLoader(); // Load a font - make sure the path is correct or use a CDN loader.load('https://cdn.jsdelivr.net/npm/three/examples/fonts/helvetiker_regular.typeface.json', function (loadedFont) { font = loadedFont; createObjects(); // Create objects only after the font is loaded animate(); // Start animation after objects are created }, undefined, function (error) { console.error('Font loading failed:', error); // Optionally, proceed without text objects or show an error }); // --- Handle Window Resize --- window.addEventListener('resize', onWindowResize, false); } function createObjects() { if (!font) return; // Don't proceed if font isn't loaded const textMaterial = new THREE.MeshStandardMaterial({ color: 0xa0c0ff, // Light blueish color roughness: 0.5, metalness: 0.1 }); const bounds = { x: 100, y: 60, z: 80 }; // Area within which objects will spawn/move for (let i = 0; i < 60; i++) { // Number of objects const symbol = symbols[Math.floor(Math.random() * symbols.length)]; const textGeometry = new THREE.TextGeometry(symbol, { font: font, size: Math.random() * 4 + 2, // Random size between 2 and 6 height: 0.5, curveSegments: 4, bevelEnabled: true, bevelThickness: 0.1, bevelSize: 0.05, bevelOffset: 0, bevelSegments: 3 }); textGeometry.center(); // Center the geometry for easier rotation const textMesh = new THREE.Mesh(textGeometry, textMaterial.clone()); // Clone material for potential individual changes // Random initial position textMesh.position.x = (Math.random() - 0.5) * bounds.x; textMesh.position.y = (Math.random() - 0.5) * bounds.y; textMesh.position.z = (Math.random() - 0.5) * bounds.z; // Random initial rotation textMesh.rotation.x = Math.random() * Math.PI * 2; textMesh.rotation.y = Math.random() * Math.PI * 2; textMesh.rotation.z = Math.random() * Math.PI * 2; // Store random movement vectors textMesh.userData.moveVector = new THREE.Vector3( (Math.random() - 0.5) * 0.01, (Math.random() - 0.5) * 0.01, (Math.random() - 0.5) * 0.01 ); textMesh.userData.rotVector = new THREE.Vector3( (Math.random() - 0.5) * 0.005, (Math.random() - 0.5) * 0.005, (Math.random() - 0.5) * 0.005 ); scene.add(textMesh); objects.push(textMesh); } } function onWindowResize() { if (!camera || !renderer || !heroSection) return; camera.aspect = heroSection.offsetWidth / heroSection.offsetHeight; camera.updateProjectionMatrix(); renderer.setSize(heroSection.offsetWidth, heroSection.offsetHeight); renderer.setPixelRatio(window.devicePixelRatio); } // --- Define the bounds for movement --- const bounds = { x: 100, y: 60, z: 80 }; // Area within which objects will move function animate() { requestAnimationFrame(animate); // Animate objects objects.forEach(obj => { // Apply rotation obj.rotation.x += obj.userData.rotVector.x; obj.rotation.y += obj.userData.rotVector.y; obj.rotation.z += obj.userData.rotVector.z; // Apply movement based on moveVector obj.position.add(obj.userData.moveVector); // <--- UNCOMMENT/ADD THIS LINE // --- Boundary Check and Direction Reversal --- // Check X boundaries if (obj.position.x > bounds.x / 2 || obj.position.x < -bounds.x / 2) { obj.userData.moveVector.x *= -1; // Reverse X direction // Optional: Nudge back inside bounds slightly to prevent sticking obj.position.x = Math.sign(obj.position.x) * (bounds.x / 2 - 0.1); } // Check Y boundaries if (obj.position.y > bounds.y / 2 || obj.position.y < -bounds.y / 2) { obj.userData.moveVector.y *= -1; // Reverse Y direction obj.position.y = Math.sign(obj.position.y) * (bounds.y / 2 - 0.1); } // Check Z boundaries if (obj.position.z > bounds.z / 2 || obj.position.z < -bounds.z / 2) { obj.userData.moveVector.z *= -1; // Reverse Z direction obj.position.z = Math.sign(obj.position.z) * (bounds.z / 2 - 0.1); } // --- End Boundary Check --- }); // End forEach loop renderer.render(scene, camera); } // Check if WebGL is available if (typeof WebGLRenderingContext !== 'undefined') { init(); } else { console.error('WebGL is not supported in this browser.'); // The fallback background color in CSS will be visible } }); // End DOMContentLoaded