feat: integrate Sensei Spook mascot in 4 stances, speech bubbles, and style updates
This commit is contained in:
commit
e04672db2a
104 files changed
+8262
No files matched your search
@@ -0,0 +1,108 @@
|
||||
"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>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user