prologue: wire Prologue tab into App, add prologue CSS
This commit is contained in:
158
src/App.css
158
src/App.css
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
33
src/App.jsx
33
src/App.jsx
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user