303 lines
8.8 KiB
JavaScript
303 lines
8.8 KiB
JavaScript
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;
|
|
}
|