case0 / web /src /ui /checklist.tsx
aliabdelwahab's picture
Per-case visuals, incident vignettes, guided play, no-repeat dealing, four new crime kinds
aabd7c7 verified
Raw
History Blame
2.57 kB
// Detective's checklist: the "what do I do next" rail. Four objectives derived
// entirely from game state; the ACCUSE step pulses when the player is ready.
import { useState } from 'preact/hooks'
import { HOT_SUSPICION, useGame } from '../store'
import { Btn } from './components'
function useObjectives() {
const g = useGame()
const c = g.case
const questioned = c.suspects.filter((s) => (g.state.interrogations[s.id] || []).length > 1).length
const presented = Object.values(g.state.usedEv).some((l) => l.length > 0)
const crackedAny = Object.values(g.state.cracked).some(Boolean)
const hot = Object.values(g.state.suspicion).some((v) => v >= HOT_SUSPICION)
const ready = (questioned >= c.suspects.length && presented && crackedAny) || hot
return { g, total: c.suspects.length, questioned, presented, crackedAny, ready }
}
function Step({ done, label }: { done: boolean; label: string }) {
return (
<div class={'checklist__step' + (done ? ' checklist__step--done' : '')}>
<span class="checklist__box">{done ? '■' : '□'}</span>
<span>{label}</span>
</div>
)
}
export function Checklist({ slim = false }: { slim?: boolean }) {
const { g, total, questioned, presented, crackedAny, ready } = useObjectives()
const doneCount = (questioned >= total ? 1 : 0) + (presented ? 1 : 0) + (crackedAny ? 1 : 0)
const [open, setOpen] = useState(questioned === 0) // collapse once the player gets going
const steps = (
<>
<Step done={questioned >= total} label={`QUESTION EVERY SUSPECT ${questioned}/${total}`} />
<Step done={presented} label="PRESENT EVIDENCE" />
<Step done={crackedAny} label="CRACK A LIE" />
<Btn
sm
variant={ready ? 'ox' : 'ghost'}
className={ready ? 'accuse-cta--pulse' : undefined}
style={{ marginTop: 6 }}
onClick={() => g.nav('accuse')}
>
{ready ? '▸ MAKE THE ACCUSATION' : 'Accuse ▸'}
</Btn>
</>
)
if (!slim) return <div class="checklist">{steps}</div>
return (
<div class="checklist checklist--slim">
<button class="checklist__toggle" onClick={() => setOpen(!open)}>
<span class="t-label" style={{ color: 'var(--amber-2)' }}>OBJECTIVES {doneCount}/3</span>
<span class="t-mono dim">{open ? '▾' : '▸'}</span>
</button>
{open && <div class="col" style={{ gap: 4, paddingTop: 6 }}>{steps}</div>}
{!open && ready && (
<Btn sm variant="ox" className="accuse-cta--pulse" onClick={() => g.nav('accuse')}>▸ ACCUSE</Btn>
)}
</div>
)
}