diff --git a/client/components/GamifiedBanner.tsx b/client/components/GamifiedBanner.tsx new file mode 100644 index 00000000..4684f3e0 --- /dev/null +++ b/client/components/GamifiedBanner.tsx @@ -0,0 +1,124 @@ +import { useEffect, useMemo, useState } from "react"; +import { cn } from "@/lib/utils"; +import { Sparkles, Trophy, Gamepad2 } from "lucide-react"; + +type Props = { + text: string; + enabled?: boolean; + style?: string | null; +}; + +const ACCENTS: Record = { + quest: { + grad: "from-emerald-500/20 via-aethex-500/15 to-neon-blue/20", + glowRing: + "ring-1 ring-emerald-400/30 shadow-[0_0_30px_rgba(16,185,129,0.25)]", + pill: "bg-emerald-500/15 text-emerald-200 border-emerald-400/30", + icon: Trophy, + }, + info: { + grad: "from-aethex-500/20 via-aethex-400/15 to-sky-500/20", + glowRing: + "ring-1 ring-aethex-400/30 shadow-[0_0_30px_rgba(99,102,241,0.25)]", + pill: "bg-aethex-500/15 text-aethex-200 border-aethex-400/30", + icon: Sparkles, + }, + arcade: { + grad: "from-fuchsia-500/20 via-purple-500/15 to-indigo-500/20", + glowRing: + "ring-1 ring-fuchsia-400/30 shadow-[0_0_30px_rgba(217,70,239,0.25)]", + pill: "bg-fuchsia-500/15 text-fuchsia-200 border-fuchsia-400/30", + icon: Gamepad2, + }, + alert: { + grad: "from-amber-500/20 via-orange-500/15 to-rose-500/20", + glowRing: + "ring-1 ring-amber-400/30 shadow-[0_0_30px_rgba(245,158,11,0.25)]", + pill: "bg-amber-500/15 text-amber-100 border-amber-400/30", + icon: Sparkles, + }, +}; + +export function GamifiedBanner({ text, enabled, style }: Props) { + const accentKey = (style || "quest").toLowerCase(); + const accent = ACCENTS[accentKey] || ACCENTS.quest; + const Icon = accent.icon; + + // Simple entrance progress for "charging" bar + const [progress, setProgress] = useState(0); + useEffect(() => { + const t = setTimeout(() => setProgress(100), 80); + return () => clearTimeout(t); + }, []); + + const parts = useMemo(() => { + // If user prefixed an emoji, use it + const m = /^([\p{Emoji}\p{Extended_Pictographic}]+)\s*(.*)$/u.exec(text || ""); + return { + emoji: m?.[1] || "🎮", + body: (m?.[2] || text || "").trim(), + }; + }, [text]); + + if (enabled === false) return null; + + return ( +
+ {/* Ambient gradient */} +
+ + {/* Floating particles */} +
+ {[...Array(16)].map((_, i) => ( +
+ {"◆●▲■".charAt(Math.floor(Math.random() * 4))} +
+ ))} +
+ +
+ {/* Content pill */} +
+ + + {parts.emoji} {parts.body} + + +
+ + {/* Charge bar */} +
+
+
+
+
+ ); +} + +export default GamifiedBanner;