"use client"; import React, { useState, useEffect } from "react"; import { Check } from "lucide-react"; import confetti from "canvas-confetti"; import { BoothConfig } from "@/types/booth"; import { useI18n } from "@/lib/i18n"; import { MascotGuide } from "@/components/MascotGuide"; interface ResultScreenProps { config: BoothConfig; resultImageUrl: string; providerInfo?: { provider: string; model: string; durationMs: number; fallbackTriggered: boolean; }; onFinish?: () => void; } export const ResultScreen: React.FC = ({ config, resultImageUrl, providerInfo, onFinish, }) => { const { t, isWeChat } = useI18n(); const [displayImageUrl, setDisplayImageUrl] = useState(resultImageUrl); useEffect(() => { try { confetti({ particleCount: 80, spread: 85, origin: { y: 0.65 }, colors: ["#ff6d00", "#06b6d4", "#a855f7", "#fbbf24", "#ffffff"], }); } catch (e) { // Ignore } }, []); useEffect(() => { let cancelled = false; let jpegObjectUrl: string | undefined; setDisplayImageUrl(resultImageUrl); const prepareJpegForDisplay = async () => { try { const response = await fetch(resultImageUrl); if (!response.ok) return; const sourceBlob = await response.blob(); if (sourceBlob.type.toLowerCase() === "image/jpeg") return; const bitmap = await createImageBitmap(sourceBlob); const canvas = document.createElement("canvas"); canvas.width = bitmap.width; canvas.height = bitmap.height; const context = canvas.getContext("2d"); if (!context) { bitmap.close(); return; } context.fillStyle = "#ffffff"; context.fillRect(0, 0, canvas.width, canvas.height); context.drawImage(bitmap, 0, 0); bitmap.close(); const jpegBlob = await new Promise((resolve, reject) => { canvas.toBlob((blob) => { if (blob) resolve(blob); else reject(new Error("Could not encode portrait as JPEG")); }, "image/jpeg", 0.92); }); jpegObjectUrl = URL.createObjectURL(jpegBlob); if (cancelled) { URL.revokeObjectURL(jpegObjectUrl); } else { setDisplayImageUrl(jpegObjectUrl); } } catch { // Keep the source image when the browser cannot fetch or encode it. } }; void prepareJpegForDisplay(); return () => { cancelled = true; if (jpegObjectUrl) URL.revokeObjectURL(jpegObjectUrl); }; }, [resultImageUrl]); const handleDownload = () => { const a = document.createElement("a"); const isStoredPortrait = resultImageUrl.startsWith("/api/images/"); const isStoredJpeg = isStoredPortrait && /\.jpe?g(?:[?#]|$)/i.test(resultImageUrl); a.href = isStoredJpeg ? `${resultImageUrl}?download=1` : displayImageUrl; const extension = isStoredJpeg ? "jpg" : displayImageUrl.startsWith("blob:") ? "jpg" : resultImageUrl.match(/\.(jpe?g|png|webp)(?:[?#]|$)/i)?.[1] || "webp"; a.download = `shanghai-karate-spooky-${Date.now()}.${extension}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; return (
{/* Top spacing */}
{/* Main Result Image Card - Compact 9:16 frame */}
{/* eslint-disable-next-line @next/next/no-img-element */} Halloween Karate Result {/* WeChat Long-Press Save Floating Badge */} {isWeChat && (
👉 {t(config.labels.wechatSaveTip)}
)}
{/* Mascot sits above a single row of portrait actions. */}
{onFinish && ( )}
); };