diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/index.html | 2 | ||||
| -rw-r--r-- | frontend/src/components/LandingPage.tsx | 262 | ||||
| -rw-r--r-- | frontend/src/styles/mobile.css | 108 | ||||
| -rw-r--r-- | frontend/src/styles/pc98.css | 323 |
4 files changed, 525 insertions, 170 deletions
diff --git a/frontend/index.html b/frontend/index.html index 095310b..0014d94 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,7 +6,7 @@ <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"> + <link href="https://fonts.googleapis.com/css2?family=DotGothic16&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet"> </head> <body> <div id="root"></div> diff --git a/frontend/src/components/LandingPage.tsx b/frontend/src/components/LandingPage.tsx index f126d6f..e7579b5 100644 --- a/frontend/src/components/LandingPage.tsx +++ b/frontend/src/components/LandingPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react' +import { useState, useEffect } from 'react' import { LoadingScreen } from './LoadingScreen' import { HeartLogo } from './HeartLogo' @@ -9,12 +9,7 @@ interface LandingPageProps { 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(() => { @@ -22,62 +17,6 @@ export function LandingPage({ onLogin }: LandingPageProps) { 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') @@ -91,144 +30,129 @@ export function LandingPage({ onLogin }: LandingPageProps) { setLoading(true) } - const handleMission = () => { - setActivePanel((mode) => (mode === 'mission' ? null : 'mission')) - } - - const handleMakimaPanel = () => { - setActivePanel((mode) => (mode === 'makima' ? null : 'makima')) + const scrollTo = (id: string) => { + document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' }) } return ( <div> {loading && <LoadingScreen onComplete={handleLoadingComplete} />} + {/* Professional floating header */} {!loading && ( - <div className={`floating-header ${showLanding ? 'fade-in' : 'hidden'}`}> - <div className="header-content"> - <div className="brand"> + <div className={`pro-header ${showLanding ? 'fade-in' : 'hidden'}`}> + <div className="pro-header-content"> + <div className="pro-header-left"> <img src="/logo/crane-logo-transparent.png" alt="Soryu" - height={40} - className="brand-mark" + height={36} + className="pro-crane-logo" onError={(e) => { const img = e.currentTarget as HTMLImageElement img.onerror = null img.src = '/logo/crane-logo.png' }} /> + <span className="pro-company-name">SORYU</span> </div> - <div className="header-center"> + + <div className="pro-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> + <nav className="pro-header-nav"> + <button className="pro-nav-link" onClick={() => scrollTo('pro-mission')}> + Mission + </button> + <button className="pro-nav-link" onClick={() => scrollTo('pro-makima')}> + Makima + </button> + <button className="pro-nav-link pro-nav-login" onClick={handleLogin}> + Login + </button> + </nav> </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> + {/* Main professional landing layout */} + <div className={`pro-landing ${showLanding && !loading ? 'fade-in' : 'hidden'}`}> + + {/* Hero section */} + <section className="pro-hero"> + <div className="pro-hero-inner"> + <div className="pro-hero-tagline-jp">そりゅう</div> + <h1 className="pro-hero-headline"> + Real‑Time Systems for<br />Mission‑Critical Observability + </h1> + <p className="pro-hero-sub"> + Low‑latency streaming infrastructure that turns live data into reliable, secure insight. + </p> + <div className="pro-hero-cta"> + <button className="pro-btn-primary" onClick={() => scrollTo('pro-mission')}> + Learn More + </button> + <button className="pro-btn-secondary" onClick={handleLogin}> + <span className="pro-btn-icon">▶</span> Launch Makima + </button> </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> + </section> + + {/* Content grid: Mission + Makima cards */} + <section className="pro-content-grid"> + <div className="pro-card" id="pro-mission"> + <div className="pro-card-header"> + <h2 className="pro-card-title">Mission</h2> + <div className="pro-card-accent" /> + </div> + <div className="pro-card-body"> + <h3 className="pro-card-subtitle"> + Building real‑time systems for mission-critical observability and surveillance + </h3> + <p className="pro-card-text"> + 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> </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 className="pro-card" id="pro-makima"> + <div className="pro-card-header"> + <h2 className="pro-card-title">Makima</h2> + <span className="pro-card-badge">Control System</span> + <div className="pro-card-accent" /> + </div> + <div className="pro-card-body"> + <img + src="/logo/makima-logo.svg" + alt="Makima mesh logo" + className="pro-makima-logo" + /> + <h3 className="pro-card-subtitle">Mesh Orchestration Platform</h3> + <p className="pro-card-text"> + Makima is a control system for orchestrating distributed daemon meshes, + coordinating concurrent execution across distinct domains. + </p> + <p className="pro-card-text"> + Unified command interface for spawning, monitoring, and directing + worker daemons. Real-time task coordination with overlay management. + </p> </div> </div> - )} + </section> + + {/* Footer */} + <footer className="pro-footer"> + <div className="pro-footer-inner"> + <span className="pro-footer-brand">SORYU</span> + <span className="pro-footer-sep">—</span> + <span className="pro-footer-text">Real‑time systems & infrastructure</span> + </div> + </footer> </div> - {/* MissionDrawer removed in favor of mission screen transformation */} </div> ) } diff --git a/frontend/src/styles/mobile.css b/frontend/src/styles/mobile.css index c1c524d..7d1f282 100644 --- a/frontend/src/styles/mobile.css +++ b/frontend/src/styles/mobile.css @@ -51,4 +51,112 @@ .makima-inline-icon { width: 16px; height: 16px; margin-left: 6px; } .makima-list { gap: 4px; } .makima-list li { font-size: 12px; line-height: 1.45; } + + /* ================== Professional Landing Page – Mobile ================== */ + + /* Compact header */ + .pro-header-content { + padding: 0.45rem 1rem; + } + + .pro-company-name { + font-size: 0.85rem; + letter-spacing: 0.12em; + } + + .pro-crane-logo { + height: 28px; + } + + /* Hide center heart on small screens to save space */ + .pro-header-center { + display: none; + } + + /* Full-width nav buttons */ + .pro-header-nav { + gap: 0.15rem; + } + + .pro-nav-link { + font-size: 0.7rem; + padding: 0.3rem 0.5rem; + } + + .pro-nav-login { + margin-left: 0.25rem; + } + + /* Hero – reduce heading size and padding */ + .pro-hero { + min-height: 85vh; + padding: 5rem 1.25rem 3rem; + } + + .pro-hero-headline { + font-size: clamp(1.4rem, 5.5vw, 2rem); + } + + .pro-hero-sub { + font-size: 0.9rem; + margin-bottom: 2rem; + } + + .pro-hero-tagline-jp { + font-size: 0.75rem; + letter-spacing: 0.35em; + } + + .pro-hero-cta { + flex-direction: column; + align-items: stretch; + } + + .pro-btn-primary, + .pro-btn-secondary { + width: 100%; + text-align: center; + justify-content: center; + } + + /* Stack cards to single column */ + .pro-content-grid { + grid-template-columns: 1fr; + padding: 0 1rem 3rem; + gap: 1.5rem; + } + + .pro-card-header { + padding: 1rem 1.25rem 0.6rem; + } + + .pro-card-body { + padding: 0.6rem 1.25rem 1.25rem; + } + + .pro-card-title { + font-size: 1rem; + } + + .pro-card-subtitle { + font-size: 0.88rem; + } + + .pro-card-text { + font-size: 0.8rem; + } + + .pro-makima-logo { + width: 48px; + height: 48px; + } + + /* Footer compact */ + .pro-footer { + padding: 1.25rem 1rem; + } + + .pro-footer-inner { + font-size: 0.7rem; + } } diff --git a/frontend/src/styles/pc98.css b/frontend/src/styles/pc98.css index 7ec0d1c..e591cdc 100644 --- a/frontend/src/styles/pc98.css +++ b/frontend/src/styles/pc98.css @@ -4680,3 +4680,326 @@ button:focus-visible { .capacity-fill.high { background: #ffcc66; } .capacity-fill.full { background: #ff4466; } .refresh-indicator { font-size: 11px; color: rgba(255, 255, 255, 0.3); margin-left: auto; } + +/* ================== Professional Landing Page ================== */ + +/* ── Header ── */ + +.pro-header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + background: rgba(10, 14, 28, 0.85); + border-bottom: 1px solid rgba(0, 200, 255, 0.15); +} + +.pro-header-content { + max-width: 1100px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.6rem 1.5rem; +} + +.pro-header-left { + display: flex; + align-items: center; + gap: 0.65rem; +} + +.pro-crane-logo { + display: block; +} + +.pro-company-name { + font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); + font-size: 1rem; + font-weight: 600; + letter-spacing: 0.18em; + color: #e0e8f0; +} + +.pro-header-center { + display: flex; + align-items: center; + justify-content: center; +} + +.pro-header-nav { + display: flex; + align-items: center; + gap: 0.25rem; +} + +.pro-nav-link { + background: none; + border: 1px solid transparent; + color: #8fa8c8; + font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); + font-size: 0.8rem; + letter-spacing: 0.08em; + padding: 0.35rem 0.75rem; + cursor: pointer; + border-radius: 3px; + transition: color 0.2s, border-color 0.2s; +} + +.pro-nav-link:hover { + color: #00c8ff; + border-color: rgba(0, 200, 255, 0.3); +} + +.pro-nav-login { + color: #00c8ff; + border-color: rgba(0, 200, 255, 0.35); + margin-left: 0.5rem; +} + +.pro-nav-login:hover { + background: rgba(0, 200, 255, 0.08); + border-color: #00c8ff; +} + +/* ── Landing Container ── */ + +.pro-landing { + min-height: 100vh; + background: #080c1a; + color: #c8d4e0; + font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); + overflow-x: hidden; +} + +/* ── Hero ── */ + +.pro-hero { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 6rem 1.5rem 4rem; + text-align: center; + position: relative; +} + +.pro-hero::before { + content: ''; + position: absolute; + inset: 0; + background: + radial-gradient(ellipse 60% 50% at 50% 40%, rgba(0, 100, 180, 0.08) 0%, transparent 100%), + radial-gradient(ellipse 40% 30% at 50% 60%, rgba(0, 200, 255, 0.04) 0%, transparent 100%); + pointer-events: none; +} + +.pro-hero-inner { + position: relative; + max-width: 700px; +} + +.pro-hero-tagline-jp { + font-size: 0.85rem; + letter-spacing: 0.5em; + color: rgba(0, 200, 255, 0.45); + margin-bottom: 1.5rem; + text-transform: uppercase; +} + +.pro-hero-headline { + font-size: clamp(1.8rem, 4vw, 2.8rem); + font-weight: 600; + line-height: 1.25; + color: #e8f0fa; + margin: 0 0 1.25rem; + letter-spacing: -0.01em; +} + +.pro-hero-sub { + font-size: 1rem; + line-height: 1.7; + color: #8fa8c8; + margin: 0 auto 2.5rem; + max-width: 520px; +} + +.pro-hero-cta { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +.pro-btn-primary, +.pro-btn-secondary { + font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); + font-size: 0.85rem; + letter-spacing: 0.06em; + padding: 0.65rem 1.5rem; + border-radius: 3px; + cursor: pointer; + transition: all 0.2s; +} + +.pro-btn-primary { + background: rgba(0, 200, 255, 0.1); + color: #00c8ff; + border: 1px solid rgba(0, 200, 255, 0.35); +} + +.pro-btn-primary:hover { + background: rgba(0, 200, 255, 0.18); + border-color: #00c8ff; +} + +.pro-btn-secondary { + background: transparent; + color: #8fa8c8; + border: 1px solid rgba(140, 170, 200, 0.25); +} + +.pro-btn-secondary:hover { + color: #e0e8f0; + border-color: rgba(140, 170, 200, 0.5); +} + +.pro-btn-icon { + margin-right: 0.35rem; + font-size: 0.75rem; +} + +/* ── Content Grid ── */ + +.pro-content-grid { + max-width: 1100px; + margin: 0 auto; + padding: 0 1.5rem 5rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 440px), 1fr)); + gap: 2rem; +} + +.pro-card { + background: rgba(12, 18, 36, 0.7); + border: 1px solid rgba(0, 200, 255, 0.12); + border-radius: 4px; + overflow: hidden; + scroll-margin-top: 5rem; + transition: border-color 0.3s ease, box-shadow 0.3s ease; +} + +.pro-card:hover { + border-color: rgba(0, 200, 255, 0.28); + box-shadow: 0 0 20px rgba(0, 200, 255, 0.06); +} + +.pro-card-header { + padding: 1.25rem 1.5rem 0.75rem; + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.pro-card-title { + font-size: 1.1rem; + font-weight: 600; + letter-spacing: 0.12em; + color: #e0e8f0; + margin: 0; + text-transform: uppercase; +} + +.pro-card-badge { + font-size: 0.65rem; + letter-spacing: 0.1em; + text-transform: uppercase; + padding: 0.2rem 0.5rem; + background: rgba(0, 200, 255, 0.1); + border: 1px solid rgba(0, 200, 255, 0.25); + border-radius: 2px; + color: #00c8ff; +} + +.pro-card-accent { + flex: 1; + height: 1px; + background: linear-gradient(90deg, rgba(0, 200, 255, 0.3), transparent); +} + +.pro-card-body { + padding: 0.75rem 1.5rem 1.5rem; +} + +.pro-card-subtitle { + font-size: 0.95rem; + font-weight: 500; + color: #c0d0e0; + margin: 0 0 0.75rem; + line-height: 1.5; +} + +.pro-card-text { + font-size: 0.85rem; + line-height: 1.7; + color: #7a90a8; + margin: 0 0 0.6rem; +} + +.pro-card-text:last-child { + margin-bottom: 0; +} + +.pro-makima-logo { + display: block; + width: 64px; + height: 64px; + margin-bottom: 1rem; + opacity: 0.75; +} + +/* ── Footer ── */ + +.pro-footer { + border-top: 1px solid rgba(0, 200, 255, 0.1); + padding: 1.5rem; + text-align: center; +} + +.pro-footer-inner { + font-size: 0.75rem; + color: #4a6080; + letter-spacing: 0.08em; +} + +.pro-footer-brand { + letter-spacing: 0.18em; + color: #5a7898; +} + +.pro-footer-sep { + margin: 0 0.5rem; + opacity: 0.4; +} + +.pro-footer-text { + color: #4a6080; +} + +/* ── Shared transitions ── */ + +.pro-landing.hidden, +.pro-header.hidden { + opacity: 0; + pointer-events: none; +} + +.pro-landing.fade-in, +.pro-header.fade-in { + opacity: 1; + transition: opacity 0.8s ease; +} |
