From 3e7b2beca1136a42700a7e1aebfe4c0fb2861a00 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sat, 15 Nov 2025 18:00:09 +0000
Subject: Initial commit

---
 frontend/src/components/CityscapeBackground.tsx | 310 ++++++++++++++++++++++++
 1 file changed, 310 insertions(+)
 create mode 100644 frontend/src/components/CityscapeBackground.tsx

(limited to 'frontend/src/components/CityscapeBackground.tsx')

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
-- 
cgit v1.2.3

