1076 lines
45 KiB
TypeScript
1076 lines
45 KiB
TypeScript
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
import { motion, useInView, AnimatePresence } from 'motion/react';
|
|
import { ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
import { Button } from './ui/button';
|
|
import Navbar from './Navbar';
|
|
import { Footer } from './Footer';
|
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
|
import { AttractionHassleFreeSection } from './AttractionHassleFreeSection';
|
|
import { MobileAppSection } from './MobileAppSection';
|
|
import { WhyChooseCityCards } from './WhyChooseCityCards';
|
|
import { EnhancedTestimonials } from './EnhancedTestimonials';
|
|
import { ReviewsSection } from './ReviewsSection';
|
|
import { Layout } from '../Layout';
|
|
|
|
// Hero Carousel Component - Full Screen Pages
|
|
function HeroCarousel({ onCheckoutClick, onPassesClick }: { onCheckoutClick: () => void; onPassesClick: () => void }) {
|
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
const [direction, setDirection] = useState(0);
|
|
|
|
const slides = [
|
|
{
|
|
id: 0,
|
|
type: 'overview',
|
|
title: (
|
|
<>
|
|
<span className="font-light text-gray-900">Your</span>{' '}
|
|
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
|
Smart, Seamless
|
|
</span>
|
|
<br />
|
|
<span className="font-semibold text-gray-900">All-in-One City Pass</span>
|
|
</>
|
|
),
|
|
description: "CityCards is your all-in-one solution for exploring cities worldwide. Get instant access to top attractions, skip long queues, and save up to 40% on your adventures—all from your phone.",
|
|
showButtons: true,
|
|
showStats: true,
|
|
gradient: "from-primary/5 via-white to-secondary/5",
|
|
image: "https://images.unsplash.com/photo-1517144447511-aebb25bbc5fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwc2t5bGluZSUyMHRyYXZlbHxlbnwxfHx8fDE3NjIxNzAxNTF8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
|
|
},
|
|
{
|
|
id: 1,
|
|
type: 'feature',
|
|
title: "Instant Access",
|
|
subtitle: "QR Code Entry",
|
|
description: "Skip the ticket lines with instant QR code access. Just scan your CityCard at 50+ top attractions in every city and walk right in. No printing, no waiting, no hassle.",
|
|
icon: (
|
|
<svg className="w-12 h-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
),
|
|
gradient: "from-blue-500 to-cyan-500",
|
|
bgGradient: "from-blue-50 via-white to-cyan-50",
|
|
image: "https://images.unsplash.com/photo-1713685714770-384c5654f6be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0b3VyaXN0JTIwYXR0cmFjdGlvbnMlMjBjaXR5fGVufDF8fHx8MTc2MjI1MjMyNnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
|
|
showButtons: true,
|
|
},
|
|
{
|
|
id: 2,
|
|
type: 'feature',
|
|
title: "Save Big",
|
|
subtitle: "Up to 40% Off",
|
|
description: "Why pay full price? CityCards bundles the best attractions at a fraction of the cost. Save up to 40% compared to buying individual tickets—the more you explore, the more you save.",
|
|
icon: (
|
|
<svg className="w-12 h-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
),
|
|
gradient: "from-green-500 to-emerald-500",
|
|
bgGradient: "from-green-50 via-white to-emerald-50",
|
|
image: "https://images.unsplash.com/photo-1650821414031-cf7291ce938c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmF2ZWwlMjB2YWNhdGlvbiUyMHNhdmluZ3MlMjBidWRnZXR8ZW58MXx8fHwxNzYyMjUyNTIxfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
|
|
showButtons: true,
|
|
},
|
|
{
|
|
id: 3,
|
|
type: 'feature',
|
|
title: "Magic Itinerary",
|
|
subtitle: "AI-Powered Planning",
|
|
description: "Let AI be your personal travel assistant. Our Magic Itinerary creates customized daily plans based on your interests, optimizes your route, and adapts in real-time. Smart travel made simple.",
|
|
icon: (
|
|
<svg className="w-12 h-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
</svg>
|
|
),
|
|
gradient: "from-purple-500 to-pink-500",
|
|
bgGradient: "from-purple-50 via-white to-pink-50",
|
|
image: "https://images.unsplash.com/photo-1741721816773-ff31d089c227?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbWFydHBob25lJTIwbW9iaWxlJTIwYXBwJTIwdHJhdmVsfGVufDF8fHx8MTc2MjI1MjMyNnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
|
|
showButtons: true,
|
|
}
|
|
];
|
|
|
|
// Auto-advance carousel
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
setDirection(1);
|
|
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
|
}, 6000); // Change slide every 6 seconds
|
|
|
|
return () => clearInterval(timer);
|
|
}, [slides.length]);
|
|
|
|
const nextSlide = () => {
|
|
setDirection(1);
|
|
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
|
};
|
|
|
|
const prevSlide = () => {
|
|
setDirection(-1);
|
|
setCurrentSlide((prev) => (prev - 1 + slides.length) % slides.length);
|
|
};
|
|
|
|
const slideVariants = {
|
|
enter: (direction: number) => ({
|
|
x: direction > 0 ? '100%' : '-100%',
|
|
opacity: 0
|
|
}),
|
|
center: {
|
|
zIndex: 1,
|
|
x: 0,
|
|
opacity: 1
|
|
},
|
|
exit: (direction: number) => ({
|
|
zIndex: 0,
|
|
x: direction < 0 ? '100%' : '-100%',
|
|
opacity: 0
|
|
})
|
|
};
|
|
|
|
const currentSlideData = slides[currentSlide];
|
|
|
|
return (
|
|
<section className="relative min-h-screen overflow-hidden">
|
|
{/* Main Content Container - Split Layout */}
|
|
<div className="relative min-h-screen flex w-full">
|
|
<AnimatePresence initial={false} custom={direction} mode="wait">
|
|
<motion.div
|
|
key={currentSlide}
|
|
className="absolute inset-0 w-full h-full flex"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
{/* Left Side - Content Panel (60% width) */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -30 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.6, 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">
|
|
{currentSlideData.type === 'overview' ? (
|
|
// Overview Slide (First Slide)
|
|
<>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.3 }}
|
|
className="mb-10"
|
|
>
|
|
{/* Title with Badge Style */}
|
|
<h1 className="font-poppins text-4xl sm:text-5xl md:text-6xl lg:text-7xl leading-tight mb-6">
|
|
<span className="font-normal text-gray-900">Your </span>
|
|
<span className="inline-block text-white px-6 py-2 rounded-2xl font-semibold" style={{ backgroundColor: '#F95F62' }}>
|
|
Smart
|
|
</span>
|
|
<br />
|
|
<span className="font-bold text-gray-900">All-in-One City Pass</span>
|
|
</h1>
|
|
|
|
{/* Description */}
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.5 }}
|
|
className="font-poppins font-normal text-base md:text-lg text-gray-700 leading-relaxed max-w-2xl"
|
|
>
|
|
{currentSlideData.description}
|
|
</motion.p>
|
|
</motion.div>
|
|
|
|
{/* CTA Buttons */}
|
|
{currentSlideData.showButtons && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.6 }}
|
|
className="flex flex-col sm:flex-row gap-4"
|
|
>
|
|
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
|
<Button
|
|
onClick={onCheckoutClick}
|
|
withShine={true}
|
|
className="font-poppins font-semibold h-14 px-10 rounded-full text-white bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 transition-all duration-300 shadow-lg hover:shadow-xl"
|
|
>
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', whiteSpace: 'nowrap' }}>
|
|
Get Started <ArrowRight className="w-5 h-5" style={{ display: 'inline-block', flexShrink: 0 }} />
|
|
</span>
|
|
</Button>
|
|
</motion.div>
|
|
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
|
<Button
|
|
onClick={onPassesClick}
|
|
variant="outline"
|
|
className="font-poppins font-semibold h-14 px-10 rounded-full border-2 border-primary !text-primary bg-white hover:!bg-primary hover:!text-white transition-all duration-300"
|
|
>
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', whiteSpace: 'nowrap' }}>
|
|
Learn More
|
|
</span>
|
|
</Button>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</>
|
|
) : (
|
|
// Feature Slides
|
|
<>
|
|
{/* Subtitle Badge */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.3 }}
|
|
className="mb-6"
|
|
>
|
|
<span className="inline-block text-white px-5 py-2 rounded-xl font-poppins font-semibold text-sm" style={{ backgroundColor: '#F95F62' }}>
|
|
{currentSlideData.subtitle}
|
|
</span>
|
|
</motion.div>
|
|
|
|
{/* Title */}
|
|
<motion.h1
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.4 }}
|
|
className="font-poppins text-4xl sm:text-5xl md:text-6xl lg:text-7xl leading-tight font-bold text-gray-900 mb-6"
|
|
>
|
|
{currentSlideData.title}
|
|
</motion.h1>
|
|
|
|
{/* Description */}
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.5 }}
|
|
className="font-poppins font-normal text-base md:text-lg text-gray-700 leading-relaxed max-w-2xl mb-8"
|
|
>
|
|
{currentSlideData.description}
|
|
</motion.p>
|
|
|
|
{/* CTA Buttons */}
|
|
{currentSlideData.showButtons && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.6 }}
|
|
className="flex flex-col sm:flex-row gap-4"
|
|
>
|
|
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
|
<Button
|
|
onClick={onCheckoutClick}
|
|
withShine={true}
|
|
className="font-poppins font-semibold h-14 px-10 rounded-full text-white bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 transition-all duration-300 shadow-lg hover:shadow-xl"
|
|
>
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', whiteSpace: 'nowrap' }}>
|
|
Get Started <ArrowRight className="w-5 h-5" style={{ display: 'inline-block', flexShrink: 0 }} />
|
|
</span>
|
|
</Button>
|
|
</motion.div>
|
|
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
|
<Button
|
|
onClick={onPassesClick}
|
|
variant="outline"
|
|
className="font-poppins font-semibold h-14 px-10 rounded-full border-2 border-primary !text-primary bg-white hover:!bg-primary hover:!text-white transition-all duration-300"
|
|
>
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', whiteSpace: 'nowrap' }}>
|
|
Learn More
|
|
</span>
|
|
</Button>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Right Side - Full Height Image (40% width) */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 30 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.2 }}
|
|
className="relative w-full md:w-[40%] h-full overflow-hidden hidden md:block"
|
|
>
|
|
<ImageWithFallback
|
|
src={currentSlideData.image}
|
|
alt={currentSlideData.title || "CityCards"}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
|
|
{/* Subtle overlay for depth */}
|
|
<div className="absolute inset-0 bg-gradient-to-l from-transparent to-black/5" />
|
|
</motion.div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
{/* Navigation Controls */}
|
|
<div className="absolute bottom-12 left-1/2 -translate-x-1/2 z-20 flex items-center gap-6">
|
|
{/* Arrow Navigation */}
|
|
<button
|
|
onClick={prevSlide}
|
|
className="w-12 h-12 rounded-full bg-white/90 backdrop-blur-sm shadow-lg flex items-center justify-center text-gray-900 hover:bg-white transition-all duration-200 hover:scale-110"
|
|
aria-label="Previous slide"
|
|
>
|
|
<ChevronLeft className="w-6 h-6" />
|
|
</button>
|
|
|
|
{/* Progress Dots */}
|
|
<div className="flex gap-3">
|
|
{slides.map((_, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => {
|
|
setDirection(index > currentSlide ? 1 : -1);
|
|
setCurrentSlide(index);
|
|
}}
|
|
className="group relative focus:outline-none"
|
|
aria-label={`Go to slide ${index + 1}`}
|
|
>
|
|
<div
|
|
className={`h-2 rounded-full transition-all duration-300 ${index === currentSlide
|
|
? 'w-12 bg-primary shadow-lg shadow-primary/30'
|
|
: 'w-2 bg-white/60 group-hover:bg-white group-hover:w-4'
|
|
}`}
|
|
/>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Arrow Navigation */}
|
|
<button
|
|
onClick={nextSlide}
|
|
className="w-12 h-12 rounded-full bg-white/90 backdrop-blur-sm shadow-lg flex items-center justify-center text-gray-900 hover:bg-white transition-all duration-200 hover:scale-110"
|
|
aria-label="Next slide"
|
|
>
|
|
<ChevronRight className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Slide Counter */}
|
|
<div className="absolute top-24 right-8 z-20 bg-white/90 backdrop-blur-sm rounded-full px-4 py-2 shadow-lg">
|
|
<span className="font-poppins font-medium text-sm text-gray-900">
|
|
{currentSlide + 1} / {slides.length}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Scroll Indicator (only on first slide) */}
|
|
{currentSlide === 0 && (
|
|
<motion.div
|
|
className="absolute bottom-8 left-1/2 -translate-x-1/2 z-10"
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 1, duration: 0.6 }}
|
|
>
|
|
<motion.div
|
|
animate={{ y: [0, 8, 0] }}
|
|
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
|
|
className="flex flex-col items-center gap-2"
|
|
>
|
|
<span className="font-poppins text-xs text-gray-600 font-medium">Scroll to explore</span>
|
|
<ChevronRight className="w-5 h-5 text-gray-600 rotate-90" />
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
interface User {
|
|
email: string;
|
|
name: string;
|
|
}
|
|
|
|
interface HowItWorksPageProps {
|
|
onBackClick: () => void;
|
|
onHomeClick: () => void;
|
|
onMelbourneClick: () => void;
|
|
onPassesClick: () => void;
|
|
onCheckoutClick: () => void;
|
|
onSignInClick: () => void;
|
|
onSignOutClick: () => void;
|
|
onAttractionsClick: () => void;
|
|
onBlogsClick: () => void;
|
|
onHowItWorksClick: () => void;
|
|
onFAQClick: () => void;
|
|
onPrivacyPolicyClick: () => void;
|
|
onAboutUsClick: () => void;
|
|
onProfileClick: () => void;
|
|
onCityCardsClick: () => void;
|
|
onMagicItineraryClick: () => void;
|
|
onPostCardsClick: () => void;
|
|
onOffersClick: () => void;
|
|
onContactUsClick?: () => void;
|
|
onEsimsClick?: () => void;
|
|
onHotelDiscountsClick?: () => void;
|
|
onSuperSavingsClick?: () => void;
|
|
currentPage: string;
|
|
user: User | null;
|
|
}
|
|
|
|
interface Step {
|
|
number: string;
|
|
title: string;
|
|
description: string;
|
|
images: string[];
|
|
}
|
|
|
|
const steps: Step[] = [
|
|
{
|
|
number: '1',
|
|
title: 'Choose City',
|
|
description: 'Browse our collection of amazing destinations and select the city you want to explore. From vibrant Melbourne to other exciting locations, find your perfect urban adventure.',
|
|
images: [
|
|
'https://images.unsplash.com/photo-1494522855154-9297ac14b55f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwc2t5bGluZXxlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080',
|
|
'https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwdHJhdmVsfGVufDF8fHx8MTc2MTExNjgzNnww&ixlib=rb-4.1.0&q=80&w=1080',
|
|
'https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwbGFuZG1hcmt8ZW58MXx8fHwxNzYxMTE2ODM4fDA&ixlib=rb-4.1.0&q=80&w=1080'
|
|
]
|
|
},
|
|
{
|
|
number: '2',
|
|
title: 'Get Your Pass',
|
|
description: 'Select from our Unlimited or Selective CityCard options. Get instant digital delivery with access to 50+ top attractions. Skip the lines and unlock exclusive benefits right away.',
|
|
images: [
|
|
'https://images.unsplash.com/photo-1567748534085-467f8a8a475d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmF2ZWwlMjB0aWNrZXRzJTIwcGFzc3xlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080',
|
|
'https://images.unsplash.com/photo-1743017784681-ba0cb69e2ead?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtb2JpbGUlMjBwaG9uZSUyMHRyYXZlbCUyMGJvb2tpbmd8ZW58MXx8fHwxNzYxMTE2ODM2fDA&ixlib=rb-4.1.0&q=80&w=1080'
|
|
]
|
|
},
|
|
{
|
|
number: '3',
|
|
title: 'Explore with Magic Itinerary',
|
|
description: 'Use our AI-powered Magic Itinerary planner to create your perfect experience. Get personalized recommendations, optimized routes, and real-time updates. Download the app to access offline maps, audio guides, and digital tickets all in one place.',
|
|
images: [
|
|
'https://images.unsplash.com/photo-1759802524049-2421ddaee0fe?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwbWFwJTIwcGxhbm5pbmd8ZW58MXx8fHwxNzYxMTE2ODM3fDA&ixlib=rb-4.1.0&q=80&w=1080'
|
|
]
|
|
}
|
|
];
|
|
|
|
// Step Component
|
|
function StepSection({
|
|
step,
|
|
index,
|
|
onInView
|
|
}: {
|
|
step: Step;
|
|
index: number;
|
|
onInView: (index: number) => void;
|
|
}) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const isInView = useInView(ref, {
|
|
margin: '-35% 0px -35% 0px',
|
|
amount: 0.5
|
|
});
|
|
|
|
// Notify parent when this step comes into view
|
|
useEffect(() => {
|
|
if (isInView) {
|
|
onInView(index);
|
|
}
|
|
}, [isInView, index, onInView]);
|
|
|
|
// Rotation angles for diagonal effect
|
|
const rotations = [-3, 3, -2];
|
|
const rotation = rotations[index];
|
|
|
|
// For alternating layout:
|
|
// Step 0 (index 0): Content on left, Image on right
|
|
// Step 1 (index 1): Content on right, Image on left
|
|
// Step 2 (index 2): Content on left, Image on right
|
|
const isContentLeft = index % 2 === 0; // Even indices: content left, image right
|
|
|
|
return (
|
|
<div ref={ref} className={`mx-[0px] ${index === 0 ? 'mt-0 mb-[60px]' : 'my-[60px]'}`}>
|
|
<motion.div
|
|
className="flex items-center"
|
|
initial={{ opacity: 0 }}
|
|
whileInView={{ opacity: 1 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.8 }}
|
|
animate={{
|
|
opacity: isInView ? 1 : 0.35
|
|
}}
|
|
style={{
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden',
|
|
WebkitFontSmoothing: 'antialiased',
|
|
willChange: 'opacity'
|
|
}}
|
|
>
|
|
<div className={`grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-12 items-center w-full`}>
|
|
{/* Text Content - Left side for even indices, right side for odd indices */}
|
|
<motion.div
|
|
className={`space-y-4 ${isContentLeft ? 'lg:order-1' : 'lg:order-2'}`}
|
|
initial={{ opacity: 0, x: isContentLeft ? -50 : 50 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true, margin: "-100px" }}
|
|
transition={{ duration: 0.8 }}
|
|
style={{
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden',
|
|
WebkitFontSmoothing: 'antialiased'
|
|
}}
|
|
>
|
|
<div className="space-y-3 relative">
|
|
{/* Pin next to heading - shows when step is active */}
|
|
{isInView && (
|
|
<motion.div
|
|
className={`absolute top-0 ${isContentLeft ? '-left-8' : '-left-8 lg:left-auto lg:-right-8'}`}
|
|
initial={{ opacity: 0, scale: 0, y: -10 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
transition={{ duration: 0.5, type: "spring", bounce: 0.5 }}
|
|
>
|
|
<div className="relative">
|
|
<div className="absolute top-2 left-1/2 -translate-x-1/2 w-4 h-1 bg-black/20 rounded-full blur-sm" />
|
|
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-[#F95F62] to-[#E53E41] shadow-lg relative border-2 border-white">
|
|
<div className="absolute top-0.5 left-1 w-1.5 h-1.5 bg-white/50 rounded-full" />
|
|
<div className="absolute inset-0 rounded-full bg-[#F95F62] opacity-30 animate-ping" />
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
|
|
<motion.h2
|
|
className="font-poppins text-3xl md:text-4xl lg:text-5xl"
|
|
animate={{
|
|
color: isInView ? '#111827' : '#9ca3af'
|
|
}}
|
|
transition={{ duration: 0.4 }}
|
|
style={{
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden',
|
|
WebkitFontSmoothing: 'antialiased'
|
|
}}
|
|
>
|
|
<span className="font-light block mb-1">{step.number}.</span>
|
|
<span className="font-semibold">{step.title}</span>
|
|
</motion.h2>
|
|
<motion.p
|
|
className="font-poppins text-sm md:text-base leading-relaxed font-normal max-w-xl"
|
|
animate={{
|
|
color: isInView ? '#4b5563' : '#d1d5db'
|
|
}}
|
|
transition={{ duration: 0.4 }}
|
|
style={{
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden',
|
|
WebkitFontSmoothing: 'antialiased'
|
|
}}
|
|
>
|
|
{step.description}
|
|
</motion.p>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Image Grid with Diagonal Rotation - Right side for even indices, left side for odd indices */}
|
|
<motion.div
|
|
className={`relative ${isContentLeft ? 'lg:order-2' : 'lg:order-1'}`}
|
|
initial={{ opacity: 0, x: isContentLeft ? 50 : -50 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true, margin: "-100px" }}
|
|
transition={{ duration: 0.8, delay: 0.2 }}
|
|
style={{
|
|
transformStyle: 'preserve-3d',
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden'
|
|
}}
|
|
>
|
|
{/* Pin on top */}
|
|
<div className="absolute -top-6 left-1/2 -translate-x-1/2 z-30">
|
|
<div className="relative">
|
|
<div className="absolute top-2 left-1/2 -translate-x-1/2 w-6 h-1.5 bg-black/20 rounded-full blur-md" />
|
|
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 shadow-2xl relative border-2 border-white">
|
|
<div className="absolute top-1 left-1.5 w-2 h-2 bg-white/50 rounded-full" />
|
|
<div className="absolute top-full left-1/2 -translate-x-1/2 w-1 h-4 bg-gradient-to-b from-gray-600 to-gray-400" />
|
|
<div className="absolute top-full left-1/2 -translate-x-1/2 w-1.5 h-1.5 bg-gray-500 rounded-full" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<motion.div
|
|
initial={{ rotate: 0 }}
|
|
animate={{
|
|
rotate: isInView ? rotation : 0
|
|
}}
|
|
transition={{
|
|
duration: 0.6,
|
|
ease: [0.34, 1.56, 0.64, 1]
|
|
}}
|
|
style={{
|
|
transformOrigin: 'center center',
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden',
|
|
willChange: 'transform'
|
|
}}
|
|
whileHover={{
|
|
rotate: 0,
|
|
scale: 1.02,
|
|
transition: { duration: 0.3 }
|
|
}}
|
|
>
|
|
<motion.div
|
|
className={`relative rounded-xl overflow-hidden transition-all duration-500 ${isInView
|
|
? 'ring-4 ring-primary'
|
|
: 'ring-2 ring-gray-300'
|
|
}`}
|
|
animate={{
|
|
boxShadow: isInView
|
|
? '0 15px 30px -8px rgba(249, 95, 98, 0.5), 0 6px 20px -3px rgba(249, 95, 98, 0.3)'
|
|
: '0 0 0 rgba(0, 0, 0, 0)'
|
|
}}
|
|
>
|
|
{index === 0 && (
|
|
<div className="p-3 bg-white">
|
|
<div className="relative h-[300px] overflow-hidden rounded-lg">
|
|
<ImageWithFallback
|
|
src={step.images[0]}
|
|
alt="Choose your pass"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{index === 1 && (
|
|
<div className="p-3 bg-white">
|
|
<div className="relative h-[300px] overflow-hidden rounded-lg">
|
|
<ImageWithFallback
|
|
src={step.images[1]}
|
|
alt="Plan your journey"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{index === 2 && (
|
|
<div className="p-3 bg-white">
|
|
<div className="relative h-[300px] overflow-hidden rounded-lg">
|
|
<ImageWithFallback
|
|
src={step.images[0]}
|
|
alt="Explore Melbourne"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Active Glow */}
|
|
{isInView && (
|
|
<motion.div
|
|
className="absolute inset-0 pointer-events-none"
|
|
animate={{
|
|
boxShadow: [
|
|
'inset 0 0 30px rgba(249, 95, 98, 0)',
|
|
'inset 0 0 40px rgba(249, 95, 98, 0.15)',
|
|
'inset 0 0 30px rgba(249, 95, 98, 0)'
|
|
]
|
|
}}
|
|
transition={{
|
|
duration: 2,
|
|
repeat: Infinity,
|
|
ease: 'easeInOut'
|
|
}}
|
|
/>
|
|
)}
|
|
</motion.div>
|
|
</motion.div>
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function HowItWorksPage({
|
|
onHomeClick,
|
|
onMelbourneClick,
|
|
onPassesClick,
|
|
onCheckoutClick,
|
|
onSignInClick,
|
|
onSignOutClick,
|
|
onAttractionsClick,
|
|
onBlogsClick,
|
|
onHowItWorksClick,
|
|
onFAQClick,
|
|
onPrivacyPolicyClick,
|
|
onAboutUsClick,
|
|
onProfileClick,
|
|
onCityCardsClick,
|
|
onMagicItineraryClick,
|
|
onPostCardsClick,
|
|
onOffersClick,
|
|
onContactUsClick,
|
|
onEsimsClick,
|
|
onHotelDiscountsClick,
|
|
onSuperSavingsClick,
|
|
currentPage,
|
|
user
|
|
}: HowItWorksPageProps) {
|
|
const [scrollProgress, setScrollProgress] = useState(0);
|
|
const [activeStep, setActiveStep] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
const progress = (window.scrollY / totalHeight) * 100;
|
|
setScrollProgress(progress);
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
// Callback to update active step
|
|
const handleStepInView = (index: number) => {
|
|
setActiveStep(index);
|
|
};
|
|
|
|
// Calculate path length based on active step
|
|
const getPathLength = () => {
|
|
if (activeStep === 0) return 0.33;
|
|
if (activeStep === 1) return 0.66;
|
|
return 1.0;
|
|
};
|
|
|
|
return (
|
|
<Layout
|
|
activeCity="shared"
|
|
onSignInClick={onSignInClick}
|
|
onSignOutClick={onSignOutClick}
|
|
user={user}
|
|
>
|
|
<div className="min-h-screen bg-white">
|
|
|
|
{/* Hero Section - Full Carousel */}
|
|
<HeroCarousel
|
|
onCheckoutClick={onCheckoutClick}
|
|
onPassesClick={onPassesClick}
|
|
/>
|
|
|
|
{/* Unified How It Works Section with Flight Path */}
|
|
<section className="relative bg-gradient-to-br from-white via-gray-50/30 to-white overflow-hidden pt-[0px] pr-[0px] pb-12 pl-[0px]">
|
|
{/* Curved Flight Path SVG - Desktop Only */}
|
|
<div className="hidden lg:block absolute inset-0 pointer-events-none z-0">
|
|
<svg
|
|
className="w-full h-full"
|
|
viewBox="0 0 1400 1200"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
preserveAspectRatio="xMidYMin slice"
|
|
>
|
|
{/* Define gradients */}
|
|
<defs>
|
|
<linearGradient id="pinGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stopColor="#FF6B6E" />
|
|
<stop offset="50%" stopColor="#F95F62" />
|
|
<stop offset="100%" stopColor="#E53E41" />
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
{/* Main curved flight path - Animated with scroll */}
|
|
<motion.path
|
|
d="M 300 180 Q 500 220, 650 320 Q 750 420, 400 540 Q 600 660, 900 780 Q 700 900, 450 1000"
|
|
stroke="#F95F62"
|
|
strokeWidth="3"
|
|
strokeDasharray="12 8"
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
initial={{ pathLength: 0, opacity: 0 }}
|
|
animate={{
|
|
pathLength: getPathLength(),
|
|
opacity: 0.35
|
|
}}
|
|
transition={{ duration: 1, ease: "easeInOut" }}
|
|
/>
|
|
|
|
{/* Shadow path for depth - follows main path */}
|
|
<motion.path
|
|
d="M 303 183 Q 503 223, 653 323 Q 753 423, 403 543 Q 603 663, 903 783 Q 703 903, 453 1003"
|
|
stroke="#000000"
|
|
strokeWidth="3"
|
|
strokeDasharray="12 8"
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
initial={{ pathLength: 0, opacity: 0 }}
|
|
animate={{
|
|
pathLength: getPathLength(),
|
|
opacity: 0.02
|
|
}}
|
|
transition={{ duration: 1, ease: "easeInOut", delay: 0.1 }}
|
|
/>
|
|
|
|
{/* Destination pin at end of path - appears when fully drawn */}
|
|
{activeStep === 2 && (
|
|
<motion.g
|
|
initial={{ opacity: 0, scale: 0 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.5, delay: 0.8, type: "spring", bounce: 0.5 }}
|
|
>
|
|
{/* Pin shadow */}
|
|
<ellipse
|
|
cx="450"
|
|
cy="1008"
|
|
rx="6"
|
|
ry="1.5"
|
|
fill="#000000"
|
|
opacity="0.2"
|
|
/>
|
|
|
|
{/* Pin circle */}
|
|
<circle
|
|
cx="450"
|
|
cy="1000"
|
|
r="8"
|
|
fill="url(#pinGradient)"
|
|
stroke="white"
|
|
strokeWidth="2"
|
|
/>
|
|
|
|
{/* Pin highlight */}
|
|
<circle
|
|
cx="447"
|
|
cy="997"
|
|
r="2"
|
|
fill="white"
|
|
opacity="0.5"
|
|
/>
|
|
</motion.g>
|
|
)}
|
|
</svg>
|
|
</div>
|
|
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full relative z-10 pt-12">
|
|
{/* Header */}
|
|
<motion.div
|
|
className="text-center mb-4"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
>
|
|
<h1 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
|
|
<span className="font-light">How it</span>{' '}
|
|
<span className="font-semibold">works</span>
|
|
</h1>
|
|
</motion.div>
|
|
|
|
{/* Steps Container */}
|
|
<div className="space-y-8 lg:space-y-10">
|
|
{steps.map((step, index) => (
|
|
<StepSection
|
|
key={step.number}
|
|
step={step}
|
|
index={index}
|
|
onInView={handleStepInView}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
|
|
{/* Additional Sections */}
|
|
<div className="container mx-auto px-4">
|
|
{/* Why Choose CityCards Section */}
|
|
<div className="my-20">
|
|
<WhyChooseCityCards />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Pass Options Overview Section */}
|
|
<section className="py-20 bg-white">
|
|
<div className="container mx-auto px-4">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.6 }}
|
|
className="text-center mb-16"
|
|
>
|
|
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl leading-tight mb-4">
|
|
<span className="font-light text-foreground">Choose Your </span>
|
|
<span className="font-bold text-primary">Perfect Pass</span>
|
|
</h2>
|
|
<p className="font-poppins text-lg text-gray-600 max-w-2xl mx-auto">
|
|
Flexible options designed to match your travel style
|
|
</p>
|
|
</motion.div>
|
|
|
|
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
|
{/* Flexi Card */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -30 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.6 }}
|
|
className="bg-white rounded-3xl p-8 border-2 border-gray-200 hover:border-gray-300 transition-all duration-300 hover:shadow-xl"
|
|
>
|
|
<div className="mb-6">
|
|
<h3 className="font-poppins font-bold text-2xl text-foreground mb-3">
|
|
FLEXI CARD
|
|
</h3>
|
|
<p className="font-poppins text-gray-600 leading-relaxed">
|
|
Perfect for travelers who want to explore selected attractions at their own pace with essential features.
|
|
</p>
|
|
</div>
|
|
|
|
<ul className="space-y-3 mb-6 min-h-[210px]">
|
|
{[
|
|
'Access to selected attractions',
|
|
'Limited number of attractions per pass',
|
|
'Flexible validity period',
|
|
'Priority entry where available',
|
|
'Mobile ticket delivery'
|
|
].map((feature) => (
|
|
<li key={feature} className="flex items-start gap-3">
|
|
<div className="w-5 h-5 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
<svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="font-poppins text-gray-700">{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<div className="mb-4">
|
|
<p className="font-poppins text-xs text-gray-500 text-center">
|
|
✓ Free cancellation up to 24 hours • Instant delivery
|
|
</p>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={onPassesClick}
|
|
className="w-full py-6 rounded-full font-poppins font-semibold text-lg bg-foreground hover:bg-foreground/90 text-white transition-all duration-300"
|
|
>
|
|
VIEW FLEXI OPTIONS
|
|
</Button>
|
|
</motion.div>
|
|
|
|
{/* Unlimited Card */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 30 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.6 }}
|
|
className="bg-white rounded-3xl p-8 border-2 border-primary hover:border-primary/80 transition-all duration-300 hover:shadow-xl relative overflow-hidden"
|
|
>
|
|
{/* Popular Badge */}
|
|
<div className="absolute top-4 right-4">
|
|
<div className="bg-gradient-to-r from-yellow-400 to-orange-500 text-white px-4 py-1 rounded-full font-poppins font-semibold text-sm">
|
|
Most Popular
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-6">
|
|
<h3 className="font-poppins font-bold text-2xl text-foreground mb-3">
|
|
UNLIMITED CARD
|
|
</h3>
|
|
<p className="font-poppins text-gray-600 leading-relaxed">
|
|
The ultimate experience for adventure seekers who want unlimited access to all attractions with premium features.
|
|
</p>
|
|
</div>
|
|
|
|
<ul className="space-y-3 mb-6 min-h-[210px]">
|
|
{[
|
|
'Unlimited access to all attractions',
|
|
'Time-limited validity (7 days)',
|
|
'Skip-the-line access',
|
|
'Expert guide inclusion',
|
|
'Mobile app access',
|
|
'Premium customer support'
|
|
].map((feature) => (
|
|
<li key={feature} className="flex items-start gap-3">
|
|
<div className="w-5 h-5 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
<svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="font-poppins text-gray-700">{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<div className="mb-4">
|
|
<p className="font-poppins text-xs text-gray-500 text-center">
|
|
✓ Free cancellation up to 24 hours • Instant delivery
|
|
</p>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={onPassesClick}
|
|
className="w-full py-6 rounded-full font-poppins font-semibold text-lg bg-primary hover:bg-primary/90 text-white transition-all duration-300"
|
|
>
|
|
VIEW UNLIMITED OPTIONS
|
|
</Button>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="container mx-auto px-4">
|
|
{/* Magic Itinerary Teaser Section */}
|
|
<AttractionHassleFreeSection onMagicItineraryClick={onMagicItineraryClick} />
|
|
|
|
{/* Enhanced Testimonials Section */}
|
|
<EnhancedTestimonials />
|
|
|
|
{/* Mobile App Section */}
|
|
<MobileAppSection />
|
|
</div>
|
|
|
|
{/* CTA Section */}
|
|
<section className="mt-20 py-32 md:py-40 bg-gradient-to-br from-primary to-secondary relative overflow-hidden">
|
|
<div className="absolute inset-0 opacity-10">
|
|
<div className="absolute top-0 left-0 w-96 h-96 bg-white rounded-full blur-3xl" />
|
|
<div className="absolute bottom-0 right-0 w-96 h-96 bg-white rounded-full blur-3xl" />
|
|
</div>
|
|
|
|
<div className="container mx-auto px-4 relative z-10">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.6 }}
|
|
className="text-center max-w-3xl mx-auto"
|
|
>
|
|
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl font-semibold text-white mb-6">
|
|
Ready to Start Your Adventure?
|
|
</h2>
|
|
<p className="font-poppins text-lg md:text-xl text-white/90 mb-8 font-normal leading-relaxed">
|
|
Join millions of travelers who've discovered the smarter way to explore cities
|
|
</p>
|
|
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<Button
|
|
onClick={onCheckoutClick}
|
|
className="px-10 py-6 rounded-full font-poppins font-semibold text-lg bg-white text-primary hover:bg-gray-100 transition-all duration-300 hover:scale-105 shadow-xl"
|
|
>
|
|
Select Your City
|
|
<ArrowRight className="w-5 h-5 ml-2" />
|
|
</Button>
|
|
<Button
|
|
onClick={onPassesClick}
|
|
variant="outline"
|
|
className="px-10 py-6 rounded-full font-poppins font-semibold text-lg border-2 border-white !text-white bg-transparent hover:!bg-white hover:!text-primary transition-all duration-300"
|
|
>
|
|
View All Passes
|
|
</Button>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="container mx-auto px-4">
|
|
{/* Reviews Section */}
|
|
<ReviewsSection />
|
|
</div>
|
|
|
|
</div>
|
|
</Layout>
|
|
);
|
|
}
|
|
|
|
export default HowItWorksPage;
|