import * as THREE from 'three'; /** * Create a 3D scene symbolizing bank transfer with QR code and ecommerce * Features: Animated QR code cube, floating coins, shopping cart, and payment flow */ export function initHeroScene() { const container = document.getElementById('hero-3d-container'); if (!container) return; // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 0.1, 1000 ); camera.position.set(0, 0, 8); const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); renderer.setSize(container.clientWidth, container.clientHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(renderer.domElement); // Lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 5, 5); scene.add(directionalLight); const pointLight = new THREE.PointLight(0xFF7A59, 1, 100); pointLight.position.set(-5, 5, 5); scene.add(pointLight); // Create QR Code Cube (centerpiece) const qrCodeTexture = createQRCodeTexture(); const qrCubeGeometry = new THREE.BoxGeometry(2, 2, 2); const qrCubeMaterial = new THREE.MeshStandardMaterial({ map: qrCodeTexture, metalness: 0.3, roughness: 0.4, }); const qrCube = new THREE.Mesh(qrCubeGeometry, qrCubeMaterial); qrCube.position.set(0, 0, 0); scene.add(qrCube); // Create Euro coins const coins = []; const coinGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.1, 32); const coinMaterial = new THREE.MeshStandardMaterial({ color: 0xFFD700, metalness: 0.8, roughness: 0.2, }); for (let i = 0; i < 6; i++) { const coin = new THREE.Mesh(coinGeometry, coinMaterial); const angle = (i / 6) * Math.PI * 2; const radius = 3; coin.position.set( Math.cos(angle) * radius, Math.sin(angle * 2) * 1.5, Math.sin(angle) * radius ); coin.rotation.x = Math.PI / 2; coins.push({ mesh: coin, angle, radius, offset: i }); scene.add(coin); } // Create shopping cart icon (simplified) const cartGroup = new THREE.Group(); // Cart body const cartBodyGeometry = new THREE.BoxGeometry(0.8, 0.6, 0.6); const cartMaterial = new THREE.MeshStandardMaterial({ color: 0x2563EB, metalness: 0.5, roughness: 0.3, }); const cartBody = new THREE.Mesh(cartBodyGeometry, cartMaterial); cartGroup.add(cartBody); // Cart wheels const wheelGeometry = new THREE.CylinderGeometry(0.1, 0.1, 0.05, 16); const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x1E293B }); const wheel1 = new THREE.Mesh(wheelGeometry, wheelMaterial); wheel1.position.set(-0.2, -0.4, 0.3); wheel1.rotation.z = Math.PI / 2; cartGroup.add(wheel1); const wheel2 = new THREE.Mesh(wheelGeometry, wheelMaterial); wheel2.position.set(0.2, -0.4, 0.3); wheel2.rotation.z = Math.PI / 2; cartGroup.add(wheel2); cartGroup.position.set(-3, -2, 2); cartGroup.scale.set(1.5, 1.5, 1.5); scene.add(cartGroup); // Create smartphone (payment device) const phoneGroup = new THREE.Group(); const phoneBodyGeometry = new THREE.BoxGeometry(0.6, 1, 0.1); const phoneMaterial = new THREE.MeshStandardMaterial({ color: 0x1E293B, metalness: 0.6, roughness: 0.2, }); const phoneBody = new THREE.Mesh(phoneBodyGeometry, phoneMaterial); phoneGroup.add(phoneBody); // Phone screen const screenGeometry = new THREE.BoxGeometry(0.54, 0.9, 0.05); const screenMaterial = new THREE.MeshStandardMaterial({ color: 0x4A90E2, emissive: 0x2563EB, emissiveIntensity: 0.3, }); const screen = new THREE.Mesh(screenGeometry, screenMaterial); screen.position.z = 0.06; phoneGroup.add(screen); phoneGroup.position.set(3, -1.5, 1); phoneGroup.rotation.y = -0.3; phoneGroup.scale.set(1.5, 1.5, 1.5); scene.add(phoneGroup); // Create bank icon const bankGroup = new THREE.Group(); const bankBodyGeometry = new THREE.BoxGeometry(1.2, 0.8, 0.6); const bankMaterial = new THREE.MeshStandardMaterial({ color: 0xFF7A59, metalness: 0.3, roughness: 0.5, }); const bankBody = new THREE.Mesh(bankBodyGeometry, bankMaterial); bankGroup.add(bankBody); // Bank roof const roofGeometry = new THREE.ConeGeometry(0.9, 0.4, 4); const roofMaterial = new THREE.MeshStandardMaterial({ color: 0xE85A3D, }); const roof = new THREE.Mesh(roofGeometry, roofMaterial); roof.position.y = 0.6; roof.rotation.y = Math.PI / 4; bankGroup.add(roof); bankGroup.position.set(2.5, 2, -1); bankGroup.scale.set(0.8, 0.8, 0.8); scene.add(bankGroup); // Particle system for data flow const particlesGeometry = new THREE.BufferGeometry(); const particlesCount = 50; const positions = new Float32Array(particlesCount * 3); for (let i = 0; i < particlesCount * 3; i++) { positions[i] = (Math.random() - 0.5) * 10; } particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const particlesMaterial = new THREE.PointsMaterial({ color: 0xFF7A59, size: 0.05, transparent: true, opacity: 0.6, }); const particles = new THREE.Points(particlesGeometry, particlesMaterial); scene.add(particles); // Animation let time = 0; function animate() { requestAnimationFrame(animate); time += 0.01; // Rotate QR cube qrCube.rotation.y += 0.005; qrCube.rotation.x = Math.sin(time) * 0.1; qrCube.position.y = Math.sin(time * 1.5) * 0.2; // Animate coins in orbit coins.forEach((coinData, index) => { const { mesh, angle, radius, offset } = coinData; const newAngle = angle + time * 0.5; mesh.position.x = Math.cos(newAngle) * radius; mesh.position.z = Math.sin(newAngle) * radius; mesh.position.y = Math.sin(time * 2 + offset) * 1.5; mesh.rotation.y += 0.02; }); // Animate cart (bobbing) cartGroup.position.y = -2 + Math.sin(time * 2) * 0.1; cartGroup.rotation.y = Math.sin(time * 0.5) * 0.1; // Animate phone phoneGroup.position.y = -1.5 + Math.sin(time * 1.5 + 1) * 0.15; phoneGroup.rotation.z = Math.sin(time * 0.8) * 0.05; // Animate bank bankGroup.position.y = 2 + Math.sin(time * 1.2 + 2) * 0.1; // Animate particles particles.rotation.y += 0.001; const positions = particles.geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { positions[i + 1] += Math.sin(time + i) * 0.002; } particles.geometry.attributes.position.needsUpdate = true; renderer.render(scene, camera); } animate(); // Handle resize function onWindowResize() { if (!container) return; camera.aspect = container.clientWidth / container.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(container.clientWidth, container.clientHeight); } window.addEventListener('resize', onWindowResize); // Cleanup function return () => { window.removeEventListener('resize', onWindowResize); renderer.dispose(); container.removeChild(renderer.domElement); }; } /** * Create a procedural QR code texture */ function createQRCodeTexture() { const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512; const ctx = canvas.getContext('2d'); // White background ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, 512, 512); // Draw QR code pattern ctx.fillStyle = '#000000'; const moduleSize = 32; const modules = 16; // QR code pattern (simplified) const pattern = [ [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0], [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0], [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0], [1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1], ]; const offset = (512 - (pattern.length * moduleSize)) / 2; pattern.forEach((row, i) => { row.forEach((cell, j) => { if (cell === 1) { ctx.fillRect( offset + j * moduleSize, offset + i * moduleSize, moduleSize, moduleSize ); } }); }); const texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = true; return texture; }