234 lines
9.5 KiB
TypeScript
234 lines
9.5 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 slides = [
|
|
{
|
|
id: 1,
|
|
title: "Discover",
|
|
highlight: "Melbourne",
|
|
subtitle: "Ultimate Guide to Iconic City",
|
|
description: "From Flinders Street to St Kilda Beach: explore the best of Melbourne'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>
|
|
);
|
|
} |