summaryrefslogblamecommitdiff
path: root/frontend/src/components/LandingPage.tsx
blob: f126d6fa18e6b592dd69ec9c388f9c4be87bd6d2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                                                          

                                               







                                                            



                                                                                
                                                                                   
                                                                                   
 
                                             
                   
                                                             


                                    























































                                                                    
                                       
                                             


                                                 


             

                                      


                    





                                                                     
   


          

                                                                       
                    


                                                                                 


                                                      

                                      




                                                                 

                  
                                           


                                                                          
















                                                                                          


                
 
















                                                                                                                          
                  













































                                                                                                                                       
                  
                
              
 











                                                                             

                  
          
            
                                                                             


          
import React, { useState, useEffect, useRef } 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 | 'makimaRedirect'>(null)
  const [activePanel, setActivePanel] = useState<null | 'mission' | 'makima'>(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 === 'makimaRedirect') {
      window.location.assign('https://makima.jp')
      return
    }
    onLogin()
  }

  const handleLogin = () => {
    setPendingAction('makimaRedirect')
    setLoading(true)
  }

  const handleMission = () => {
    setActivePanel((mode) => (mode === 'mission' ? null : 'mission'))
  }

  const handleMakimaPanel = () => {
    setActivePanel((mode) => (mode === 'makima' ? null : 'makima'))
  }

  return (
    <div>
      {loading && <LoadingScreen onComplete={handleLoadingComplete} />}

      {!loading && (
        <div className={`floating-header ${showLanding ? 'fade-in' : 'hidden'}`}>
          <div className="header-content">
            <div className="brand">
              <img
                src="/logo/crane-logo-transparent.png"
                alt="Soryu"
                height={40}
                className="brand-mark"
                onError={(e) => {
                  const img = e.currentTarget as HTMLImageElement
                  img.onerror = null
                  img.src = '/logo/crane-logo.png'
                }}
              />
            </div>
            <div className="header-center">
              <HeartLogo size="header-no-rays" className="header-heart" />
            </div>

            <div className="system-info">
              <div className="info-item">
                <span className="info-label">System:</span>
                <span
                  className="info-value live-status clickable"
                  onClick={() => setIsStandby(!isStandby)}
                  title="Click to toggle between LIVE and STANDBY"
                >
                  <span className={`status-dot ${isStandby ? 'standby' : 'live'}`}></span>
                  {isStandby ? 'STDBY' : 'LIVE'}
                </span>
              </div>
              <div className="info-item">
                <span className="info-label">Version:</span>
                <span className="info-value">v1.0.0</span>
              </div>
            </div>
          </div>
        </div>
      )}

      <div className={`modern-landing-page manga-style ${showLanding && !loading ? 'fade-in' : 'hidden'}`}>
        {/* Background GIF fills the main body */}
        <div className="background-only" aria-hidden="true">
          <img src="/background-animation.gif" alt="" className="background-gif" />
        </div>

        {/* Minimal overlay: masthead, issue badge, and CTA */}
        <div className={`taisho-cover ${activePanel ? 'mission-mode' : ''}`}>
          <div className="cover-backdrop" aria-hidden="true" />
          <div className="cover-content">
            {/* Masthead + Issue badge (kept) */}
            <div className="masthead">
              <div className="masthead-vertical">
                <span className="jp">そりゅう</span>
                <span className="en">SORYU</span>
              </div>
              <div className="issue-badge"><span className="led-heart" aria-hidden="true"></span>かはいい Vol.01</div>
            </div>

            {/* Hero area becomes Mission content when in mission mode */}
            {activePanel === 'mission' ? (
              <div className="mission-screen" role="region" aria-label="Mission">
                <h1 className="mission-headline">Building real‑time systems for mission-critical observability and surveillance </h1>
                <img src="/PC98Doukuusei.webp" alt="Mission montage" className="mission-image" />
                <p className="mission-paragraph">
                  We deliver low‑latency streaming & infrastructure that turns live data into
                  reliable, secure insight. Target selection, monitoring and full end to end observability
                  to make vital decisions where it matters most.
                </p>
              </div>
            ) : activePanel === 'makima' ? (
              <div className="mission-screen makima-screen" role="region" aria-label="Makima">
                <h1 className="mission-headline makima-headline">Mesh Orchestration Platform</h1>
                <span className="makima-badge">Control System</span>
                <img src="/logo/makima-logo.svg" alt="Makima mesh logo" className="mission-image makima-logo" />
                <p className="mission-paragraph">
                  Makima is a control system for orchestrating distributed daemon meshes,
                  coordinating concurrent execution across distinct domains.
                </p>
                <p className="mission-paragraph">
                  Unified command interface for spawning, monitoring, and directing
                  worker daemons. Real-time task coordination with overlay management.
                </p>
              </div>
            ) : (
              <div className="hero" />
            )}

            {/* CTA row spanning full width: left Mission/MAKIMA, right Login */}
            <div className="cta-area">
              <div className="cta-left">
                <button className="taisho-cta" onClick={handleMission}>
                  <span className="cta-text">{activePanel === 'mission' ? 'Close' : 'Mission'}</span>
                </button>
                <button className="taisho-cta" onClick={handleMakimaPanel}>
                  <span className="cta-text">{activePanel === 'makima' ? 'Close' : 'MAKIMA'}</span>
                </button>
              </div>
              <div className="cta-right">
                <button className="taisho-cta" onClick={handleLogin}>
                  <span className="cta-icon">▶</span>
                  <span className="cta-text">Login</span>
                </button>
              </div>
            </div>
          </div>
        </div>

        {/* Bottom stats: Velocity + Energy only (hidden in mission mode) */}
        {!activePanel && (
          <div className="bottom-stats">
            <div className="rf-stats">
              <div className="rf-stat">
                <span className="label">Velocity</span>
                <span className="value">{Math.round(velocity)} km/h</span>
              </div>
              <div className="rf-stat">
                <span className="label">Energy</span>
                <span className="value">{energy.toFixed(1)} MJ</span>
              </div>
            </div>
          </div>
        )}
      </div>
      {/* MissionDrawer removed in favor of mission screen transformation */}
    </div>
  )
}