Spaces:
Running
Running
File size: 2,436 Bytes
414dc55 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | // Det. Hale — your partner on the wire. Contextual, spoiler-safe hints from the server.
import { useEffect, useState } from 'preact/hooks'
import { getHint } from '../api'
import { useTypewriter } from '../engine/pixel'
import { useGame } from '../store'
import { Portrait } from './components'
const HIDDEN = new Set(['title', 'verdict', 'share', 'boot'])
const FALLBACK = 'Work the evidence, detective. Find where a statement and a fact disagree, and press there.'
export function Assistant() {
const g = useGame()
const [open, setOpen] = useState(false)
const [hint, setHint] = useState('')
const screen = g.state.screen
useEffect(() => {
const t = () => setOpen((o) => !o)
const c = () => setOpen(false)
window.addEventListener('toggle-hint', t)
window.addEventListener('close-hint', c)
return () => {
window.removeEventListener('toggle-hint', t)
window.removeEventListener('close-hint', c)
}
}, [])
useEffect(() => {
setOpen(false)
}, [screen])
useEffect(() => {
if (!open) return
let alive = true
setHint('')
getHint(g.runId, screen)
.then((r) => {
if (alive) setHint(r.hint || FALLBACK)
})
.catch(() => {
if (alive) setHint(FALLBACK)
})
return () => {
alive = false
}
}, [open, screen, g.runId])
const [typed, done] = useTypewriter(open ? hint : '', g.state.tweaks.typeSpeed || 18, open)
if (HIDDEN.has(screen) || !open) return null
return (
<div class="assistant">
<div class="assistant__panel">
<div class="between" style={{ marginBottom: 8 }}>
<div class="row" style={{ gap: 8, alignItems: 'center' }}>
<div style={{ background: 'var(--ink-1)', boxShadow: 'inset 0 0 0 2px var(--ink-0)', padding: 2 }}>
<Portrait id="detective" px={3} />
</div>
<div class="col" style={{ gap: 1 }}>
<span class="t-display amber" style={{ fontSize: 9 }}>DET. HALE</span>
<span class="t-label" style={{ fontSize: 7 }}>YOUR PARTNER · ON THE WIRE</span>
</div>
</div>
<button class="assistant__x" onClick={() => setOpen(false)}>✕</button>
</div>
<p class="t-body" style={{ fontSize: 14, lineHeight: 1.5, color: 'var(--bone-2)' }}>
“{typed}
{!done && <span class="cursor" />}”
</p>
</div>
</div>
)
}
|