WebP reduces UI payload; JPEG keeps portrait saves compatible.\nFinish ends the visit without prompting another portrait.
115 lines
4.4 KiB
TypeScript
115 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import { Check, ArrowLeft, Wand2 } from "lucide-react";
|
|
import { BoothConfig, BoothStyle } from "@/types/booth";
|
|
import { useI18n } from "@/lib/i18n";
|
|
import { MascotGuide } from "@/components/MascotGuide";
|
|
|
|
interface StyleScreenProps {
|
|
config: BoothConfig;
|
|
styles: BoothStyle[];
|
|
onStyleSelected: (style: BoothStyle) => void;
|
|
onBack: () => void;
|
|
}
|
|
|
|
export const StyleScreen: React.FC<StyleScreenProps> = ({
|
|
config,
|
|
styles,
|
|
onStyleSelected,
|
|
onBack,
|
|
}) => {
|
|
const { t } = useI18n();
|
|
const [selectedId, setSelectedId] = useState<string>(styles[0]?.id || "");
|
|
|
|
const handleConfirm = () => {
|
|
const chosen = styles.find((s) => s.id === selectedId) || styles[0];
|
|
if (chosen) {
|
|
onStyleSelected(chosen);
|
|
}
|
|
};
|
|
|
|
const currentStyle = styles.find((s) => s.id === selectedId);
|
|
|
|
return (
|
|
<div className="relative flex-1 flex flex-col justify-between p-3 sm:p-4 overflow-hidden select-none">
|
|
{/* Top Header - Back on left, Confirm on right */}
|
|
<div className="w-full flex items-center justify-between px-1 mb-1 z-30 min-h-[40px]">
|
|
<button
|
|
onClick={onBack}
|
|
className="p-2 px-3 game-btn-dark flex items-center space-x-1 font-bold text-sm shrink-0 cursor-pointer shadow-md"
|
|
title="Back"
|
|
>
|
|
<ArrowLeft size={16} className="stroke-[2.5]" />
|
|
<span>{t(config.labels.backButton)}</span>
|
|
</button>
|
|
|
|
{/* Top-Right Confirm / Transform Button - Clean text, no icon */}
|
|
<button
|
|
onClick={handleConfirm}
|
|
className="py-2 px-3.5 game-btn-gold font-extrabold text-sm tracking-wider flex items-center justify-center uppercase font-cinzel shadow-lg active:scale-95 cursor-pointer"
|
|
>
|
|
<span>{t(config.labels.confirmStyleButton)}</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Main Styles Grid (2x2) with Game Stone/Wood Frame Card Styling - Moved down with spacing */}
|
|
<div className="w-full max-w-sm mx-auto grid grid-cols-2 gap-3 z-20 mt-3 mb-auto py-1">
|
|
{styles.map((style) => {
|
|
const isSelected = selectedId === style.id;
|
|
const styleName = t(style.name);
|
|
|
|
return (
|
|
<div
|
|
key={style.id}
|
|
onClick={() => setSelectedId(style.id)}
|
|
className={`relative flex flex-col items-center p-2.5 rounded-2xl cursor-pointer transition-all duration-200 ${
|
|
isSelected
|
|
? "game-panel-amber ring-2 ring-amber-400 scale-[1.03] shadow-[0_8px_25px_rgba(249,115,22,0.6)]"
|
|
: "game-panel opacity-85 hover:opacity-100 hover:border-purple-400/80"
|
|
}`}
|
|
>
|
|
{isSelected && (
|
|
<div className="absolute -top-2.5 -right-2.5 w-7 h-7 rounded-full bg-gradient-to-r from-amber-400 to-orange-500 border-2 border-black flex items-center justify-center text-black z-30 shadow-[0_2px_8px_rgba(0,0,0,0.8)]">
|
|
<Check size={16} className="stroke-[3.5]" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Artwork Thumbnail */}
|
|
<div className="relative w-full aspect-square rounded-xl overflow-hidden bg-black/80 border-2 border-[#3d1e6d] shadow-inner">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={style.thumbnail}
|
|
alt={styleName}
|
|
className="w-full h-full object-cover select-none"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
|
|
{/* Title & Tag */}
|
|
<div className="mt-2 text-center w-full px-1">
|
|
<span className="block text-sm font-bold text-amber-100 truncate drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
|
|
{styleName}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Selected Style Description Card & Mascot Comic Guide - Lifted up to slightly overlay bottom of style cards */}
|
|
<div className="w-full max-w-sm mx-auto z-30 flex items-center justify-start gap-1 px-1 -mt-5 sm:-mt-6 pb-2 pointer-events-none">
|
|
<MascotGuide
|
|
pose="punch"
|
|
size="md"
|
|
speechPosition="right"
|
|
speechText={{
|
|
en: "Choose your legendary martial transformation!",
|
|
zh: "挑选你专属的万圣节武道变身奥义!",
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|