From 679127a4f4685aa20fbf55fbd78c3a2e6832dabb Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 16 Nov 2025 18:26:26 +0000 Subject: Landing page redesign: minimalist layout, Mission/Contact buttons, Login redirect; update title and favicon Remove top level backend as it is used. Will be part of this monorepo, but structured differently --- frontend/index.html | 3 +- frontend/src/components/LandingPage.tsx | 237 +++++++++++++++----------------- frontend/src/styles/pc98.css | 20 ++- 3 files changed, 126 insertions(+), 134 deletions(-) (limited to 'frontend') 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 @@ - PC-98 VN Skeleton + soryu.co + 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) - // 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 (
- {loading && ( - - )} - - {/* Floating Header Bar - Hidden during loading */} + {loading && } + {!loading && (
@@ -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' + }} />
@@ -121,20 +139,17 @@ export function LandingPage({ onLogin }: LandingPageProps) {
)} - +
- {/* Retro-futuristic page background */} -