// 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) => (
{i + 1}. {k}
{v}
))}
g.nav('accuse')}>Name the {perpNoun(g.case.kind)} ▸
) }