"use client"; import { CheckIcon, CopyIcon, GiftIcon } from "lucide-react"; import { motion, useInView, useReducedMotion } from "motion/react"; import { useEffect, useId, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; export type GiftBoxHue = "sky" | "emerald" | "amber" | "violet" | "rose" | "orange" | "indigo" | "teal"; const hues: Record = { sky: "[--hue-hi:var(--color-sky-300)] [--hue:var(--color-sky-500)] [--hue-deep:var(--color-sky-700)]", emerald: "[--hue-hi:var(--color-emerald-300)] [--hue:var(--color-emerald-500)] [--hue-deep:var(--color-emerald-700)]", amber: "[--hue-hi:var(--color-amber-300)] [--hue:var(--color-amber-500)] [--hue-deep:var(--color-amber-700)]", violet: "[--hue-hi:var(--color-violet-300)] [--hue:var(--color-violet-500)] [--hue-deep:var(--color-violet-700)]", rose: "[--hue-hi:var(--color-rose-300)] [--hue:var(--color-rose-500)] [--hue-deep:var(--color-rose-700)]", orange: "[--hue-hi:var(--color-orange-300)] [--hue:var(--color-orange-500)] [--hue-deep:var(--color-orange-700)]", indigo: "[--hue-hi:var(--color-indigo-300)] [--hue:var(--color-indigo-500)] [--hue-deep:var(--color-indigo-700)]", teal: "[--hue-hi:var(--color-teal-300)] [--hue:var(--color-teal-500)] [--hue-deep:var(--color-teal-700)]", }; export interface GiftBoxProps { amount?: string; code?: string; label?: string; title?: string; description?: string; openTitle?: string; openDescription?: string; hue?: GiftBoxHue; autoPlay?: boolean; className?: string; } const W = 400; const VY = 34; const H = 192; const CX = 200; const BODY = { x: 144, y: 132, w: 112, h: 82 }; const LID = { x: 136, y: 112, w: 128, h: 24 }; const COUPON = { w: 152, h: 84, rest: 136, up: 44, tucked: 0.68 }; const sparkles = [ { x: 108, y: 66, s: 1, d: 0 }, { x: 296, y: 58, s: 0.8, d: 0.12 }, { x: 94, y: 128, s: 0.65, d: 0.24 }, { x: 312, y: 120, s: 0.9, d: 0.06 }, { x: 150, y: 44, s: 0.55, d: 0.3 }, { x: 258, y: 40, s: 0.6, d: 0.18 }, ]; const couponPath = (w: number, h: number, notch: number) => `M9 0 H${notch - 5} A5 5 0 0 0 ${notch + 5} 0 H${w - 9} Q${w} 0 ${w} 9 V${h - 9} Q${w} ${h} ${w - 9} ${h} H${notch + 5} A5 5 0 0 0 ${notch - 5} ${h} H9 Q0 ${h} 0 ${h - 9} V9 Q0 0 9 0 Z`; function useCalmMotion() { const prefers = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted && !!prefers; } export function GiftBox({ amount = "$20", code = "MIRA-20", label = "Referral credit", title = "You've got a reward", description = "Mira joined with your invite link. Open it up to see what you earned.", openTitle = "$20 credit unlocked", openDescription = "It comes off your next invoice. Share the code to give a friend the same.", hue = "rose", autoPlay = true, className, }: GiftBoxProps) { const uid = useId().replace(/:/g, ""); const reduce = useCalmMotion(); const root = useRef(null); const inView = useInView(root); const [open, setOpen] = useState(false); const [touched, setTouched] = useState(false); const [copied, setCopied] = useState(false); useEffect(() => { if (!autoPlay || touched || !inView || reduce) return; const t = setTimeout(() => setOpen((o) => !o), open ? 4200 : 2600); return () => clearTimeout(t); }, [autoPlay, touched, inView, open, reduce]); useEffect(() => { if (!copied) return; const t = setTimeout(() => setCopied(false), 1600); return () => clearTimeout(t); }, [copied]); const toggle = () => { setTouched(true); setOpen((o) => !o); }; const action = async () => { setTouched(true); if (!open) { setOpen(true); return; } try { await navigator.clipboard?.writeText(code); } catch {} setCopied(true); }; const wobbling = !open && inView && !reduce; const instant = { duration: reduce ? 0 : 0.2 }; const notch = 108; return (
{[ { key: "closed", show: !open, t: title, d: description }, { key: "open", show: open, t: openTitle, d: openDescription }, ].map((copy) => (

{copy.t}

{copy.d}

))}
); }