prologue: wire Prologue tab into App, add prologue CSS

This commit is contained in:
otivm
2026-05-03 01:27:08 +00:00
parent 645a593a6d
commit f8c323858c
2 changed files with 189 additions and 2 deletions

View File

@@ -421,3 +421,161 @@
.btn-new-game:hover { .btn-new-game:hover {
color: #6b5a3e; color: #6b5a3e;
} }
/* ── Prologue screen ─────────────────────────────────────────────── */
.prologue-screen {
max-width: 680px;
margin: 0 auto;
padding: 2rem 1rem 4rem;
}
.prologue-header {
margin-bottom: 1.75rem;
}
.prologue-label {
display: block;
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #6b5a3e;
margin-bottom: 0.4rem;
}
.prologue-title {
font-size: 1.6rem;
font-weight: normal;
color: #2a1f0e;
letter-spacing: 0.05em;
margin: 0 0 0.4rem;
}
.prologue-subtitle {
font-size: 0.8rem;
color: #6b5a3e;
font-style: italic;
margin: 0;
}
/* Selection grid — two columns of cards */
.prologue-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 1.5rem;
}
.prologue-card {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
background: #faf7f2;
border: 1px solid #c8b89a;
border-radius: 8px;
padding: 0.875rem 1rem;
cursor: pointer;
text-align: left;
transition: border-color 0.15s, background 0.15s;
}
.prologue-card:hover {
border-color: #8fbc8f;
background: #f0f7f0;
}
.prologue-card--selected {
border: 2px solid #2d5a2d;
background: #f0f7f0;
}
.prologue-card-latin {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #9a8a6e;
}
.prologue-card-name {
font-size: 0.9rem;
font-weight: bold;
color: #2a1f0e;
}
.prologue-card-summary {
font-size: 0.73rem;
color: #6b5a3e;
line-height: 1.5;
}
.prologue-card-den {
font-size: 0.72rem;
color: #2d5a2d;
font-weight: bold;
margin-top: 2px;
}
/* Confirm row */
.prologue-confirm-row {
display: flex;
justify-content: flex-end;
}
.prologue-confirm {
padding: 0.75rem 1.5rem;
border-radius: 6px;
border: 1px solid #2d5a2d;
background: #faf7f2;
color: #2d5a2d;
font-size: 0.85rem;
font-weight: bold;
cursor: pointer;
transition: background 0.15s;
}
.prologue-confirm:hover:not(:disabled) {
background: #d4e8d4;
}
.prologue-confirm:disabled {
opacity: 0.4;
cursor: not-allowed;
border-color: #c8b89a;
color: #6b5a3e;
}
/* Read-only chosen view */
.prologue-chosen {
/* inherits .prologue-screen padding */
}
.prologue-chosen-name {
font-size: 1.4rem;
font-weight: normal;
color: #2a1f0e;
letter-spacing: 0.05em;
margin: 0 0 0.2rem;
}
.prologue-chosen-latin {
display: block;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #9a8a6e;
margin-bottom: 1rem;
}
.prologue-chosen-summary {
font-size: 0.85rem;
color: #6b5a3e;
line-height: 1.7;
font-style: italic;
margin-bottom: 0.75rem;
}
.prologue-chosen-den {
font-size: 0.78rem;
color: #2a1f0e;
}

View File

@@ -1,8 +1,10 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { generateToken, loadState, saveState } from './api.js' import { generateToken, loadState, saveState } from './api.js'
import { createState } from './gameState.js' import { createState } from './gameState.js'
import { BACKGROUNDS } from './constants.js'
import Ledger from './screens/Ledger.jsx' import Ledger from './screens/Ledger.jsx'
import Map from './screens/Map.jsx' import Map from './screens/Map.jsx'
import Prologue from './screens/Prologue.jsx'
import './App.css' import './App.css'
const TOKEN_KEY = 'otivm_token' const TOKEN_KEY = 'otivm_token'
@@ -11,7 +13,7 @@ export default function App() {
const [state, setState] = useState(null) const [state, setState] = useState(null)
const [token, setToken] = useState(null) const [token, setToken] = useState(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [screen, setScreen] = useState('ledger') const [screen, setScreen] = useState('prologue')
useEffect(() => { useEffect(() => {
async function bootstrap() { async function bootstrap() {
@@ -24,10 +26,13 @@ export default function App() {
const saved = await loadState(tok) const saved = await loadState(tok)
if (saved) { if (saved) {
setState(saved) setState(saved)
// If background already chosen, open on ledger
setScreen(saved.background_id ? 'ledger' : 'prologue')
} else { } else {
const fresh = createState(tok) const fresh = createState(tok)
setState(fresh) setState(fresh)
await saveState(tok, fresh) await saveState(tok, fresh)
setScreen('prologue')
} }
setLoading(false) setLoading(false)
} }
@@ -39,6 +44,21 @@ export default function App() {
await saveState(token, newState) await saveState(token, newState)
} }
// Called from Prologue when player confirms a background.
// Seeds background_id into state, saves, switches to Ledger.
async function onSelectBackground(backgroundId) {
const bg = BACKGROUNDS.find(b => b.id === backgroundId)
if (!bg) return
const updated = {
...state,
background_id: backgroundId,
den: bg.starting_den,
}
setState(updated)
await saveState(token, updated)
setScreen('ledger')
}
// Session abandonment — forward-looking lifecycle handler. // Session abandonment — forward-looking lifecycle handler.
// Does not delete the old save. Appends a terminal event so the record // Does not delete the old save. Appends a terminal event so the record
// is complete. The old save becomes a historical artefact on disk. // is complete. The old save becomes a historical artefact on disk.
@@ -62,7 +82,7 @@ export default function App() {
const fresh = createState(newTok) const fresh = createState(newTok)
setState(fresh) setState(fresh)
await saveState(newTok, fresh) await saveState(newTok, fresh)
setScreen('ledger') setScreen('prologue')
} }
if (loading) { if (loading) {
@@ -78,6 +98,12 @@ export default function App() {
<nav className="main-nav"> <nav className="main-nav">
<span className="nav-title">OTIVM</span> <span className="nav-title">OTIVM</span>
<div className="nav-links"> <div className="nav-links">
<button
className={`nav-btn${screen === 'prologue' ? ' active' : ''}`}
onClick={() => setScreen('prologue')}
>
Prologue
</button>
<button <button
className={`nav-btn${screen === 'ledger' ? ' active' : ''}`} className={`nav-btn${screen === 'ledger' ? ' active' : ''}`}
onClick={() => setScreen('ledger')} onClick={() => setScreen('ledger')}
@@ -93,6 +119,9 @@ export default function App() {
</div> </div>
</nav> </nav>
<div className="screen-wrap"> <div className="screen-wrap">
<div style={{ display: screen === 'prologue' ? 'block' : 'none' }}>
<Prologue state={state} onSelectBackground={onSelectBackground} />
</div>
<div style={{ display: screen === 'ledger' ? 'block' : 'none' }}> <div style={{ display: screen === 'ledger' ? 'block' : 'none' }}>
<Ledger state={state} onStateChange={onStateChange} onNewGame={onNewGame} /> <Ledger state={state} onStateChange={onStateChange} onNewGame={onNewGame} />
</div> </div>