Files
karate_halloween/components/MascotGuide.tsx
T

108 lines
4.0 KiB
TypeScript
Raw Normal View History

"use client";
import React from "react";
import { useI18n } from "@/lib/i18n";
export type MascotPose = "bow" | "punch" | "kick" | "zen";
interface MascotGuideProps {
pose?: MascotPose;
speechText?: {
en: string;
zh: string;
};
speechPosition?: "left" | "right" | "top";
className?: string;
speechClassName?: string;
size?: "sm" | "md" | "lg" | "xl" | "2xl";
animate?: boolean;
}
const POSE_IMAGES: Record<MascotPose, string> = {
bow: "/assets/theme/mascot_bow.png",
punch: "/assets/theme/mascot_punch.png",
kick: "/assets/theme/mascot_kick.png",
zen: "/assets/theme/mascot_zen.png",
};
export const MascotGuide: React.FC<MascotGuideProps> = ({
pose = "bow",
speechText,
speechPosition = "right",
className = "",
speechClassName = "",
size = "md",
animate = true,
}) => {
const { t, language } = useI18n();
const sizeClasses = {
sm: "w-28 h-28 sm:w-36 sm:h-36",
md: "w-40 h-40 sm:w-48 sm:h-48",
lg: "w-48 h-48 sm:w-56 sm:h-56",
xl: "w-56 h-56 sm:w-68 sm:h-68",
"2xl": "w-64 h-64 sm:w-80 sm:h-80",
}[size];
return (
<div
className={`relative flex items-center select-none pointer-events-none ${
speechPosition === "left"
? "flex-row-reverse"
: speechPosition === "top"
? "flex-col-reverse items-center"
: "flex-row"
} ${className}`}
>
{/* Mascot Cutout Character */}
<div
className={`relative shrink-0 ${
animate ? "animate-cartoon-wobble" : ""
}`}
>
{/* Soft magical glow backdrop */}
<div className="absolute inset-0 rounded-full bg-amber-400/25 blur-2xl transform scale-125" />
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={POSE_IMAGES[pose]}
alt={`Sensei Spook - ${pose}`}
className={`${sizeClasses} object-contain filter drop-shadow-[0_12px_22px_rgba(0,0,0,0.85)] relative z-10`}
/>
</div>
{/* Comic Speech Bubble */}
{speechText && (
<div
className={`relative z-20 pointer-events-auto ${
speechPosition === "top"
? "mb-1"
: speechPosition === "left"
? "-mr-1 sm:mr-0"
: "-ml-3 sm:-ml-2"
} ${speechClassName}`}
>
<div className="relative px-3.5 py-2.5 rounded-2xl bg-[#1b0f2e]/95 border-2 border-amber-400/90 shadow-[0_4px_18px_rgba(0,0,0,0.85)] max-w-[240px] sm:max-w-[290px] text-left backdrop-blur-sm">
{/* Bubble arrow tail pointing towards the ghost */}
{speechPosition === "right" && (
<div className="absolute -left-2 top-1/2 -translate-y-1/2 w-0 h-0 border-y-[7px] border-y-transparent border-r-[8px] border-r-amber-400 after:content-[''] after:absolute after:-top-[6px] after:left-[2px] after:border-y-[6px] after:border-y-transparent after:border-r-[7px] after:border-r-[#1b0f2e]" />
)}
{speechPosition === "left" && (
<div className="absolute -right-2 top-1/2 -translate-y-1/2 w-0 h-0 border-y-[7px] border-y-transparent border-l-[8px] border-l-amber-400 after:content-[''] after:absolute after:-top-[6px] after:right-[2px] after:border-y-[6px] after:border-y-transparent after:border-l-[7px] after:border-l-[#1b0f2e]" />
)}
{speechPosition === "top" && (
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-0 h-0 border-x-[7px] border-x-transparent border-t-[8px] border-t-amber-400 after:content-[''] after:absolute after:-left-[6px] after:-top-[8px] after:border-x-[6px] after:border-x-transparent after:border-t-[7px] after:border-t-[#1b0f2e]" />
)}
<p
className={`text-amber-100 font-extrabold text-[12px] sm:text-[14px] leading-snug drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)] ${
language === "en" ? "font-cinzel" : "font-dojo"
}`}
>
{t(speechText)}
</p>
</div>
</div>
)}
</div>
);
};