Files
CityCards-Website/src/components/HeroBannerCarousel.tsx
2026-01-29 14:57:04 +05:30

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>
);
}