// Flashback (dual reconstruction) · Timeline · Notes.
import { useState } from 'preact/hooks'
import { useGame } from '../store'
import type { Evidence, FlashbackAccount, TimelineBeat } from '../types'
import { BottomNav, Btn, Chip, EvIcon, Hud, Panel, Scene, perpNoun } from '../ui/components'
function Side({ data, hot }: { data: FlashbackAccount; hot?: boolean }) {
return (
{data.who}
{hot && CONTESTED}
{data.lines.map((l, i) => (
{data.flags.includes(i) && '⚑ '}
{l}
))}
)
}
export function FlashbackScreen() {
const g = useGame()
const F = g.case.flashback
const [ab, setAB] = useState<'a' | 'b'>('a')
return (
g.nav('board')}>Board} />
Two accounts of the same minutes. Where they diverge, the truth is hiding. Oxblood marks a contradiction.
{g.mode === 'mobile' && (
)}
{g.mode === 'mobile' ? (
ab === 'a' ?
:
) : (
)}
g.nav('accuse')}>This is enough. Name the {perpNoun(g.case.kind)} ▸
)
}
export function TimelineScreen() {
const g = useGame()
const c = g.case
const [sel, setSel] = useState(null)
const conflicts = c.timeline.filter((t) => t.conflict)
const evOf = (id?: string | null): Evidence | undefined => (id ? c.evidence.find((e) => e.id === id) : undefined)
return (
g.nav('board')}>Board} />
The reconstructed night. Amber pins are fixed facts; red nodes mark where a statement collides with the evidence.
{c.timeline.map((t, i) => {
const ev = evOf(t.ev)
return (
ev && setSel(t)}>
{t.time}
{ev &&
}
{t.label}
)
})}
{conflicts.length > 0 && (
{conflicts.map((t, i) => (
{t.time}. {t.label}
{evOf(t.ev) && <> The {evOf(t.ev)!.name.toLowerCase()} tells a different story.>}
))}
)}
g.nav('accuse')}>Build the accusation ▸
{sel && (
setSel(null)}>
e.stopPropagation()}>
{sel.time} · {evOf(sel.ev)?.name}
setSel(null)}>✕
{evOf(sel.ev)?.summary}
)}
)
}
export function NotesScreen() {
const g = useGame()
const c = g.case
const grilled = c.suspects.filter((s) => (g.state.interrogations[s.id] || []).length > 1)
const pinned = c.evidence.filter((e) => g.state.pinned.includes(e.id))
const conflicts = c.timeline.filter((t) => t.conflict)
const sections: [string, preact.ComponentChildren][] = [
['SUSPECTS GRILLED', grilled.length ? grilled.map((s) => s.name).join(', ') : 'None yet. Start on the board.'],
['EXHIBITS PINNED', pinned.length ? pinned.map((e) => e.name).join(', ') : 'Pin the exhibits that contradict a statement.'],
['OPEN CONTRADICTIONS', conflicts.length ? conflicts.map((t) => t.label).join(' · ') : 'Reconstruct the timeline to surface them.'],
['THE QUESTION', 'Who could be where the evidence says, when it says — and who had the most to lose?'],
]
return (
g.nav('board')}>Board} />
{sections.map(([k, v], i) => (
))}
g.nav('accuse')}>Name the {perpNoun(g.case.kind)} ▸
)
}