diff options
Diffstat (limited to 'frontend/src/components/CityscapeBackground.tsx')
| -rw-r--r-- | frontend/src/components/CityscapeBackground.tsx | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/frontend/src/components/CityscapeBackground.tsx b/frontend/src/components/CityscapeBackground.tsx new file mode 100644 index 0000000..82a679d --- /dev/null +++ b/frontend/src/components/CityscapeBackground.tsx @@ -0,0 +1,310 @@ +import React, { useRef, useEffect } from 'react' +import * as THREE from 'three' + +interface CityscapeBackgroundProps { + className?: string +} + +export function CityscapeBackground({ className }: CityscapeBackgroundProps) { + const canvasRef = useRef<HTMLCanvasElement>(null) + const sceneRef = useRef<THREE.Scene>() + const rendererRef = useRef<THREE.WebGLRenderer>() + const cameraRef = useRef<THREE.PerspectiveCamera>() + const animationIdRef = useRef<number>() + const speedLinesRef = useRef<THREE.LineSegments>() + const mouseRef = useRef(new THREE.Vector2()) + const startTimeRef = useRef<number>(Date.now()) + + useEffect(() => { + if (!canvasRef.current) { + console.log('CityscapeBackground: Canvas ref not available') + return + } + + console.log('FuturistBackground: Initializing 3D futurist scene') + + // Scene setup with futurist art movement atmosphere + const scene = new THREE.Scene() + scene.background = new THREE.Color(0x000000) // Black background + scene.fog = new THREE.Fog(0x000000, 30, 120) // Black fog for depth + + // Camera setup for isometric view + const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200) + camera.position.set(30, 25, 30) + camera.lookAt(0, 0, 0) + + // Renderer setup + const renderer = new THREE.WebGLRenderer({ + canvas: canvasRef.current, + antialias: false, // Disable for better performance + alpha: false // Disable alpha for opaque background + }) + renderer.setSize(window.innerWidth, window.innerHeight) + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)) // Limit pixel ratio for performance + + // Store references + sceneRef.current = scene + rendererRef.current = renderer + cameraRef.current = camera + + // Futurist art movement lighting - dramatic contrasts inspired by paintings + const ambientLight = new THREE.AmbientLight(0x1a1a1a, 0.4) // Warm grey ambient + scene.add(ambientLight) + + // Bold primary light - warm like industrial sunlight + const primaryLight = new THREE.DirectionalLight(0xffaa33, 1.8) // Golden orange + primaryLight.position.set(-25, 35, 20) + scene.add(primaryLight) + + // Dynamic red light for energy and motion + const dynamicLight = new THREE.DirectionalLight(0xdd2222, 1.2) // Bold red + dynamicLight.position.set(20, 10, -15) + scene.add(dynamicLight) + + // Contrasting blue for depth and drama + const contrastLight = new THREE.PointLight(0x2244bb, 0.8, 50) // Deep blue + contrastLight.position.set(-30, 5, 0) + scene.add(contrastLight) + + // Yellow accent for vibrant highlights + const accentLight = new THREE.PointLight(0xdddd22, 0.7, 40) // Yellow + accentLight.position.set(25, 20, -5) + scene.add(accentLight) + + // Green industrial light + const industrialLight = new THREE.PointLight(0x22aa22, 0.6, 45) // Industrial green + industrialLight.position.set(0, -5, 30) + scene.add(industrialLight) + + // Create speed lines + createSpeedLines() + + console.log('FuturistBackground: Dynamic futurist art created, starting animation') + + // Reset start time + startTimeRef.current = Date.now() + + // Animation loop + const animate = () => { + animationIdRef.current = requestAnimationFrame(animate) + + const time = Date.now() * 0.001 // Convert to seconds for smoother animation + + // Animate speed lines + animateSpeedLines(time) + + // Apply interactive effects + applyFuturistInteractions() + + // Dramatic camera movement inspired by futurist dynamism + const cameraSpeed = time * 0.2 + camera.position.x = 20 + Math.sin(cameraSpeed * 1.3) * 12 + Math.cos(cameraSpeed * 0.7) * 6 + camera.position.y = 15 + Math.cos(cameraSpeed * 0.9) * 8 + Math.sin(cameraSpeed * 1.1) * 4 + camera.position.z = 25 + Math.sin(cameraSpeed * 0.6) * 15 + Math.cos(cameraSpeed * 1.4) * 8 + + // Dynamic look-at point for more dramatic perspective shifts + const lookAtTarget = new THREE.Vector3( + Math.sin(cameraSpeed * 0.8) * 5, + Math.cos(cameraSpeed * 0.5) * 3, + Math.sin(cameraSpeed * 1.2) * 8 + ) + camera.lookAt(lookAtTarget) + + renderer.render(scene, camera) + } + + animate() + + // Mouse tracking for interactive effects + const handleMouseMove = (event: MouseEvent) => { + // Convert screen coordinates to normalized device coordinates (-1 to +1) + mouseRef.current.set( + (event.clientX / window.innerWidth) * 2 - 1, + -(event.clientY / window.innerHeight) * 2 + 1 + ) + } + + // Handle window resize + const handleResize = () => { + if (camera && renderer) { + camera.aspect = window.innerWidth / window.innerHeight + camera.updateProjectionMatrix() + renderer.setSize(window.innerWidth, window.innerHeight) + } + } + + window.addEventListener('mousemove', handleMouseMove) + window.addEventListener('resize', handleResize) + + // Cleanup + return () => { + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current) + } + window.removeEventListener('mousemove', handleMouseMove) + window.removeEventListener('resize', handleResize) + + // Dispose of Three.js objects + scene.clear() + renderer.dispose() + } + }, []) + + const applyFuturistInteractions = () => { + const camera = cameraRef.current! + const raycaster = new THREE.Raycaster() + + // Convert mouse coordinates to world position + raycaster.setFromCamera(mouseRef.current, camera) + + // Interactive speed line distortion + if (speedLinesRef.current) { + const positions = speedLinesRef.current.geometry.attributes.position.array as Float32Array + + for (let i = 0; i < positions.length; i += 6) { // Step by 6 for line pairs + const linePos = new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]) + const mouseWorldPos = new THREE.Vector3() + raycaster.ray.at(linePos.z, mouseWorldPos) + + const distance = linePos.distanceTo(mouseWorldPos) + const distortRadius = 15 + + if (distance < distortRadius) { + const distortStrength = (distortRadius - distance) / distortRadius + + // Create dramatic wave distortion around mouse + const waveX = Math.sin(Date.now() * 0.02 + i) * distortStrength * 2.0 + const waveY = Math.cos(Date.now() * 0.025 + i) * distortStrength * 1.5 + + positions[i] += waveX + positions[i + 1] += waveY + positions[i + 3] += waveX * 0.8 // End point follows with slight lag + positions[i + 4] += waveY * 0.8 + } + } + + speedLinesRef.current.geometry.attributes.position.needsUpdate = true + } + } + + + const createSpeedLines = () => { + const scene = sceneRef.current! + + // Create sharp, dramatic speed lines + const motionVertices = [] + const motionColors = [] + + // High-contrast speed colors + const speedColors = [ + { r: 1.0, g: 1.0, b: 1.0 }, // Brilliant white + { r: 1.0, g: 0.1, b: 0.1 }, // Sharp red + { r: 1.0, g: 0.8, b: 0.0 }, // Electric yellow + { r: 0.0, g: 1.0, b: 1.0 }, // Cyan + { r: 1.0, g: 0.0, b: 1.0 }, // Magenta + ] + + // Create 300 sharp speed lines for intense motion + for (let i = 0; i < 300; i++) { + // Start point - spread across space + const startX = (Math.random() - 0.5) * 100 + const startY = (Math.random() - 0.5) * 40 + const startZ = Math.random() * -200 + + // End point - long streaks suggesting extreme speed + const endX = startX + (Math.random() - 0.5) * 20 + const endY = startY + (Math.random() - 0.5) * 8 + const endZ = startZ + 20 + Math.random() * 40 // Longer streaks + + motionVertices.push(startX, startY, startZ) + motionVertices.push(endX, endY, endZ) + + // High-contrast colors for sharp appearance + const colorIndex = Math.floor(Math.random() * speedColors.length) + const color = speedColors[colorIndex] + + motionColors.push(color.r, color.g, color.b) + motionColors.push(color.r, color.g, color.b) + } + + const motionGeometry = new THREE.BufferGeometry() + motionGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(motionVertices), 3)) + motionGeometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(motionColors), 3)) + + const motionMaterial = new THREE.LineBasicMaterial({ + vertexColors: true, + transparent: true, + opacity: 0.9, + linewidth: 2 + }) + + const speedLines = new THREE.LineSegments(motionGeometry, motionMaterial) + speedLinesRef.current = speedLines + scene.add(speedLines) + } + + + + + + + const animateSpeedLines = (time: number) => { + if (!speedLinesRef.current) return + + const positions = speedLinesRef.current.geometry.attributes.position.array as Float32Array + + for (let i = 0; i < positions.length; i += 6) { // Step by 6 since we have line pairs + // Extreme speed effect - lines blazing toward camera + positions[i + 2] += 6.0 + Math.sin(time * 2 + i) * 3.0 // High variable speed + positions[i + 5] += 6.0 + Math.sin(time * 2 + i) * 3.0 // End point matches + + // Intense lateral movement for speed blur + const lateralMotion = Math.sin(time * 1.5 + i * 0.15) * 0.8 + positions[i] += lateralMotion + positions[i + 3] += lateralMotion + + // Sharp vertical oscillation for dynamic energy + const verticalMotion = Math.cos(time * 2.2 + i * 0.12) * 0.6 + positions[i + 1] += verticalMotion + positions[i + 4] += verticalMotion + + // Reset lines that have blazed past camera + if (positions[i + 2] > 100) { + positions[i + 2] = -250 - Math.random() * 100 + positions[i + 5] = positions[i + 2] + 20 + Math.random() * 40 + + positions[i] = (Math.random() - 0.5) * 100 + positions[i + 1] = (Math.random() - 0.5) * 40 + positions[i + 3] = positions[i] + (Math.random() - 0.5) * 20 + positions[i + 4] = positions[i + 1] + (Math.random() - 0.5) * 8 + } + } + + speedLinesRef.current.geometry.attributes.position.needsUpdate = true + + // Rapid rotation for intense motion blur + speedLinesRef.current.rotation.x += 0.008 + speedLinesRef.current.rotation.y += 0.012 + speedLinesRef.current.rotation.z += 0.025 + } + + + + return ( + <canvas + ref={canvasRef} + className={className} + style={{ + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + zIndex: 0, + pointerEvents: 'auto', + opacity: 1, + visibility: 'visible', + }} + /> + ) +}
\ No newline at end of file |
