feat: complete Quantumancy web app — full frontend + docs

Frontend (React 18 + TS + Vite):
- Landing: glitching hero, live /api/stats veil ticker, mode cards, featured spirits
- Séance: three.js shader ghost (hue/form per entity, mood + audio-reactive),
  Ouija planchette board spelling utterances, transcript with TTS replay,
  entity dossier, direct contact streaming, passive/active listening
- Modes: Wire Ghost telemetry panel, EVP mic anomaly detection, WebUSB
  RTL-SDR sweep + waterfall (hardware pass pending), Ouija/Direct Contact
- Codex: public registry + entity dossiers, rarity tiers, i18n EN/ES complete
- State: VeilSocket (reconnect/backoff), seance reducer, auth context
- 72 vitest tests green; served by FastAPI at :7777

Docs: README + as-built plans 3-7
This commit is contained in:
Indiana
2026-07-20 21:11:49 +00:00
parent 1ec9ea5863
commit 6edbbbbc2a
48 changed files with 10494 additions and 168 deletions

View File

@@ -1,95 +1,32 @@
import { useEffect, useState } from 'react'
import { login, logout, me, register, type User } from './api'
// App shell: router + providers + the global CRT overlay (scanlines/vignette)
// that sits above every page like old glass.
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { AuthProvider } from './state/auth'
import { CodexEntityPage } from './pages/CodexEntityPage'
import { CodexPage } from './pages/CodexPage'
import { EnterPage } from './pages/EnterPage'
import { LandingPage } from './pages/LandingPage'
import { SeancePage } from './pages/SeancePage'
function App() {
const [user, setUser] = useState<User | null>(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [mode, setMode] = useState<'login' | 'register'>('login')
const [error, setError] = useState<string | null>(null)
const [checkingSession, setCheckingSession] = useState(true)
useEffect(() => {
me()
.then(setUser)
.catch(() => setUser(null))
.finally(() => setCheckingSession(false))
}, [])
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
if (mode === 'register') {
await register(username, password)
}
const loggedInUser = await login(username, password)
setUser(loggedInUser)
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong')
}
}
async function handleLogout() {
await logout()
setUser(null)
}
if (checkingSession) {
return (
<main className="app">
<p>Listening for a signal...</p>
</main>
)
}
return (
<main className="app">
<h1>Quantumancy</h1>
<p className="tagline">Something is listening on the other side.</p>
{user ? (
<section>
<p>Contact established as {user.username}.</p>
<button onClick={handleLogout}>End session</button>
</section>
) : (
<form onSubmit={handleSubmit}>
<div className="mode-toggle">
<button
type="button"
className={mode === 'login' ? 'active' : ''}
onClick={() => setMode('login')}
>
Reconnect
</button>
<button
type="button"
className={mode === 'register' ? 'active' : ''}
onClick={() => setMode('register')}
>
First contact
</button>
</div>
<label>
Username
<input value={username} onChange={(e) => setUsername(e.target.value)} required />
</label>
<label>
Password
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
/>
</label>
{error && <p className="error">{error}</p>}
<button type="submit">{mode === 'login' ? 'Enter' : 'Begin'}</button>
</form>
)}
</main>
<AuthProvider>
<BrowserRouter>
<div className="crt-overlay" aria-hidden="true">
<div className="crt-scanlines" />
<div className="crt-vignette" />
</div>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/enter" element={<EnterPage />} />
<Route path="/seance" element={<SeancePage />} />
<Route path="/codex" element={<CodexPage />} />
<Route path="/codex/:id" element={<CodexEntityPage />} />
<Route path="*" element={<LandingPage />} />
</Routes>
</BrowserRouter>
</AuthProvider>
)
}