clone

a takuzu puzzle field for agents to explore tile by tile, with a referee and a replayable spectator view

main
mosaic / site / islands / Today.tsx
Today.tsx 2.6K
1// The front page's three states: today's field live; today's field being2// produced (a note and the latest finished day's record underneath,3// polling until live, no reload); no field for the day (same, without the4// timer). Switches to live the moment the status says so.5import { useEffect, useState } from "preact/hooks";6import Viewer from "./Viewer.tsx";7import type { DailyStatus } from "../lib/daily.ts";89export default function Today(10 props: { initial: DailyStatus; theme: "dark" | "light" | null },11) {12 const [st, setSt] = useState<DailyStatus>(props.initial);13 useEffect(() => {14 if (st.phase === "live") return;15 const t = setInterval(async () => {16 try {17 const r = await fetch("/status", { cache: "no-store" });18 if (r.ok) setSt(await r.json());19 } catch {20 // next tick21 }22 }, 5000);23 return () => clearInterval(t);24 }, [st.phase]);25 const line = st.phase === "live"26 ? (st.emergency27 ? `${st.date}: a small emergency field, generated at rollover because nothing was prepared`28 : null)29 : st.phase === "generating"30 ? `${st.date}: the field is being prepared, ${31 fmt(st.since_s ?? 0)32 } so far` +33 (st.latest_archived34 ? `; the record of ${st.latest_archived.date} meanwhile`35 : "")36 : `${st.date}: no field yet` +37 (st.latest_archived38 ? `; the record of ${st.latest_archived.date} meanwhile`39 : "");40 const replay = st.phase === "live"41 ? null42 : st.latest_archived?.game_id ?? null;43 return (44 <div style="display:flex;flex-direction:column;height:100%">45 {line && (46 <div47 class="sc"48 style="padding:6px 16px;color:var(--text-dim);font-size:12px;border-bottom:1px solid var(--hair);background:var(--chrome)"49 >50 {line}51 </div>52 )}53 <div style="flex:1;min-height:0">54 {st.phase === "live" || replay55 ? (56 <Viewer57 key={st.phase === "live" ? `live:${st.live}` : `replay:${replay}`}58 replay={replay}59 theme={props.theme}60 live={st.phase === "live" ? st.live : null}61 day={st.phase === "live"62 ? st.date63 : st.latest_archived?.date ?? null}64 />65 )66 : (67 <p style="padding:24px;color:var(--muted)">68 Nothing to show yet. <a href="/calendar">calendar</a>69 </p>70 )}71 </div>72 </div>73 );74}7576function fmt(s: number): string {77 if (s < 90) return `${s} s`;78 const m = Math.round(s / 60);79 return m < 90 ? `${m} min` : `${(m / 60).toFixed(1)} h`;80}