Untitled
unknown
plain_text
2 months ago
17 kB
3
Indexable
"use client" import { motion } from "framer-motion" import type { ClientConfig } from "@/lib/client-config" import { getR2ImageUrl } from "@/lib/r2" import { ChevronDown, Heart, MousePointer, ArrowDown } from "lucide-react" interface PortraitGalleryHeroProps { config: ClientConfig } export function PortraitGalleryHero({ config }: PortraitGalleryHeroProps) { const heroConfig = config.welcomeHero || {} const images = heroConfig.images || [config.highlightImage] // Limit to 3 images maximum const displayImages = images.slice(0, 3) // Determine layout based on number of images const layoutClass = displayImages.length === 1 ? "grid-cols-1" : displayImages.length === 2 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1 md:grid-cols-3" // Visual settings const overlayOpacity = heroConfig.overlayOpacity ?? 0.3 const overlayColor = heroConfig.overlayColor || "black" const overlayGradient = heroConfig.overlayGradient || "gradient-to-b from-black/40 via-transparent to-black/70" const textColor = heroConfig.textColor === "dark" ? "text-gray-800" : "text-white" // Animation settings const animationStyle = heroConfig.animationStyle || "fade" const animationSpeed = heroConfig.animationSpeed || "medium" const animationDuration = animationSpeed === "slow" ? 1.5 : animationSpeed === "fast" ? 0.6 : 1 const animationDelay = animationSpeed === "slow" ? 0.3 : animationSpeed === "fast" ? 0.1 : 0.2 // Typography settings const titleStyle = heroConfig.titleStyle || "elegant" const titleSize = heroConfig.titleSize || "medium" const titleFont = heroConfig.titleFont || "" const subtitleFont = heroConfig.subtitleFont || "" // Content layout settings const contentAlignment = heroConfig.contentAlignment || "center" const contentPosition = heroConfig.contentPosition || "center" const contentWidth = heroConfig.contentWidth || "medium" // Create dynamic classes based on settings const titleStyleClasses = { classic: "font-medium", elegant: "font-light tracking-wide", minimal: "font-thin tracking-wider", script: "font-serif italic", bold: "font-bold", outlined: "text-transparent bg-clip-text bg-gradient-to-r from-white to-gray-100 [-webkit-text-stroke:1px_rgba(255,255,255,0.7)]" }[titleStyle] const titleSizeClasses = { small: "text-2xl sm:text-3xl md:text-4xl lg:text-5xl", medium: "text-3xl sm:text-4xl md:text-5xl lg:text-6xl", large: "text-4xl sm:text-5xl md:text-6xl lg:text-7xl", xlarge: "text-5xl sm:text-6xl md:text-7xl lg:text-8xl" }[titleSize] const contentWidthClasses = { narrow: "max-w-xl", medium: "max-w-3xl", wide: "max-w-5xl", full: "max-w-full px-6" }[contentWidth] const contentAlignmentClasses = { center: "items-center text-center", left: "items-start text-left", right: "items-end text-right" }[contentAlignment] const contentPositionClasses = { center: "justify-center", top: "justify-start pt-24", bottom: "justify-end pb-24" }[contentPosition] // Scroll indicator style const scrollIndicatorStyle = heroConfig.scrollIndicatorStyle || "arrow" // Logo settings const showLogo = heroConfig.showLogo ?? false const logoUrl = heroConfig.logoUrl || "" const logoPosition = heroConfig.logoPosition || "center" const logoSize = heroConfig.logoSize || "medium" const logoSizeClasses = { small: "h-10 w-auto", medium: "h-16 w-auto", large: "h-24 w-auto" }[logoSize] const logoPositionClasses = { center: "mx-auto mb-8", "top-left": "absolute top-8 left-8 z-30", "top-right": "absolute top-8 right-8 z-30", "bottom-left": "absolute bottom-8 left-8 z-30", "bottom-right": "absolute bottom-8 right-8 z-30" }[logoPosition] // Photographer info settings const showPhotographerInfo = heroConfig.showPhotographerInfo ?? true const photographerInfoPosition = heroConfig.photographerInfoPosition || "bottom" const photographerInfoStyle = heroConfig.photographerInfoStyle || "minimal" const photographerPositionClasses = { bottom: "bottom-8 left-0 right-0 text-center", top: "top-8 left-0 right-0 text-center", side: "bottom-8 right-8 text-right", overlay: "bottom-1/4 left-0 right-0 text-center" }[photographerInfoPosition] // Get decorative elements configuration const decorativeElements = heroConfig.decorativeElements || {} const showCorners = decorativeElements.corners !== false const showBorders = decorativeElements.borders !== false const showDividers = decorativeElements.dividers ?? true const showFloral = decorativeElements.floral ?? false const backgroundPattern = decorativeElements.backgroundPattern || "/decorative/floral-pattern.png" // Animation variants based on style const imageAnimationVariants = { fade: { initial: { opacity: 0, scale: 1.05 }, animate: { opacity: 1, scale: 1 } }, slide: { initial: { opacity: 0, x: -50 }, animate: { opacity: 1, x: 0 } }, reveal: { initial: { opacity: 1, clipPath: "polygon(0 0, 0 0, 0 100%, 0% 100%)" }, animate: { opacity: 1, clipPath: "polygon(0 0, 100% 0, 100% 100%, 0 100%)" } }, parallax: { initial: { opacity: 0, y: 50 }, animate: { opacity: 1, y: 0 } }, none: { initial: { opacity: 1 }, animate: { opacity: 1 } } }[animationStyle] // Choose animation for title based on style const titleAnimationVariants = { fade: { initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 } }, slide: { initial: { opacity: 0, x: -30 }, animate: { opacity: 1, x: 0 } }, reveal: { initial: { opacity: 0, y: 50 }, animate: { opacity: 1, y: 0 } }, typing: { initial: { width: "0%" }, animate: { width: "100%" } }, none: { initial: { opacity: 1 }, animate: { opacity: 1 } } }[animationStyle === "typing" ? "typing" : animationStyle] return ( <div className="relative h-screen w-full overflow-hidden bg-black"> {/* Decorative background pattern */} {backgroundPattern && ( <div className="absolute inset-0 bg-repeat opacity-5" style={{ backgroundImage: `url('${backgroundPattern}')` }} ></div> )} {/* Image Grid */} <div className={`grid ${layoutClass} h-full w-full`}> {displayImages.map((image, index) => { const imageUrl = image.startsWith("http") ? image : getR2ImageUrl(config.id, image.split("/").pop() || "") return ( <div key={index} className="relative h-full w-full overflow-hidden"> <motion.div initial={imageAnimationVariants.initial} animate={imageAnimationVariants.animate} transition={{ duration: animationDuration, delay: index * animationDelay }} className="absolute inset-0" > <img src={imageUrl || "/placeholder.svg"} alt={`Wedding portrait ${index + 1}`} className="h-full w-full object-cover object-center" /> <div className={`absolute inset-0 bg-${overlayColor} bg-${overlayGradient}`} style={{ opacity: overlayOpacity }} /> </motion.div> </div> ) })} </div> {/* Decorative frame - only show if borders are enabled */} {showBorders && ( <motion.div className="absolute inset-4 border border-white/10 pointer-events-none z-10" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: animationDuration, delay: animationDelay }} /> )} {/* Logo - show only if configured */} {showLogo && logoUrl && ( <motion.div className={`z-30 ${logoPositionClasses}`} initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: animationDuration, delay: animationDelay }} > <img src={logoUrl} alt="Logo" className={logoSizeClasses} /> </motion.div> )} {/* Content overlay */} <div className={`absolute inset-0 flex flex-col ${contentAlignmentClasses} ${contentPositionClasses} z-20 px-4`}> <div className={contentWidthClasses}> {/* Decorative heart element */} {showFloral && ( <motion.div className="mb-6 flex justify-center" initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: animationDuration, delay: animationDelay * 3 }} > <Heart className="h-8 w-8 text-rose-300/80" /> </motion.div> )} {/* Title with animation based on selected style */} {animationStyle === "typing" ? ( <div className="overflow-hidden"> <motion.h1 className={`${textColor} ${titleSizeClasses} ${titleStyleClasses}`} initial={titleAnimationVariants.initial} animate={titleAnimationVariants.animate} transition={{ duration: animationDuration * 2, delay: animationDelay * 4, ease: "easeInOut" }} style={{ fontFamily: titleFont, whiteSpace: "nowrap", overflow: "hidden" }} > {config.title} </motion.h1> </div> ) : ( <motion.h1 className={`${textColor} ${titleSizeClasses} ${titleStyleClasses} mb-6`} initial={titleAnimationVariants.initial} animate={titleAnimationVariants.animate} transition={{ duration: animationDuration, delay: animationDelay * 4 }} style={{ fontFamily: titleFont }} > {config.title} </motion.h1> )} {/* Elegant divider - show only if dividers are enabled */} {showDividers && ( <motion.div className="my-6 flex items-center justify-center gap-4" initial={{ opacity: 0, scaleX: 0 }} animate={{ opacity: 1, scaleX: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 5 }} > <div className="h-px w-16 bg-white/60"></div> <div className="w-2 h-2 rounded-full bg-rose-300/80"></div> <div className="h-px w-16 bg-white/60"></div> </motion.div> )} {/* Subtitle */} {heroConfig.subtitle && ( <motion.p className={`${textColor} max-w-xl mx-auto text-lg sm:text-xl tracking-wide`} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 6 }} style={{ fontFamily: subtitleFont }} > {heroConfig.subtitle} </motion.p> )} {/* Date */} <motion.div className={`${textColor} mt-8 text-lg sm:text-xl tracking-widest`} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 7 }} > {config.date} </motion.div> </div> </div> {/* Photographer credit - based on position and style */} {showPhotographerInfo && ( <motion.div className={`absolute ${photographerPositionClasses} z-20`} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 8 }} > {photographerInfoStyle === "signature" ? ( <div className="inline-flex items-center px-3 py-1.5 bg-black/20 backdrop-blur-sm rounded-full"> {logoUrl && ( <img src={logoUrl} alt="Logo" className="w-4 h-4 mr-2" /> )} <span className="text-white text-xs font-medium tracking-wide"> {config.photographerName || config.photographerCredit} </span> </div> ) : photographerInfoStyle === "detailed" ? ( <div className="flex items-center justify-center gap-2"> <div className="px-4 py-2 bg-black/30 backdrop-blur-sm rounded-md"> <div className="flex items-center gap-2"> {logoUrl && ( <img src={logoUrl} alt="Logo" className="w-5 h-5" /> )} <div> <p className="text-white text-xs font-medium">{config.photographerName || ""}</p> <p className="text-white/80 text-xs">{config.photographerCredit}</p> </div> </div> </div> </div> ) : ( // Default minimal style <p className="text-white/70 text-xs tracking-widest">{config.photographerCredit}</p> )} </motion.div> )} {/* Scroll Indicator with style variants */} {(heroConfig.scrollIndicator ?? true) && ( <motion.div className="absolute bottom-20 left-0 right-0 flex justify-center z-20" initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0, transition: { duration: animationDuration * 0.8, delay: animationDelay * 9, repeat: Number.POSITIVE_INFINITY, repeatType: "reverse", repeatDelay: 0.5, }, }} > {scrollIndicatorStyle === "dot" ? ( <div className="flex flex-col items-center gap-1"> <div className={`h-2 w-2 rounded-full ${textColor} opacity-80`}></div> <div className={`h-2 w-2 rounded-full ${textColor} opacity-60`}></div> <div className={`h-2 w-2 rounded-full ${textColor} opacity-40`}></div> </div> ) : scrollIndicatorStyle === "icon" ? ( <div className="p-2 border border-white/20 rounded-full"> <MousePointer className="h-4 w-4 text-white/70" /> </div> ) : scrollIndicatorStyle === "text" ? ( <p className="text-sm uppercase tracking-widest text-white/70">Scroll</p> ) : ( // Default arrow style <div className="p-2 border border-white/20 rounded-full"> <ChevronDown className="h-5 w-5 text-white/70" /> </div> )} </motion.div> )} {/* Corner decorations - only show if corners are enabled */} {showCorners && ( <div className="absolute inset-0 pointer-events-none z-10"> <motion.img src="/decorative/floral-corner-1.png" alt="Decorative corner" className="absolute top-8 left-8 w-16 h-16 opacity-40" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 0.4, scale: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 6 }} /> <motion.img src="/decorative/floral-corner-1.png" alt="Decorative corner" className="absolute top-8 right-8 w-16 h-16 opacity-40 transform -scale-x-100" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 0.4, scale: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 7 }} /> <motion.img src="/decorative/floral-corner-1.png" alt="Decorative corner" className="absolute bottom-8 left-8 w-16 h-16 opacity-40 transform -scale-y-100" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 0.4, scale: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 8 }} /> <motion.img src="/decorative/floral-corner-1.png" alt="Decorative corner" className="absolute bottom-8 right-8 w-16 h-16 opacity-40 transform -scale-x-100 -scale-y-100" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 0.4, scale: 1 }} transition={{ duration: animationDuration, delay: animationDelay * 9 }} /> </div> )} </div> ) }
Editor is loading...
Leave a Comment