Files
CityCards-Website/src/components/HeroBannerCarousel.tsx
2026-04-24 13:52:27 +05:30

236 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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