feat(web): responsive layout for phone and tablet (header stack, padding scale, seed-row wrap)
14c6f28 unverified | import { useProgress } from "@/lib/progress"; | |
| function fmt(s: number): string { | |
| const m = Math.floor(s / 60); | |
| const sec = Math.floor(s % 60); | |
| return `${m}:${sec.toString().padStart(2, "0")}`; | |
| } | |
| export default function ProgressBar() { | |
| const state = useProgress(); | |
| if (state.phase === "idle") return null; | |
| if (state.phase === "error") { | |
| return ( | |
| <div className="sticky top-0 z-40 border-b border-red-900/40 bg-red-950/80 backdrop-blur-md px-4 sm:px-8 py-2.5"> | |
| <span className="label-mono text-red-400">progress error</span> | |
| <span className="ml-3 text-sm text-red-300/90 break-words">{state.message}</span> | |
| </div> | |
| ); | |
| } | |
| const isRunning = state.phase === "running"; | |
| const isDialog = isRunning && state.kind === "dialog"; | |
| const fill = | |
| state.phase === "done" | |
| ? 1 | |
| : isDialog && state.total > 0 | |
| ? state.turn / state.total | |
| : null; | |
| const elapsedS = state.phase === "running" ? state.elapsedS : state.phase === "done" ? state.elapsedS : 0; | |
| const label = | |
| state.phase === "done" | |
| ? `done 路 ${fmt(elapsedS)}` | |
| : isDialog | |
| ? `Turn ${state.turn} of ${state.total} 路 ${fmt(elapsedS)}` | |
| : `Generating 路 ${fmt(elapsedS)}`; | |
| return ( | |
| <div className="sticky top-0 z-40 border-b border-[hsl(var(--ember))]/30 bg-[hsl(var(--ember))]/15 backdrop-blur-md px-4 sm:px-8 py-2"> | |
| <div className="flex items-center gap-3 sm:gap-4"> | |
| <span className="label-mono text-[hsl(var(--ember))] whitespace-nowrap text-[10px] sm:text-[10.5px]"> | |
| {label} | |
| </span> | |
| <div className="flex-1 min-w-0 h-1 bg-[hsl(var(--ember))]/20 rounded-sm overflow-hidden"> | |
| {fill === null ? ( | |
| <div className="h-full w-1/3 bg-[hsl(var(--ember))] animate-progress-stripe" /> | |
| ) : ( | |
| <div | |
| className="h-full bg-[hsl(var(--ember))] transition-[width] duration-200 ease-linear" | |
| style={{ width: `${fill * 100}%` }} | |
| /> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |