236 lines
9.7 KiB
TypeScript
236 lines
9.7 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { motion } from 'motion/react';
|
||
import { ArrowRight } from 'lucide-react';
|
||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||
|
||
interface HeroBannerCarouselProps {
|
||
onCheckoutClick?: () => void;
|
||
onPassesClick?: () => void;
|
||
onEsimsClick?: () => void;
|
||
onHotelDiscountsClick?: () => void;
|
||
}
|
||
|
||
export function HeroBannerCarousel({
|
||
onCheckoutClick,
|
||
onPassesClick,
|
||
onEsimsClick,
|
||
onHotelDiscountsClick
|
||
}: HeroBannerCarouselProps) {
|
||
const [currentSlide, setCurrentSlide] = useState(0);
|
||
const [isPaused, setIsPaused] = useState(false);
|
||
|
||
const cityName = localStorage.getItem("cityName")
|
||
|
||
const slides = [
|
||
{
|
||
id: 1,
|
||
title: "Discover",
|
||
highlight: cityName,
|
||
subtitle: "Ultimate Guide to Iconic City",
|
||
description: cityName === "Melbourne" ? "From Flinders Street to St Kilda Beach: explore the best of Melbourne's landmarks, culture, food and more!" : "From the Sydney Opera House to Bondi Beach: explore the best of Sydney’s landmarks, culture, food and more!",
|
||
image: "https://images.unsplash.com/photo-1757470238279-0e9f331d02c9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBza3lsaW5lJTIwc3Vuc2V0fGVufDF8fHx8MTc2MDUwOTIyMHww&ixlib=rb-4.1.0&q=80&w=1080",
|
||
cta: "Get Started",
|
||
onClick: onCheckoutClick
|
||
},
|
||
{
|
||
id: 2,
|
||
title: "Unlock",
|
||
highlight: "City Passes",
|
||
subtitle: "Save More, Experience More",
|
||
description: "Get unlimited access to top attractions with our flexible city passes. Choose from 1, 2, 3, or 5-day options!",
|
||
image: "https://images.unsplash.com/photo-1743441914096-e8f9aaded6f8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwYXR0cmFjdGlvbiUyMHBhc3N8ZW58MXx8fHwxNzYwNTA5MjIxfDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||
cta: "Explore Passes",
|
||
onClick: onPassesClick
|
||
},
|
||
{
|
||
id: 3,
|
||
title: "Stay",
|
||
highlight: "Connected",
|
||
subtitle: "Travel eSIMs for Every Journey",
|
||
description: "Never lose touch while traveling. Get instant data connectivity in over 190 countries with our eSIM solutions!",
|
||
image: "https://images.unsplash.com/photo-1755286218783-5b8334109336?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtb2JpbGUlMjBwaG9uZSUyMHJvYW1pbmd8ZW58MXx8fHwxNzYwNTA5MjIxfDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||
cta: "Get eSIM",
|
||
onClick: onEsimsClick
|
||
},
|
||
{
|
||
id: 4,
|
||
title: "Exclusive",
|
||
highlight: "Hotel Deals",
|
||
subtitle: "Luxury Stays at Best Prices",
|
||
description: "Book premium hotels at unbeatable rates. Enjoy exclusive discounts on handpicked accommodations worldwide!",
|
||
image: "https://images.unsplash.com/photo-1634041441461-a1789d008830?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxsdXh1cnklMjBob3RlbCUyMGV4dGVyaW9yfGVufDF8fHx8MTc2MDQ5NzY1MXww&ixlib=rb-4.1.0&q=80&w=1080",
|
||
cta: "View Hotels",
|
||
onClick: onHotelDiscountsClick
|
||
}
|
||
];
|
||
|
||
// Auto-scroll effect
|
||
useEffect(() => {
|
||
if (isPaused) return;
|
||
|
||
const interval = setInterval(() => {
|
||
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
||
}, 5000); // Change slide every 5 seconds
|
||
|
||
return () => clearInterval(interval);
|
||
}, [isPaused, slides.length]);
|
||
|
||
const goToSlide = (index: number) => {
|
||
setCurrentSlide(index);
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className="relative w-full min-h-screen overflow-hidden"
|
||
onMouseEnter={() => setIsPaused(true)}
|
||
onMouseLeave={() => setIsPaused(false)}
|
||
>
|
||
{/* Slides */}
|
||
{slides.map((slide, index) => {
|
||
return (
|
||
<motion.div
|
||
key={slide.id}
|
||
className="absolute inset-0 w-full h-full flex"
|
||
initial={{ opacity: 0 }}
|
||
animate={{
|
||
opacity: currentSlide === index ? 1 : 0,
|
||
zIndex: currentSlide === index ? 1 : 0
|
||
}}
|
||
transition={{ duration: 1, ease: "easeInOut" }}
|
||
>
|
||
{/* Left Side - Content Panel (60% width) */}
|
||
<motion.div
|
||
initial={{ opacity: 0, x: -30 }}
|
||
animate={{
|
||
opacity: currentSlide === index ? 1 : 0,
|
||
x: currentSlide === index ? 0 : -30
|
||
}}
|
||
transition={{ duration: 0.8, delay: 0.2 }}
|
||
className="relative w-full md:w-[60%] h-full flex items-center"
|
||
style={{ backgroundColor: '#FFF5F5' }}
|
||
>
|
||
<div className="px-6 sm:px-8 md:px-10 lg:px-16 xl:px-20 py-8 w-full">
|
||
<h1 className="font-poppins leading-tight mb-4 lg:mb-6">
|
||
<div className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl:text-7xl mb-0">
|
||
<span className="font-light" style={{ color: '#1F2937' }}>{slide.title} </span>
|
||
<span className="font-bold text-white px-3 py-1.5 md:px-4 md:py-2 inline-block" style={{ backgroundColor: '#F95F62' }}>
|
||
{slide.highlight}
|
||
</span>
|
||
</div>
|
||
<div className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold mt-3 md:mt-4" style={{ color: '#1F2937', lineHeight: '1.2' }}>
|
||
{slide.subtitle}
|
||
</div>
|
||
</h1>
|
||
|
||
<p className="font-poppins text-base md:text-lg lg:text-xl leading-relaxed font-normal mb-6 lg:mb-8 max-w-2xl" style={{ color: '#4B5563' }}>
|
||
{slide.description}
|
||
</p>
|
||
|
||
<button
|
||
onClick={slide.onClick}
|
||
className="group px-6 py-3 md:px-8 md:py-4 rounded-full flex items-center gap-3 transition-all duration-300 hover:scale-105 shadow-lg hover:shadow-xl font-poppins font-semibold text-base text-white"
|
||
style={{ backgroundColor: '#F95F62' }}
|
||
>
|
||
{slide.cta}
|
||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-300" />
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* Right Side - Full Height Image (40% width) */}
|
||
<motion.div
|
||
initial={{ opacity: 0, x: 30 }}
|
||
animate={{
|
||
opacity: currentSlide === index ? 1 : 0,
|
||
x: currentSlide === index ? 0 : 30
|
||
}}
|
||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||
className="relative w-full md:w-[40%] h-full overflow-hidden"
|
||
>
|
||
<ImageWithFallback
|
||
src={slide.image}
|
||
alt={`${slide.highlight} - ${slide.subtitle}`}
|
||
className="w-full h-full object-cover"
|
||
/>
|
||
|
||
{/* Subtle overlay for depth */}
|
||
<div className="absolute inset-0 bg-gradient-to-l from-transparent to-black/10" />
|
||
</motion.div>
|
||
</motion.div>
|
||
);
|
||
})}
|
||
|
||
{/* Slide Indicators - Bottom Center (All Screens) */}
|
||
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-20 flex gap-3">
|
||
{slides.map((_, index) => (
|
||
<button
|
||
key={index}
|
||
onClick={() => goToSlide(index)}
|
||
className="group"
|
||
aria-label={`Go to slide ${index + 1}`}
|
||
>
|
||
<div
|
||
className={`h-2 rounded-full transition-all duration-300 ${
|
||
currentSlide === index
|
||
? 'w-12'
|
||
: 'w-2 group-hover:opacity-75'
|
||
}`}
|
||
style={{
|
||
backgroundColor: currentSlide === index ? '#F95F62' : '#9CA3AF',
|
||
opacity: currentSlide === index ? 1 : 0.5
|
||
}}
|
||
/>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Numbered Slide Navigation - Bottom Left (Desktop) */}
|
||
<div className="hidden md:block absolute bottom-8 left-8 lg:left-16 z-20">
|
||
<div className="flex gap-6 lg:gap-8">
|
||
{slides.map((slide, index) => (
|
||
<button
|
||
key={index}
|
||
onClick={() => goToSlide(index)}
|
||
className="group text-left transition-all duration-300"
|
||
aria-label={`Go to slide ${index + 1}: ${slide.highlight}`}
|
||
>
|
||
<div className="flex flex-col gap-2">
|
||
{/* Number with active indicator line */}
|
||
<div className="relative">
|
||
{currentSlide === index && (
|
||
<div
|
||
className="absolute -top-2 left-0 right-0 h-0.5 rounded-full transition-all duration-300"
|
||
style={{ backgroundColor: '#F95F62' }}
|
||
/>
|
||
)}
|
||
<span
|
||
className={`font-poppins font-semibold transition-all duration-300 ${
|
||
currentSlide === index
|
||
? 'text-lg'
|
||
: 'text-base'
|
||
}`}
|
||
style={{
|
||
color: currentSlide === index ? '#F95F62' : '#FFFFFF',
|
||
opacity: currentSlide === index ? 1 : 0.6
|
||
}}
|
||
>
|
||
{String(index + 1).padStart(2, '0')}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Label */}
|
||
<div className={`transition-all duration-300 ${
|
||
currentSlide === index ? 'opacity-100' : 'opacity-0 group-hover:opacity-70'
|
||
}`}>
|
||
<p className="font-poppins text-xs text-white/90 whitespace-nowrap max-w-[120px]">
|
||
{slide.highlight}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
} |