Files
hanzo.ai/components/enso/cycle.ts
zeekayandHanzo Dev c24cb0f9d0 Rebuild /enso as the router, and put the measured boards back.
A request resolves to a preset on the fold. The GPQA list and the other runs are read from the leaderboard, and a short call is priced from the billed rate.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-07 13:28:22 -07:00

62 lines
1.7 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { TIME } from './motion'
/**
* Advance a small set while its section is on screen.
*
* The index opens on `rest` and stays there when motion is reduced, so the
* first paint and a reader who declines motion share one frame. Off screen,
* or with the tab hidden, the clock waits. Nothing is hidden either way:
* the caller draws every item, and this only says which one is current.
*/
export function useCycle(id: string, count: number, rest: number) {
const [i, setI] = useState(rest)
const [phase, setPhase] = useState<'weigh' | 'pick'>('pick')
const [play, setPlay] = useState(false)
useEffect(() => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
let stop = false
let timer = 0
const shown = () => {
if (document.hidden) return false
const node = document.getElementById(id)
if (!node) return false
const r = node.getBoundingClientRect()
return r.bottom > 64 && r.top < window.innerHeight - 32
}
const step = () => {
if (stop) return
if (!shown()) {
timer = window.setTimeout(step, 400)
return
}
setPlay(true)
setI((n) => (n + 1) % count)
setPhase('weigh')
timer = window.setTimeout(() => {
if (stop) return
setPhase('pick')
timer = window.setTimeout(step, TIME.hold)
}, TIME.weigh)
}
const hold = window.setTimeout(step, TIME.open)
return () => {
stop = true
window.clearTimeout(hold)
window.clearTimeout(timer)
}
}, [id, count])
return {
i: play ? i : rest,
phase: play ? phase : ('pick' as const),
play,
}
}