Files
mozimo.in/src/components/BannerSlider.tsx
2025-08-19 23:04:20 +05:30

305 lines
10 KiB
TypeScript

"use client";
import { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image";
const banners = [
{ id: 1, src: "/banners/b1.svg", alt: "Mozimo Chocolate Banner 1" },
{ id: 2, src: "/banners/b3.svg", alt: "Mozimo Chocolate Banner 2" },
{ id: 3, src: "/banners/b2.svg", alt: "Mozimo Chocolate Banner 3" },
{ id: 4, src: "/banners/b4.svg", alt: "Mozimo Chocolate Banner 4" },
];
export default function BannerSlider() {
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0);
const [showArrows, setShowArrows] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
// Auto-advance slider
useEffect(() => {
const timer = setInterval(() => {
if (!isHovered) {
setDirection(1);
setCurrentIndex((prevIndex) => (prevIndex + 1) % banners.length);
}
}, 4000);
return () => clearInterval(timer);
}, [isHovered]);
const goToSlide = (index: number) => {
setDirection(index > currentIndex ? 1 : -1);
setCurrentIndex(index);
};
const goToPrevious = () => {
setDirection(-1);
setCurrentIndex((prevIndex) =>
prevIndex === 0 ? banners.length - 1 : prevIndex - 1
);
};
const goToNext = () => {
setDirection(1);
setCurrentIndex((prevIndex) => (prevIndex + 1) % banners.length);
};
const slideVariants = {
enter: (direction: number) => ({
x: direction > 0 ? "100%" : "-100%",
opacity: 1,
}),
center: {
x: 0,
opacity: 1,
},
exit: (direction: number) => ({
x: direction < 0 ? "100%" : "-100%",
opacity: 1,
}),
};
const swipeConfidenceThreshold = 10000;
const swipePower = (offset: number, velocity: number) => {
return Math.abs(offset) * velocity;
};
const paginate = (newDirection: number) => {
if (newDirection > 0) {
goToNext();
} else {
goToPrevious();
}
};
return (
<div
ref={containerRef}
className="relative w-full h-[calc(100vh-80px)] md:h-screen overflow-hidden bg-gradient-to-br from-[#1a0f0a] to-[#2d1810]"
onMouseEnter={() => {
setShowArrows(true);
setIsHovered(true);
}}
onMouseLeave={() => {
setShowArrows(false);
setIsHovered(false);
}}
>
{/* Premium Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute inset-0 bg-gradient-to-br from-amber-200/20 to-orange-200/20" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[length:20px_20px]" />
</div>
{/* Banner Images with Premium Animations */}
<AnimatePresence initial={false} custom={direction} mode="popLayout">
<motion.div
key={`${currentIndex}-${direction}`}
custom={direction}
variants={slideVariants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
duration: 0.4,
}}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={0.7}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(1);
} else if (swipe > swipeConfidenceThreshold) {
paginate(-1);
}
}}
className="absolute inset-0"
>
<Image
src={banners[currentIndex].src}
alt={banners[currentIndex].alt}
fill
className="object-cover img-premium"
draggable={false}
priority={currentIndex === 0}
loading={currentIndex === 0 ? "eager" : "lazy"}
sizes="100vw"
quality={85}
/>
{/* Premium overlay with gradient */}
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</AnimatePresence>
{/* Premium Shop Now Button */}
{currentIndex === 0 && (
<div className="absolute bottom-16 md:bottom-20 left-1/2 transform -translate-x-1/2 z-10">
<motion.button
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.3 }}
whileHover={{
scale: 1.05,
y: -2,
transition: { duration: 0.3 },
}}
whileTap={{ scale: 0.95 }}
className="bg-white text-[#3C2A21] px-8 md:px-12 py-4 md:py-5 rounded-full font-semibold text-base md:text-lg shadow-premium hover:shadow-premium-hover transition-all duration-300 font-renner border border-white/20 hover:border-white/40"
style={{
fontWeight: 300,
fontFamily: "Renner*",
fontSize: "22px",
lineHeight: "100%",
letterSpacing: "0%",
}}
>
<span className="relative z-10">Shop Now</span>
</motion.button>
</div>
)}
{/* Premium Navigation Arrows - Hidden on mobile */}
<AnimatePresence>
{showArrows && (
<>
<motion.button
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
onClick={goToPrevious}
className="hidden md:block absolute left-6 top-1/2 transform -translate-y-1/2 z-20 glass text-white p-4 rounded-full backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/40 shadow-premium hover:shadow-premium-hover"
whileHover={{ scale: 1.1, rotate: -5 }}
whileTap={{ scale: 0.95 }}
aria-label="Previous banner"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</motion.button>
<motion.button
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ duration: 0.3 }}
onClick={goToNext}
className="hidden md:block absolute right-6 top-1/2 transform -translate-y-1/2 z-20 glass text-white p-4 rounded-full backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/40 shadow-premium hover:shadow-premium-hover"
whileHover={{ scale: 1.1, rotate: 5 }}
whileTap={{ scale: 0.95 }}
aria-label="Next banner"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</motion.button>
</>
)}
</AnimatePresence>
{/* Mobile Navigation Arrows */}
<div className="md:hidden absolute inset-x-0 top-1/2 transform -translate-y-1/2 z-20 flex justify-between items-center px-4">
<motion.button
onClick={goToPrevious}
className="glass text-white p-3 rounded-full backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/40 shadow-premium"
whileHover={{ scale: 1.1, rotate: -5 }}
whileTap={{ scale: 0.95 }}
aria-label="Previous banner"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</motion.button>
<motion.button
onClick={goToNext}
className="glass text-white p-3 rounded-full backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/40 shadow-premium"
whileHover={{ scale: 1.1, rotate: 5 }}
whileTap={{ scale: 0.95 }}
aria-label="Next banner"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</motion.button>
</div>
{/* Premium Dots Indicator */}
<div className="absolute bottom-6 md:bottom-8 left-1/2 transform -translate-x-1/2 z-20 hidden md:flex space-x-2 md:space-x-3">
{banners.map((_, index) => (
<motion.button
key={index}
onClick={() => goToSlide(index)}
className={`w-2 h-2 md:w-3 md:h-3 rounded-full transition-all duration-300 ${
index === currentIndex
? "bg-white scale-110 shadow-glow"
: "bg-white/50 hover:bg-white/70 hover:scale-110"
}`}
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
aria-label={`Go to banner ${index + 1}`}
/>
))}
</div>
{/* Premium Progress Bar */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-white/10 z-20">
<motion.div
className="h-full bg-gradient-to-r from-[#8B4513] to-[#DAA520]"
initial={{ width: 0 }}
animate={{ width: "100%" }}
transition={{ duration: 4, ease: "linear" }}
key={currentIndex}
/>
</div>
</div>
);
}