| import React, { useState } from "react"; |
| import { motion, AnimatePresence } from "motion/react"; |
| import { Heart, Sparkles, Gift, HeartHandshake, Eye, ArrowRight } from "lucide-react"; |
|
|
| interface OpeningAnimationProps { |
| gfNickname: string; |
| bfName: string; |
| customLetter: string; |
| onOpenComplete: () => void; |
| } |
|
|
| export default function OpeningAnimation({ |
| gfNickname, |
| bfName, |
| customLetter, |
| onOpenComplete, |
| }: OpeningAnimationProps) { |
| const [stage, setStage] = useState<"heybaby" | "envelope" | "letter">("heybaby"); |
|
|
| |
| const particles = React.useMemo(() => { |
| return Array.from({ length: 8 }, (_, i) => ({ |
| id: i, |
| left: `${(i * 12) + 8}%`, |
| delay: i * 0.6, |
| size: (i % 3 === 0) ? 14 : (i % 3 === 1) ? 18 : 22, |
| duration: 12 + (i % 3) * 3, |
| })); |
| }, []); |
|
|
| |
| const letterParagraphs = customLetter.split("\n\n"); |
|
|
| return ( |
| <div className="fixed inset-0 z-50 flex items-center justify-center bg-radial from-[#ffe4e6] via-[#fff1f2] to-[#ffe4e6] overflow-hidden px-4 select-none"> |
| {/* Decorative Floating Hearts Backdrop */} |
| <div className="absolute inset-0 pointer-events-none opacity-40"> |
| {particles.map((p) => ( |
| <motion.div |
| key={p.id} |
| className="absolute text-rose-300" |
| style={{ |
| left: p.left, |
| bottom: "-10%", |
| fontSize: p.size, |
| willChange: "transform", |
| }} |
| animate={{ |
| y: "-120vh", |
| x: p.id % 2 === 0 ? 15 : -15, |
| rotate: p.id % 2 === 0 ? 45 : -45, |
| }} |
| transition={{ |
| duration: p.duration, |
| repeat: Infinity, |
| delay: p.delay, |
| ease: "linear", |
| }} |
| > |
| ❤️ |
| </motion.div> |
| ))} |
| </div> |
| |
| <AnimatePresence mode="wait"> |
| {/* Stage 0: The Sweet Greeting 'hey baby...' */} |
| {stage === "heybaby" && ( |
| <motion.div |
| key="heybaby-stage" |
| initial={{ opacity: 0, scale: 0.95 }} |
| animate={{ opacity: 1, scale: 1 }} |
| exit={{ opacity: 0, y: -40, scale: 1.05 }} |
| transition={{ duration: 0.8 }} |
| className="flex flex-col items-center max-w-md w-full text-center px-4" |
| > |
| {/* Pulsating central love anchor */} |
| <motion.div |
| animate={{ scale: [1, 1.15, 1], rotate: [0, 5, -5, 0] }} |
| transition={{ repeat: Infinity, duration: 2, ease: "easeInOut" }} |
| className="text-rose-500 mb-8" |
| > |
| <Heart className="w-16 h-16 fill-rose-500 stroke-rose-400 drop-shadow-md" /> |
| </motion.div> |
| |
| {/* Title requested by client */} |
| <motion.h1 |
| initial={{ y: 20 }} |
| animate={{ y: 0 }} |
| transition={{ delay: 0.3, type: "spring" }} |
| className="font-script text-7xl md:text-8xl text-rose-600 block leading-tight py-2" |
| > |
| hey baby... |
| </motion.h1> |
| |
| <motion.p |
| initial={{ opacity: 0 }} |
| animate={{ opacity: 1 }} |
| transition={{ delay: 0.7, duration: 0.8 }} |
| className="text-slate-600 font-display text-base md:text-lg font-medium mt-4 tracking-wide max-w-sm" |
| > |
| It's your special day, and i built something sweet just for you |
| </motion.p> |
| |
| <motion.div |
| initial={{ opacity: 0, y: 10 }} |
| animate={{ opacity: 1, y: 0 }} |
| transition={{ delay: 1.3, duration: 0.6 }} |
| className="mt-10" |
| > |
| <motion.button |
| whileHover={{ scale: 1.05 }} |
| whileTap={{ scale: 0.96 }} |
| onClick={() => setStage("envelope")} |
| className="bg-white hover:bg-rose-50 text-rose-600 font-display font-semibold text-sm md:text-base px-8 py-4 rounded-full shadow-lg border border-rose-100 flex items-center gap-2.5 cursor-pointer animate-heartbeat" |
| id="view-gift-button" |
| > |
| <span>Tap to see my surprise</span> |
| <Sparkles className="w-4 h-4 text-rose-500" /> |
| </motion.button> |
| </motion.div> |
| </motion.div> |
| )} |
| |
| {/* Stage 1: Closed Envelope / Pulsing Gift Card */} |
| {stage === "envelope" && ( |
| <motion.div |
| key="envelope-stage" |
| initial={{ scale: 0.9, opacity: 0 }} |
| animate={{ scale: 1, opacity: 1 }} |
| exit={{ scale: 1.05, opacity: 0 }} |
| transition={{ type: "spring", stiffness: 120, damping: 16 }} |
| className="flex flex-col items-center max-w-sm w-full text-center" |
| style={{ willChange: "transform" }} |
| > |
| <div className="mb-6 relative"> |
| <motion.div |
| animate={{ rotate: [-2, 2, -2] }} |
| transition={{ repeat: Infinity, duration: 5, ease: "easeInOut" }} |
| className="bg-white/80 backdrop-blur-md px-6 py-2.5 rounded-full shadow-sm border border-rose-100 text-rose-500 font-display font-medium text-sm inline-flex items-center gap-1.5" |
| style={{ willChange: "transform" }} |
| > |
| <Sparkles className="w-4 h-4 text-rose-400" /> |
| I prepared a letter with love! |
| </motion.div> |
| </div> |
| |
| {/* Interactive Envelope Graphic */} |
| <motion.div |
| whileHover={{ scale: 1.02 }} |
| whileTap={{ scale: 0.98 }} |
| onClick={() => setStage("letter")} |
| className="relative w-80 h-52 bg-gradient-to-br from-rose-400 to-rose-500 rounded-2xl shadow-xl hover:shadow-2xl cursor-pointer flex items-center justify-center border-t-2 border-rose-300 overflow-hidden" |
| style={{ willChange: "transform" }} |
| id="cute-envelope-button" |
| > |
| {/* Envelope flap folds and layers */} |
| <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-rose-300/40 via-transparent to-transparent pointer-events-none" /> |
| |
| {/* Diagonal back lines mimicking envelope flap */} |
| <svg className="absolute inset-0 w-full h-full pointer-events-none" viewBox="0 0 320 208" fill="none"> |
| <path d="M0 0 L160 110 L320 0" stroke="#f43f5e" strokeWidth="2" opacity="0.4" /> |
| <path d="M0 208 L140 100" stroke="#f43f5e" strokeWidth="1.5" opacity="0.3" /> |
| <path d="M320 208 L180 100" stroke="#f43f5e" strokeWidth="1.5" opacity="0.3" /> |
| </svg> |
| |
| {/* Glowing pulsating Wax Seal */} |
| <div className="absolute flex flex-col items-center justify-center"> |
| <motion.div |
| className="w-16 h-16 rounded-full bg-amber-400 flex items-center justify-center shadow-lg border-2 border-amber-300 text-amber-900 animate-heartbeat relative" |
| > |
| <Heart className="w-8 h-8 fill-rose-600 text-rose-600" /> |
| <span className="absolute inset-0 rounded-full border-2 border-dotted border-amber-100/60 scale-90" /> |
| </motion.div> |
| <p className="mt-3 text-white text-xs font-display tracking-widest uppercase font-semibold"> |
| Tap to open seal |
| </p> |
| </div> |
| </motion.div> |
| |
| <p className="mt-6 text-rose-600/80 font-display tracking-wider text-sm"> |
| Sent with absolute adoration by {bfName} |
| </p> |
| </motion.div> |
| )} |
| |
| {/* Stage 2: Revealing the Love Letter */} |
| {stage === "letter" && ( |
| <motion.div |
| key="letter-stage" |
| initial={{ y: 200, opacity: 0 }} |
| animate={{ y: 0, opacity: 1 }} |
| className="w-full max-w-2xl bg-[#fffdfe] rounded-3xl shadow-2xl border border-rose-100 p-6 md:p-10 relative overflow-hidden flex flex-col max-h-[90vh]" |
| > |
| {/* Top golden crest */} |
| <div className="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-rose-300 via-pink-400 to-rose-300" /> |
| |
| {/* Corner floral motifs using shapes */} |
| <div className="absolute -top-8 -right-8 w-24 h-24 bg-rose-100/40 rounded-full pointer-events-none" /> |
| <div className="absolute -bottom-8 -left-8 w-24 h-24 bg-rose-100/40 rounded-full pointer-events-none" /> |
| |
| {/* Letter Header */} |
| <div className="text-center mb-6"> |
| <motion.div |
| initial={{ scale: 0.5 }} |
| animate={{ scale: 1, rotate: [0, -10, 10, 0] }} |
| transition={{ duration: 1.2, delay: 0.2 }} |
| className="w-12 h-12 bg-rose-50 rounded-full flex items-center justify-center mx-auto text-rose-500 border border-rose-100" |
| > |
| <Gift className="w-6 h-6" /> |
| </motion.div> |
| <h2 className="mt-3 font-script text-4xl text-rose-600 font-medium"> |
| Dearest {gfNickname}, |
| </h2> |
| </div> |
| |
| {/* Letter Content Pane (Scrollable within paper) */} |
| <div className="flex-1 overflow-y-auto pr-2 overflow-x-hidden space-y-4 max-h-[50vh] text-slate-700 font-display text-sm md:text-base leading-relaxed text-center sm:text-left"> |
| {letterParagraphs.map((para, idx) => ( |
| <motion.p |
| key={idx} |
| initial={{ opacity: 0, y: 15 }} |
| animate={{ opacity: 1, y: 0 }} |
| transition={{ delay: 0.5 + idx * 0.2 }} |
| className="whitespace-pre-line last:font-semibold" |
| > |
| {para} |
| </motion.p> |
| ))} |
| |
| <motion.div |
| initial={{ opacity: 0 }} |
| animate={{ opacity: 1 }} |
| transition={{ delay: 0.6 + letterParagraphs.length * 0.2 }} |
| className="pt-6 border-t border-rose-50/80 text-right font-script text-3xl text-rose-500" |
| > |
| For you, with love, |
| <div className="font-display text-slate-800 font-semibold text-lg pr-4 mt-1"> |
| — {bfName} |
| </div> |
| </motion.div> |
| </div> |
| |
| {/* Love Footer Navigation To Timeline */} |
| <div className="mt-6 pt-4 border-t border-rose-100/80 text-center flex flex-col items-center"> |
| <motion.button |
| whileHover={{ scale: 1.04 }} |
| whileTap={{ scale: 0.98 }} |
| onClick={onOpenComplete} |
| className="bg-gradient-to-r from-rose-500 to-pink-600 text-white rounded-full px-8 py-3.5 shadow-lg shadow-rose-500/20 hover:shadow-rose-500/30 font-display font-medium text-sm md:text-base flex items-center gap-2.5 cursor-pointer hover:from-rose-600 hover:to-pink-700 transition-colors animate-heartbeat" |
| id="enter-universe-button" |
| > |
| <span>Take a Walk Down Memory Lane</span> |
| <HeartHandshake className="w-5 h-5 fill-white" /> |
| </motion.button> |
| |
| <p className="mt-3 text-slate-400 text-xs font-display flex items-center gap-1"> |
| <Eye className="w-3.5 h-3.5" /> |
| Scroll down & explore interactive widgets next |
| </p> |
| </div> |
| </motion.div> |
| )} |
| </AnimatePresence> |
| </div> |
| ); |
| } |
|
|
|
|