import React, { useState, useEffect } from 'react' import { LoadingScreen } from './LoadingScreen' import { HeartLogo } from './HeartLogo' interface LandingPageProps { onLogin: () => void } export function LandingPage({ onLogin }: LandingPageProps) { const [loading, setLoading] = useState(false) const [showLanding, setShowLanding] = useState(false) const [isStandby, setIsStandby] = useState(true) // false = LIVE, true = STDBY const [velocity, setVelocity] = useState(0) const [energy, setEnergy] = useState(0) const [ramped, setRamped] = useState(false) const [pendingAction, setPendingAction] = useState(null) // Fade-in landing page content after mount useEffect(() => { const timer = setTimeout(() => setShowLanding(true), 500) return () => clearTimeout(timer) }, []) // Ramp up stats, then keep them fluctuating near max useEffect(() => { const VELOCITY_MAX = 603 const ENERGY_MAX = 32 let rampInterval: number | undefined let fluctuateInterval: number | undefined // Ramp-up for ~2 seconds const rampDurationMs = 2000 const tickMs = 30 const vStep = VELOCITY_MAX / (rampDurationMs / tickMs) const eStep = ENERGY_MAX / (rampDurationMs / tickMs) rampInterval = window.setInterval(() => { setVelocity((v) => { const next = v + vStep return next >= VELOCITY_MAX ? VELOCITY_MAX : next }) setEnergy((e) => { const next = e + eStep return next >= ENERGY_MAX ? ENERGY_MAX : next }) }, tickMs) const stopRamp = window.setTimeout(() => { if (rampInterval) window.clearInterval(rampInterval) setVelocity(VELOCITY_MAX) setEnergy(ENERGY_MAX) setRamped(true) // Fluctuate near the top fluctuateInterval = window.setInterval(() => { setVelocity((v) => { const min = VELOCITY_MAX - 18 const max = VELOCITY_MAX const delta = (Math.random() - 0.5) * 6 // ±3 const next = Math.max(min, Math.min(max, v + delta)) return next }) setEnergy((e) => { const min = ENERGY_MAX - 2 const max = ENERGY_MAX const delta = (Math.random() - 0.5) * 0.25 // ±0.125 const next = Math.max(min, Math.min(max, e + delta)) return next }) }, 220) }, rampDurationMs + 60) return () => { if (rampInterval) window.clearInterval(rampInterval) if (fluctuateInterval) window.clearInterval(fluctuateInterval) } }, []) const handleLoadingComplete = () => { if (pendingAction === 'makima') { window.location.assign('https://makima.jp') return } onLogin() } const handleMakima = () => { setPendingAction('makima') setLoading(true) } const handleMission = () => { // Placeholder action for now setPendingAction('mission') } return (
{loading && } {!loading && (
Soryu { const img = e.currentTarget as HTMLImageElement img.onerror = null img.src = '/logo/crane-logo.png' }} />
System: setIsStandby(!isStandby)} title="Click to toggle between LIVE and STANDBY" > {isStandby ? 'STDBY' : 'LIVE'}
Version: v1.0.0
)}
{/* Background GIF fills the main body */} {/* Minimal overlay: masthead, issue badge, and CTA */}
{/* Masthead + Issue badge (kept) */}
そりゅう SORYU
かはいい Vol.01
{/* Empty hero area to preserve grid; background sits behind */}
{/* CTA row spanning full width: left Mission/Contact, right Login */}
{/* Bottom stats: Velocity + Energy only */}
Velocity {Math.round(velocity)} km/h
Energy {energy.toFixed(1)} MJ
) }