summaryrefslogtreecommitdiff
path: root/frontend/src/components/LandingPage.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/LandingPage.tsx')
-rw-r--r--frontend/src/components/LandingPage.tsx237
1 files changed, 109 insertions, 128 deletions
diff --git a/frontend/src/components/LandingPage.tsx b/frontend/src/components/LandingPage.tsx
index f5dc55c..0489794 100644
--- a/frontend/src/components/LandingPage.tsx
+++ b/frontend/src/components/LandingPage.tsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react'
import { LoadingScreen } from './LoadingScreen'
import { HeartLogo } from './HeartLogo'
-// Using direct PNG logo on landing header
interface LandingPageProps {
onLogin: () => void
@@ -11,80 +10,95 @@ 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 [asciiArt, setAsciiArt] = useState('')
- const [isGlitching, setIsGlitching] = useState(false)
- // Note: Removed VN preview and ASCII features to focus on an Art Deco/Nouveau landing.
+ const [velocity, setVelocity] = useState(0)
+ const [energy, setEnergy] = useState(0)
+ const [ramped, setRamped] = useState(false)
+ const [pendingAction, setPendingAction] = useState<null | 'makima' | 'mission'>(null)
- // Auto-fade in landing page content after component mounts
+ // Fade-in landing page content after mount
useEffect(() => {
- const timer = setTimeout(() => {
- setShowLanding(true)
- }, 500) // Delay before fading in content
-
+ const timer = setTimeout(() => setShowLanding(true), 500)
return () => clearTimeout(timer)
}, [])
- // Removed VN dialogue preview / typing effect.
-
- // Load ASCII art for overlay in hero (right/bottom)
- useEffect(() => {
- fetch('/ascii/ascii1.txt')
- .then((res) => res.text())
- .then((txt) => setAsciiArt(txt.replace(/\n+$/, '')))
- .catch(() => setAsciiArt(''))
- }, [])
-
- // Occasionally trigger a brief glitch on the word "futurist"
+ // Ramp up stats, then keep them fluctuating near max
useEffect(() => {
- let armTimer: number | undefined
- let activeTimer: number | undefined
- let cancelled = false
-
- const arm = () => {
- // Random delay between glitches: 0.8s–2s (slightly punchier)
- const delay = 800 + Math.random() * 1200
- armTimer = window.setTimeout(() => {
- setIsGlitching(true)
- // Glitch duration: 150ms–350ms (snappier)
- const dur = 150 + Math.random() * 200
- activeTimer = window.setTimeout(() => {
- setIsGlitching(false)
- if (!cancelled) arm()
- }, dur)
- }, delay)
- }
-
- arm()
+ 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 () => {
- cancelled = true
- if (armTimer) clearTimeout(armTimer)
- if (activeTimer) clearTimeout(activeTimer)
+ if (rampInterval) window.clearInterval(rampInterval)
+ if (fluctuateInterval) window.clearInterval(fluctuateInterval)
}
}, [])
-
- // Handle loading screen completion
const handleLoadingComplete = () => {
- // Call the original onLogin immediately to transition to VN page
- // Don't reset loading states as we're leaving the landing page
+ if (pendingAction === 'makima') {
+ window.location.assign('https://makima.jp')
+ return
+ }
onLogin()
}
- // Handle login button click
- const handleLogin = () => {
+ const handleMakima = () => {
+ setPendingAction('makima')
setLoading(true)
}
- // Removed ASCII art effects and rendering.
+ const handleMission = () => {
+ // Placeholder action for now
+ setPendingAction('mission')
+ }
return (
<div>
- {loading && (
- <LoadingScreen onComplete={handleLoadingComplete} />
- )}
-
- {/* Floating Header Bar - Hidden during loading */}
+ {loading && <LoadingScreen onComplete={handleLoadingComplete} />}
+
{!loading && (
<div className={`floating-header ${showLanding ? 'fade-in' : 'hidden'}`}>
<div className="header-content">
@@ -94,7 +108,11 @@ export function LandingPage({ onLogin }: LandingPageProps) {
alt="Soryu"
height={40}
className="brand-mark"
- onError={(e) => { const img = (e.currentTarget as HTMLImageElement); img.onerror = null; img.src = '/logo/crane-logo.png'; }}
+ onError={(e) => {
+ const img = e.currentTarget as HTMLImageElement
+ img.onerror = null
+ img.src = '/logo/crane-logo.png'
+ }}
/>
</div>
<div className="header-center">
@@ -121,20 +139,17 @@ export function LandingPage({ onLogin }: LandingPageProps) {
</div>
</div>
)}
-
+
<div className={`modern-landing-page manga-style ${showLanding && !loading ? 'fade-in' : 'hidden'}`}>
- {/* Retro-futuristic page background */}
- <div className="rf-page-bg" aria-hidden="true">
- <div className="rf-page-speedlines layer-a"></div>
- <div className="rf-page-speedlines layer-b"></div>
+ {/* Background GIF fills the main body */}
+ <div className="background-only" aria-hidden="true">
+ <img src="/background-animation.gif" alt="" className="background-gif" />
</div>
- {/* Taisho Magazine Cover Backdrop */}
- <div className="taisho-cover">
- <div className="cover-backdrop" aria-hidden="true"></div>
- {/* Cover Content Grid */}
+ {/* Minimal overlay: masthead, issue badge, and CTA */}
+ <div className="taisho-cover">
<div className="cover-content">
- {/* Vertical Masthead (magazine-style) */}
+ {/* Masthead + Issue badge (kept) */}
<div className="masthead">
<div className="masthead-vertical">
<span className="jp">そりゅう</span>
@@ -143,73 +158,39 @@ export function LandingPage({ onLogin }: LandingPageProps) {
<div className="issue-badge">かはいい Vol.01</div>
</div>
- {/* Central Hero - full-frame retro-futuristic */}
- <div className="hero">
- <div className="hero-frame taisho-modern-frame">
- <div className="hero-inner hero-fill">
- {/* Retro-futuristic racing hero content */}
- <div className="rf-hero" aria-hidden="true">
- <div className="rf-speedlines layer-a"></div>
- <div className="rf-speedlines layer-b"></div>
- <div className="rf-hero-content">
- <div className="rf-badge">Engineering • Systems • para bellum</div>
- <h2 className="rf-headline">
- Mission driven{' '}
- <span
- className={`glitch-word ${isGlitching ? 'is-glitching' : ''}`}
- data-text="futurist"
- >
- futurist
- </span>{' '}
- technology
- </h2>
- <p className="rf-tagline">Avant-garde. Aesthetic Engineering. A Race of Steel</p>
- <div className="rf-stats">
- <div className="rf-stat"><span className="label">Velocity</span><span className="value">0–603 km/h</span></div>
- <div className="rf-stat"><span className="label">Energy</span><span className="value">32 MJ</span></div>
- <div className="rf-stat"><span className="label">Flow</span><span className="value">MAX</span></div>
- </div>
- </div>
- <div className="rf-accent-diagonal"></div>
- {/* ASCII overlay (transparent, gradient text) */}
- {asciiArt && (
- <div className="rf-ascii-overlay" aria-hidden="true">
- {asciiArt.split('\n').map((line, i) => (
- <div key={i} className="ascii-line">
- {Array.from(line).map((ch, j) => (
- <span
- key={`${i}-${j}`}
- className="ascii-char"
- style={{
- '--delay': `${(i * 0.08 + j * 0.01)}s`,
- } as React.CSSProperties}
- >
- {ch}
- </span>
- ))}
- </div>
- ))}
- </div>
- )}
- </div>
- </div>
+ {/* Empty hero area to preserve grid; background sits behind */}
+ <div className="hero" />
+
+ {/* CTA row spanning full width: left Mission/Contact, right Login */}
+ <div className="cta-area">
+ <div className="cta-left">
+ <button className="taisho-cta" onClick={handleMission}>
+ <span className="cta-text">Mission</span>
+ </button>
+ <button className="taisho-cta" onClick={() => {/* placeholder contact */}}>
+ <span className="cta-text">Contact</span>
+ </button>
+ </div>
+ <div className="cta-right">
+ <button className="taisho-cta" onClick={handleMakima}>
+ <span className="cta-icon">▶</span>
+ <span className="cta-text">Login</span>
+ </button>
</div>
</div>
+ </div>
+ </div>
- {/* CTA */}
- <div className="cta-area">
- <button className="taisho-cta" onClick={handleLogin}>
- <span className="cta-icon">▶</span>
- <span className="cta-text">Enter</span>
- </button>
+ {/* Bottom stats: Velocity + Energy only */}
+ <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>
- {/* Visitor Counter */}
- <div className="visit-counter" aria-label="visitor counter">
- <img
- src="https://count.getloli.com/get/@soryu-landing?theme=booru-jaypee&darkmode=0"
- alt="visit counter"
- referrerPolicy="no-referrer-when-downgrade"
- />
+ <div className="rf-stat">
+ <span className="label">Energy</span>
+ <span className="value">{energy.toFixed(1)} MJ</span>
</div>
</div>
</div>