summaryrefslogtreecommitdiff
path: root/frontend/src/components/CityscapeBackground.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/CityscapeBackground.tsx')
-rw-r--r--frontend/src/components/CityscapeBackground.tsx310
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