/** * Create a 2D scene matching the screenshot exactly * Features: Person with phone, large device with QR code, security shield, floating coins and circles */ export function initHeroScene2D() { const container = document.getElementById('hero-3d-container'); if (!container) return; // Create canvas const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Set canvas size const resizeCanvas = () => { canvas.width = container.clientWidth; canvas.height = container.clientHeight; }; resizeCanvas(); container.appendChild(canvas); // Animation variables let time = 0; // Color palette const colors = { orange: '#FF7A59', darkGray: '#4a5568', mediumGray: '#64748b', lightGray: '#94a3b8', veryLightGray: '#cbd5e1', white: '#ffffff', darkBlue: '#1e3a5f', skin: '#ffdbac', darkNavy: '#1e293b', }; // Draw rounded rectangle helper function roundRect(x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); } // Draw shield shape function drawShield(x, y, width, height) { ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + width, y); ctx.lineTo(x + width, y + height * 0.6); ctx.quadraticCurveTo(x + width, y + height * 0.85, x + width / 2, y + height); ctx.quadraticCurveTo(x, y + height * 0.85, x, y + height * 0.6); ctx.lineTo(x, y); ctx.closePath(); } // Draw QR code pattern function drawQRCode(x, y, size) { const moduleSize = size / 19; 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], ]; ctx.fillStyle = colors.darkGray; pattern.forEach((row, i) => { row.forEach((cell, j) => { if (cell === 1) { ctx.fillRect(x + j * moduleSize, y + i * moduleSize, moduleSize, moduleSize); } }); }); } // Draw person function drawPerson(baseX, baseY, scale, bobOffset) { ctx.save(); ctx.translate(baseX, baseY + bobOffset); ctx.scale(scale, scale); // Head ctx.fillStyle = colors.skin; ctx.beginPath(); ctx.arc(0, -60, 25, 0, Math.PI * 2); ctx.fill(); // Hair ctx.fillStyle = colors.darkNavy; ctx.beginPath(); ctx.arc(-8, -68, 18, 0, Math.PI * 2); ctx.arc(8, -68, 18, 0, Math.PI * 2); ctx.arc(0, -75, 20, 0, Math.PI * 2); ctx.fill(); // Face details // Eyes ctx.fillStyle = colors.darkNavy; ctx.beginPath(); ctx.arc(-8, -62, 2, 0, Math.PI * 2); ctx.arc(8, -62, 2, 0, Math.PI * 2); ctx.fill(); // Smile ctx.strokeStyle = colors.darkNavy; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, -58, 8, 0.2, Math.PI - 0.2); ctx.stroke(); // Neck ctx.fillStyle = colors.skin; ctx.fillRect(-8, -35, 16, 15); // White shirt/collar ctx.fillStyle = '#f8f8f8'; ctx.beginPath(); ctx.moveTo(-15, -25); ctx.lineTo(15, -25); ctx.lineTo(12, 0); ctx.lineTo(-12, 0); ctx.closePath(); ctx.fill(); // Orange jacket/blazer ctx.fillStyle = colors.orange; ctx.beginPath(); // Left side ctx.moveTo(-25, -20); ctx.lineTo(-12, -25); ctx.lineTo(-15, 40); ctx.lineTo(-28, 40); ctx.closePath(); ctx.fill(); ctx.beginPath(); // Right side ctx.moveTo(25, -20); ctx.lineTo(12, -25); ctx.lineTo(15, 40); ctx.lineTo(28, 40); ctx.closePath(); ctx.fill(); // Dark skirt ctx.fillStyle = colors.darkBlue; ctx.beginPath(); ctx.moveTo(-18, 40); ctx.lineTo(18, 40); ctx.lineTo(25, 100); ctx.lineTo(-25, 100); ctx.closePath(); ctx.fill(); // Legs ctx.fillStyle = colors.skin; ctx.fillRect(-12, 100, 8, 30); ctx.fillRect(4, 100, 8, 30); // Orange shoes ctx.fillStyle = colors.orange; roundRect(-15, 128, 12, 8, 2); ctx.fill(); roundRect(3, 128, 12, 8, 2); ctx.fill(); // Arm holding phone ctx.fillStyle = colors.orange; ctx.beginPath(); ctx.moveTo(25, -10); ctx.lineTo(45, 5); ctx.lineTo(42, 12); ctx.lineTo(22, -3); ctx.closePath(); ctx.fill(); // Hand ctx.fillStyle = colors.skin; ctx.beginPath(); ctx.arc(45, 10, 6, 0, Math.PI * 2); ctx.fill(); // Small phone in hand ctx.fillStyle = colors.darkNavy; roundRect(40, 0, 12, 20, 2); ctx.fill(); ctx.fillStyle = '#4A90E2'; roundRect(41, 1, 10, 18, 1); ctx.fill(); // Small phone screen detail (checkmark icon) ctx.fillStyle = colors.white; ctx.font = '10px Arial'; ctx.textAlign = 'center'; ctx.fillText('✓', 46, 12); ctx.restore(); } // Draw tablet/phone device function drawDevice(x, y, width, height, tilt) { ctx.save(); ctx.translate(x, y); ctx.rotate(tilt); // Shadow/base ctx.fillStyle = 'rgba(0, 0, 0, 0.15)'; roundRect(10, 10, width, height, 20); ctx.fill(); // Device frame (dark gray) ctx.fillStyle = colors.darkGray; roundRect(0, 0, width, height, 20); ctx.fill(); // Screen (white) ctx.fillStyle = colors.white; roundRect(15, 15, width - 30, height - 30, 15); ctx.fill(); // QR Code const qrSize = Math.min(width, height) * 0.45; const qrX = (width - qrSize) / 2; const qrY = 80; // Orange frame around QR ctx.strokeStyle = colors.orange; ctx.lineWidth = 8; roundRect(qrX - 10, qrY - 10, qrSize + 20, qrSize + 20, 15); ctx.stroke(); // QR Code drawQRCode(qrX, qrY, qrSize); // Price text ctx.fillStyle = colors.mediumGray; ctx.font = 'bold 60px Arial'; ctx.textAlign = 'center'; ctx.fillText('$200', width / 2, qrY + qrSize + 80); // Some text lines below (simulating details) ctx.fillStyle = colors.veryLightGray; const lineY = qrY + qrSize + 130; roundRect(40, lineY, width - 80, 12, 6); ctx.fill(); roundRect(60, lineY + 25, width - 120, 12, 6); ctx.fill(); roundRect(50, lineY + 50, width - 100, 12, 6); ctx.fill(); ctx.restore(); } // Draw security shield function drawSecurityShield(x, y, size, bobOffset) { ctx.save(); ctx.translate(x, y + bobOffset); // Shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; drawShield(3, 3, size, size * 1.3); ctx.fill(); // Shield body ctx.fillStyle = colors.orange; drawShield(0, 0, size, size * 1.3); ctx.fill(); // White stroke ctx.strokeStyle = colors.white; ctx.lineWidth = 3; drawShield(0, 0, size, size * 1.3); ctx.stroke(); // Checkmark ctx.strokeStyle = colors.white; ctx.lineWidth = 8; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(size * 0.25, size * 0.5); ctx.lineTo(size * 0.45, size * 0.7); ctx.lineTo(size * 0.8, size * 0.25); ctx.stroke(); ctx.restore(); } // Draw coin function drawCoin(x, y, radius, rotation, showDollar = false) { ctx.save(); ctx.translate(x, y); ctx.rotate(rotation); // Shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.15)'; ctx.beginPath(); ctx.arc(2, 2, radius, 0, Math.PI * 2); ctx.fill(); // Coin body ctx.fillStyle = colors.orange; ctx.beginPath(); ctx.arc(0, 0, radius, 0, Math.PI * 2); ctx.fill(); // Inner circle ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, 0, radius * 0.8, 0, Math.PI * 2); ctx.stroke(); // Dollar sign if (showDollar) { ctx.fillStyle = colors.white; ctx.font = `bold ${radius * 1.2}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('$', 0, 0); } ctx.restore(); } // Draw floating circle function drawFloatingCircle(x, y, radius, opacity) { ctx.fillStyle = `rgba(148, 163, 184, ${opacity})`; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); } // Main animation loop function animate() { requestAnimationFrame(animate); time += 0.01; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); const centerX = canvas.width / 2; const centerY = canvas.height / 2; const scale = Math.min(canvas.width, canvas.height) / 800; // Draw floating circles (background) const circles = [ { x: 0.15, y: 0.15, radius: 20, phase: 0 }, { x: 0.2, y: 0.35, radius: 15, phase: 1 }, { x: 0.85, y: 0.2, radius: 25, phase: 2 }, { x: 0.8, y: 0.7, radius: 18, phase: 3 }, { x: 0.25, y: 0.75, radius: 12, phase: 4 }, { x: 0.9, y: 0.5, radius: 22, phase: 2.5 }, ]; circles.forEach(circle => { const x = canvas.width * circle.x; const y = canvas.height * circle.y + Math.sin(time * 0.8 + circle.phase) * 10; const opacity = 0.25 + Math.sin(time * 0.5 + circle.phase) * 0.1; drawFloatingCircle(x, y, circle.radius * scale, opacity); }); // Draw person (left side) const personX = centerX - 200 * scale; const personY = centerY + 120 * scale; const personBob = Math.sin(time * 1.1) * 3; drawPerson(personX, personY, scale * 1.1, personBob); // Draw main device (center-right) const deviceWidth = 320 * scale; const deviceHeight = 480 * scale; const deviceX = centerX - 40 * scale; const deviceY = centerY - 150 * scale; const deviceTilt = Math.sin(time * 0.5) * 0.02; const deviceBob = Math.sin(time * 0.8) * 5; ctx.save(); ctx.translate(0, deviceBob); drawDevice(deviceX, deviceY, deviceWidth, deviceHeight, deviceTilt); ctx.restore(); // Draw security shield (top right of device) const shieldX = deviceX + deviceWidth - 20 * scale; const shieldY = deviceY - 20 * scale; const shieldSize = 70 * scale; const shieldBob = Math.sin(time * 1.2) * 4; drawSecurityShield(shieldX, shieldY, shieldSize, shieldBob); // Draw coins // Large coin (bottom right) const largeCoinX = deviceX + deviceWidth + 40 * scale; const largeCoinY = deviceY + deviceHeight - 60 * scale; const largeCoinBob = Math.sin(time * 1.5) * 6; drawCoin(largeCoinX, largeCoinY + largeCoinBob, 50 * scale, time * 0.5, true); // Small coins const smallCoins = [ { x: 0.65, y: 0.25, radius: 20, phase: 0 }, { x: 0.75, y: 0.45, radius: 18, phase: 1.5 }, { x: 0.7, y: 0.15, radius: 15, phase: 2.5 }, ]; smallCoins.forEach(coin => { const x = canvas.width * coin.x; const y = canvas.height * coin.y + Math.sin(time * 1.3 + coin.phase) * 8; const rotation = time * 0.8 + coin.phase; drawCoin(x, y, coin.radius * scale, rotation, false); }); // Draw floating circles (foreground) const foregroundCircles = [ { x: 0.12, y: 0.5, radius: 16, phase: 1.5 }, { x: 0.88, y: 0.35, radius: 20, phase: 3 }, ]; foregroundCircles.forEach(circle => { const x = canvas.width * circle.x; const y = canvas.height * circle.y + Math.sin(time * 0.9 + circle.phase) * 8; const opacity = 0.3 + Math.sin(time * 0.6 + circle.phase) * 0.15; drawFloatingCircle(x, y, circle.radius * scale, opacity); }); } // Start animation animate(); // Handle resize function onWindowResize() { resizeCanvas(); } window.addEventListener('resize', onWindowResize); // Cleanup function return () => { window.removeEventListener('resize', onWindowResize); container.removeChild(canvas); }; }