"use client"; import { useRef, useState } from "react"; export type Phase = "loading-first" | "ready" | "interacting"; const SHADOW = "0 1px 0 rgba(45,24,16,0.05), 0 36px 64px -28px rgba(45,24,16,0.25), 0 8px 18px -6px rgba(45,24,16,0.10)"; export function PlayCanvas({ imageBase64, phase, pendingClick, onClick, fullViewport = false, }: { imageBase64: string | null; phase: Phase; pendingClick: { x: number; y: number } | null; onClick: (click: { x: number; y: number }) => void; fullViewport?: boolean; }) { const imgRef = useRef(null); const [dims, setDims] = useState<{ w: number; h: number } | null>(null); function handleClick(e: React.MouseEvent) { if (phase !== "ready" || !imgRef.current) return; const rect = imgRef.current.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = (e.clientY - rect.top) / rect.height; onClick({ x: Math.max(0, Math.min(1, x)), y: Math.max(0, Math.min(1, y)), }); } const interactive = phase === "ready" && !!imageBase64; const dimmed = phase === "interacting"; // 16:9 sizing — letterbox into available viewport const sizeStyle = fullViewport ? { maxWidth: "100vw", maxHeight: "100dvh" } : { maxWidth: "96vw", maxHeight: "calc(100dvh - 280px)" }; // Placeholder needs an explicit width for aspect-video to compute height. // Pick the largest 16:9 box that fits in the available viewport. const placeholderWidth = fullViewport ? "min(100vw, calc(100dvh * 16 / 9))" : "min(96vw, calc((100dvh - 280px) * 16 / 9))"; return (
{imageBase64 ? (
Generated frame { const img = e.currentTarget; setDims({ w: img.naturalWidth, h: img.naturalHeight }); }} draggable={false} className={`block w-auto h-auto select-none animate-fade-in transition-opacity duration-700 ease-out ${interactive ? "cursor-pointer" : "cursor-wait"} ${dimmed ? "opacity-30" : "opacity-100"}`} style={sizeStyle} /> {!fullViewport && ( <>
)} {pendingClick && ( <>
)}
) : (

正 · 在 · 绘 · 制 · 第 · 一 · 帧

)} {!fullViewport && (
{dims ? `${dims.w} × ${dims.h} · png` : "—"} {phase === "ready" ? "任 · 意 · 点 · 击" : "···"}
)}
); }