summaryrefslogblamecommitdiff
path: root/frontend/src/components/CityscapeBackground.tsx
blob: 82a679d201d8bfbed218397ec3b8fe9789548b4e (plain) (tree)





















































































































































































































































































































                                                                                                           
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',
      }}
    />
  )
}