diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/index.html | 3 | ||||
| -rw-r--r-- | frontend/src/components/LandingPage.tsx | 237 | ||||
| -rw-r--r-- | frontend/src/styles/pc98.css | 20 |
3 files changed, 126 insertions, 134 deletions
diff --git a/frontend/index.html b/frontend/index.html index 4bb8d23..095310b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,8 @@ <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> - <title>PC-98 VN Skeleton</title> + <title>soryu.co</title> + <link rel="icon" type="image/png" href="/logo/crane-logo-transparent.png" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=DotGothic16&display=swap" rel="stylesheet"> </head> 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> diff --git a/frontend/src/styles/pc98.css b/frontend/src/styles/pc98.css index 63e1996..39b8392 100644 --- a/frontend/src/styles/pc98.css +++ b/frontend/src/styles/pc98.css @@ -641,6 +641,14 @@ button:focus-visible { pointer-events: none; } +/* Minimal landing background */ +.background-only { position: absolute; inset: 0; z-index: 0; } +.background-only .background-gif { width: 100%; height: 100%; object-fit: cover; display: block; } + +/* Bottom overlay for Velocity/Energy */ +.bottom-stats { position: absolute; left: 0; right: 0; bottom: 16px; display: flex; justify-content: center; z-index: 2; pointer-events: none; } +.bottom-stats .rf-stats { background: rgba(10, 12, 20, 0.55); border: 1px solid rgba(102,204,255,0.6); padding: 8px 12px; border-radius: 6px; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); margin: 0; pointer-events: auto; } + /* Floating Header Bar */ .floating-header { position: fixed; @@ -1670,11 +1678,11 @@ button:focus-visible { z-index: 1; height: 100%; display: grid; - grid-template-columns: 1fr 220px; /* hero left, masthead right */ - grid-template-rows: 1fr auto; /* CTA sits below hero */ + grid-template-columns: 220px 1fr; /* masthead left, hero right */ + grid-template-rows: 1fr auto; /* CTA spans bottom */ grid-template-areas: - "hero masthead" - "hero cta"; + "masthead hero" + "cta cta"; gap: 28px; padding: 40px 48px; } @@ -2128,7 +2136,9 @@ button:focus-visible { /* .ascii-section / .ascii-frame / .ascii-art-container not used */ /* CTA */ -.cta-area { grid-area: cta; align-self: end; display: flex; justify-content: flex-end; } +.cta-area { grid-area: cta; align-self: end; display: flex; justify-content: space-between; align-items: center; gap: 16px; } +.cta-left { display: flex; flex-direction: column; gap: 12px; } +.cta-right { display: flex; align-items: center; } .taisho-cta { position: relative; background: var(--ink); |
