revamp how it works
This commit is contained in:
@@ -1,17 +1,14 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { motion } from 'motion/react';
|
||||
import { Check, ChevronRight, MapPin, Calendar, Smartphone, CreditCard } from 'lucide-react';
|
||||
import { motion, useInView } from 'motion/react';
|
||||
import { Button } from './ui/button';
|
||||
import { Card, CardContent } from './ui/card';
|
||||
import { Badge } from './ui/badge';
|
||||
import Navbar from './Navbar';
|
||||
// import { CitySubmenu } from './CitySubmenu';
|
||||
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 { Footer } from './Footer';
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
@@ -40,48 +37,229 @@ interface HowItWorksPageProps {
|
||||
onContactUsClick?: () => void;
|
||||
onEsimsClick?: () => void;
|
||||
onHotelDiscountsClick?: () => void;
|
||||
onSuperSavingsClick?: () => void;
|
||||
currentPage: string;
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
interface StepItem {
|
||||
id: string;
|
||||
interface Step {
|
||||
number: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
isMainStep: boolean;
|
||||
parentStep?: string;
|
||||
image?: string;
|
||||
features?: string[];
|
||||
images: string[];
|
||||
}
|
||||
|
||||
const steps: StepItem[] = [
|
||||
const steps: Step[] = [
|
||||
{
|
||||
id: 'pick-pass',
|
||||
title: 'Pick Your Pass',
|
||||
description: 'Choose between our Selective or Unlimited pass options. Each designed to match your travel style and give you the freedom to explore Melbourne your way.',
|
||||
icon: <CreditCard className="w-6 h-6" />,
|
||||
isMainStep: true,
|
||||
features: ['Selective or Unlimited options', 'Instant digital delivery', 'Skip-the-line access', 'Mobile-friendly tickets']
|
||||
number: '1',
|
||||
title: 'Choose Your Pass',
|
||||
description: 'Select from our Unlimited or Selective CityCard options. Get instant digital delivery with access to 50+ top Melbourne attractions. Skip the lines and start exploring 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',
|
||||
'https://images.unsplash.com/photo-1677200922658-d0df5b2ac91e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhdHRyYWN0aW9uc3xlbnwxfHx8fDE3NjExMTY4Mzh8MA&ixlib=rb-4.1.0&q=80&w=1080'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'plan-itinerary',
|
||||
number: '2',
|
||||
title: 'Plan Your Journey',
|
||||
description: 'Use our smart planning tools to create your perfect Melbourne itinerary. Get personalized recommendations based on your interests and available time.',
|
||||
icon: <Calendar className="w-6 h-6" />,
|
||||
isMainStep: true,
|
||||
features: ['AI-powered suggestions', 'Real-time crowd updates', 'Weather-based planning', 'Flexible scheduling']
|
||||
description: 'Use our AI-powered Magic Itinerary planner to create your perfect Melbourne experience. Get personalized recommendations, optimized routes, and real-time updates tailored to your interests.',
|
||||
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',
|
||||
'https://images.unsplash.com/photo-1741721816773-ff31d089c227?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbWFydHBob25lJTIwYXBwJTIwdHJhdmVsfGVufDF8fHx8MTc2MTExNjgzOHww&ixlib=rb-4.1.0&q=80&w=1080'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'explore-enjoy',
|
||||
title: 'Explore & Enjoy',
|
||||
description: 'Experience Melbourne like never before with our mobile app, offline maps, and 24/7 support. Every moment is designed to be effortless and memorable.',
|
||||
icon: <MapPin className="w-6 h-6" />,
|
||||
isMainStep: true,
|
||||
features: ['Mobile app included', 'Offline map access', '24/7 customer support', 'Audio guide content']
|
||||
number: '3',
|
||||
title: 'Explore Melbourne',
|
||||
description: 'Download the mobile app and start your adventure. Access offline maps, audio guides, and your digital tickets all in one place. Experience Melbourne like never before with 24/7 support at your fingertips.',
|
||||
images: [
|
||||
'https://images.unsplash.com/photo-1552333709-c465f89a4dfd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0b3VyaXN0JTIwZXhwbG9yaW5nJTIwY2l0eXxlbnwxfHx8fDE3NjExMTY4Mzd8MA&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: '-25% 0px -25% 0px',
|
||||
amount: 0.3
|
||||
});
|
||||
|
||||
// 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];
|
||||
|
||||
return (
|
||||
<div ref={ref} className={`mx-[0px] ${index === 0 ? 'mt-0 mb-[168px]' : 'my-[168px]'}`}>
|
||||
<motion.div
|
||||
className="min-h-screen flex items-center"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
animate={{
|
||||
opacity: isInView ? 1 : 1,
|
||||
}}
|
||||
>
|
||||
<div className={`grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center w-full ${
|
||||
index === 1 ? 'lg:flex lg:flex-row-reverse' : ''
|
||||
}`}>
|
||||
{/* Text Content */}
|
||||
<motion.div
|
||||
className={`space-y-8 ${index === 1 ? 'order-1 lg:order-2' : ''}`}
|
||||
initial={{ opacity: 0, x: index === 1 ? 50 : -50 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8 }}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<motion.h2
|
||||
className="font-poppins text-6xl md:text-7xl lg:text-8xl"
|
||||
animate={{
|
||||
color: isInView ? '#111827' : '#9ca3af'
|
||||
}}
|
||||
transition={{ duration: 0.4 }}
|
||||
>
|
||||
<span className="font-light block mb-2">{step.number}.</span>
|
||||
<span className="font-semibold">{step.title}</span>
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
className="font-poppins text-lg md:text-xl leading-relaxed font-normal max-w-xl"
|
||||
animate={{
|
||||
color: isInView ? '#4b5563' : '#d1d5db'
|
||||
}}
|
||||
transition={{ duration: 0.4 }}
|
||||
>
|
||||
{step.description}
|
||||
</motion.p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Image Grid with Diagonal Rotation */}
|
||||
<motion.div
|
||||
className={`relative ${index === 1 ? 'order-2 lg:order-1' : ''}`}
|
||||
initial={{ opacity: 0, x: index === 1 ? -50 : 50 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
style={{
|
||||
transformStyle: 'preserve-3d'
|
||||
}}
|
||||
>
|
||||
{/* Pin on top */}
|
||||
<div className="absolute -top-8 left-1/2 -translate-x-1/2 z-30">
|
||||
<div className="relative">
|
||||
<div className="absolute top-3 left-1/2 -translate-x-1/2 w-8 h-2 bg-black/20 rounded-full blur-md" />
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 shadow-2xl relative border-3 border-white">
|
||||
<div className="absolute top-1.5 left-2 w-3 h-3 bg-white/50 rounded-full" />
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 w-1.5 h-5 bg-gradient-to-b from-gray-600 to-gray-400" />
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 w-2 h-2 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'
|
||||
}}
|
||||
whileHover={{
|
||||
rotate: 0,
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.3 }
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
className={`relative rounded-2xl overflow-hidden transition-all duration-500 ${
|
||||
isInView
|
||||
? 'ring-[6px] ring-primary'
|
||||
: 'ring-2 ring-gray-300'
|
||||
}`}
|
||||
animate={{
|
||||
boxShadow: isInView
|
||||
? '0 25px 50px -12px rgba(249, 95, 98, 0.5), 0 10px 30px -5px rgba(249, 95, 98, 0.3)'
|
||||
: '0 0 0 rgba(0, 0, 0, 0)'
|
||||
}}
|
||||
>
|
||||
{index === 0 && (
|
||||
<div className="p-4 bg-white">
|
||||
<div className="relative h-[500px] 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-4 bg-white">
|
||||
<div className="relative h-[500px] 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-4 bg-white">
|
||||
<div className="relative h-[500px] 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,
|
||||
@@ -103,67 +281,38 @@ export function HowItWorksPage({
|
||||
onContactUsClick,
|
||||
onEsimsClick,
|
||||
onHotelDiscountsClick,
|
||||
onSuperSavingsClick,
|
||||
currentPage,
|
||||
user
|
||||
}: HowItWorksPageProps) {
|
||||
const [activeStep, setActiveStep] = useState<string>('pick-pass');
|
||||
const [scrollProgress, setScrollProgress] = useState(0);
|
||||
const mainSteps = steps; // All steps are now main steps
|
||||
const stepRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
|
||||
// Scroll-based step activation
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const scrollY = window.scrollY;
|
||||
const windowHeight = window.innerHeight;
|
||||
const containerTop = containerRef.current.offsetTop;
|
||||
const containerHeight = containerRef.current.offsetHeight;
|
||||
|
||||
// Calculate trigger points for each step (when step indicator is in center of viewport)
|
||||
const stepElements = stepRefs.current.filter(Boolean);
|
||||
let currentStepIndex = 0;
|
||||
let minDistance = Infinity;
|
||||
|
||||
stepElements.forEach((stepEl, index) => {
|
||||
if (stepEl) {
|
||||
const stepTop = stepEl.offsetTop + containerTop;
|
||||
const stepCenter = stepTop + stepEl.offsetHeight / 2;
|
||||
const viewportCenter = scrollY + windowHeight / 2;
|
||||
const distance = Math.abs(stepCenter - viewportCenter);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
currentStepIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update active step
|
||||
const newActiveStep = mainSteps[currentStepIndex]?.id || 'pick-pass';
|
||||
if (newActiveStep !== activeStep) {
|
||||
setActiveStep(newActiveStep);
|
||||
}
|
||||
|
||||
// Calculate smooth progress through entire stepper
|
||||
const sectionStart = containerTop - windowHeight / 2;
|
||||
const sectionEnd = containerTop + containerHeight - windowHeight / 2;
|
||||
const rawProgress = (scrollY - sectionStart) / (sectionEnd - sectionStart);
|
||||
const progress = Math.max(0, Math.min(1, rawProgress));
|
||||
|
||||
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const progress = (window.scrollY / totalHeight) * 100;
|
||||
setScrollProgress(progress);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
handleScroll(); // Initial call
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, [activeStep, mainSteps]);
|
||||
}, []);
|
||||
|
||||
// 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 (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Navigation */}
|
||||
<Navbar
|
||||
activeCity="Melbourne"
|
||||
@@ -184,190 +333,178 @@ export function HowItWorksPage({
|
||||
onMagicItineraryClick={onMagicItineraryClick}
|
||||
onPostCardsClick={onPostCardsClick}
|
||||
onOffersClick={onOffersClick}
|
||||
onSuperSavingsClick={onSuperSavingsClick}
|
||||
currentPage={currentPage}
|
||||
isUserSignedIn={!!user}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
{/* <CitySubmenu
|
||||
currentPage={currentPage}
|
||||
onClose={() => {}}
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
/> */}
|
||||
|
||||
<div className="container mx-auto px-4 pt-52 pb-12 relative z-10">
|
||||
{/* Page Header */}
|
||||
<motion.div
|
||||
className="text-center mb-16"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h1 className="font-poppins font-light text-4xl md:text-5xl lg:text-6xl mb-4">
|
||||
How{' '}
|
||||
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
||||
It Works
|
||||
</span>
|
||||
</h1>
|
||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-3xl mx-auto">
|
||||
Discover the simple process that transforms your city exploration from ordinary to extraordinary
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Vertical Stepper with Alternating Layout */}
|
||||
<div ref={containerRef} className="relative max-w-6xl mx-auto mb-20">
|
||||
{/* Central Progress Line */}
|
||||
<div className="absolute left-1/2 transform -translate-x-0.5 top-0 bottom-0 w-1 bg-gray-200 rounded-full">
|
||||
{/* Progress Fill */}
|
||||
<motion.div
|
||||
className="w-full bg-gradient-to-b from-primary to-secondary rounded-full relative"
|
||||
initial={{ height: "0%" }}
|
||||
{/* 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-[70px] 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 2800"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMin slice"
|
||||
>
|
||||
{/* Main curved flight path - Animated with scroll */}
|
||||
<motion.path
|
||||
d="M 200 300 Q 600 450, 800 650 Q 900 850, 350 1100 Q 700 1350, 1100 1600 Q 800 1850, 400 2100"
|
||||
stroke="#F95F62"
|
||||
strokeWidth="4"
|
||||
strokeDasharray="15 10"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{
|
||||
height: `${Math.min(100, scrollProgress * 100)}%`
|
||||
pathLength: getPathLength(),
|
||||
opacity: 0.4
|
||||
}}
|
||||
transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
>
|
||||
{/* Ellipse at the end when progress reaches 100% */}
|
||||
{scrollProgress >= 1 && (
|
||||
<motion.div
|
||||
className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-6 h-3 bg-gradient-to-r from-primary to-secondary rounded-full shadow-lg"
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.4, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
transition={{ duration: 0.8, ease: "easeInOut" }}
|
||||
/>
|
||||
|
||||
{/* Shadow path for depth - follows main path */}
|
||||
<motion.path
|
||||
d="M 203 305 Q 603 455, 803 655 Q 903 855, 353 1105 Q 703 1355, 1103 1605 Q 803 1855, 403 2105"
|
||||
stroke="#000000"
|
||||
strokeWidth="4"
|
||||
strokeDasharray="15 10"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{
|
||||
pathLength: getPathLength(),
|
||||
opacity: 0.02
|
||||
}}
|
||||
transition={{ duration: 0.8, ease: "easeInOut", delay: 0.1 }}
|
||||
/>
|
||||
|
||||
{/* 3D Pin marker at the end - only shows when all steps complete */}
|
||||
{activeStep === 2 && (
|
||||
<motion.g
|
||||
initial={{ opacity: 0, scale: 0, y: -20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4, type: "spring", bounce: 0.5 }}
|
||||
>
|
||||
{/* Pin shadow */}
|
||||
<ellipse
|
||||
cx="400"
|
||||
cy="2145"
|
||||
rx="18"
|
||||
ry="4"
|
||||
fill="black"
|
||||
opacity="0.25"
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Steps Container */}
|
||||
<div className="space-y-40">
|
||||
{mainSteps.map((step, index) => {
|
||||
const isActive = activeStep === step.id;
|
||||
const isLeft = index % 2 === 0; // Step 1 & 3 on left, Step 2 on right
|
||||
const stepNumber = `0${index + 1}`;
|
||||
{/* Pin body/stick - gradient effect */}
|
||||
<path
|
||||
d="M 400 2070 L 398 2130 L 402 2130 Z"
|
||||
fill="url(#pinGradient)"
|
||||
opacity="0.8"
|
||||
/>
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={step.id}
|
||||
ref={(el) => stepRefs.current[index] = el}
|
||||
className="relative"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.2 }}
|
||||
>
|
||||
{/* Step Indicator Circle - Always in center */}
|
||||
<motion.div
|
||||
className={`
|
||||
absolute left-1/2 transform -translate-x-1/2 w-16 h-16 rounded-full
|
||||
flex items-center justify-center z-10 border-4 transition-all duration-500
|
||||
${isActive
|
||||
? 'bg-primary border-white text-white shadow-xl scale-110'
|
||||
: 'bg-white border-gray-300 text-gray-600'
|
||||
}
|
||||
`}
|
||||
animate={{ scale: isActive ? 1.1 : 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="w-6 h-6">
|
||||
{step.icon}
|
||||
</div>
|
||||
</motion.div>
|
||||
{/* Pin head - larger and more prominent */}
|
||||
<circle
|
||||
cx="400"
|
||||
cy="2070"
|
||||
r="22"
|
||||
fill="url(#pinHeadGradient)"
|
||||
/>
|
||||
|
||||
{/* Content Container - Zigzag Layout */}
|
||||
<div className="grid grid-cols-12 gap-8 items-center">
|
||||
{/* Content Card */}
|
||||
<motion.div
|
||||
className={`${isLeft ? 'col-span-5' : 'col-span-5 col-start-8'}`}
|
||||
initial={{ opacity: 0, x: isLeft ? -50 : 50 }}
|
||||
animate={{
|
||||
opacity: isActive ? 1 : 0.6,
|
||||
x: 0,
|
||||
scale: isActive ? 1 : 0.95
|
||||
}}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className={`p-8 border-2 transition-all duration-500 rounded-3xl ${
|
||||
isActive
|
||||
? 'border-primary shadow-xl bg-gradient-to-br from-primary/5 to-secondary/5'
|
||||
: 'border-gray-200 bg-white'
|
||||
}`}>
|
||||
<CardContent className="p-0 text-center">
|
||||
{/* Step Number */}
|
||||
<motion.div
|
||||
className={`text-6xl font-poppins font-light mb-4 transition-colors duration-300 ${
|
||||
isActive ? 'text-primary' : 'text-gray-300'
|
||||
}`}
|
||||
animate={{ scale: isActive ? 1.1 : 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{stepNumber}
|
||||
</motion.div>
|
||||
{/* Pin head border */}
|
||||
<circle
|
||||
cx="400"
|
||||
cy="2070"
|
||||
r="22"
|
||||
fill="none"
|
||||
stroke="white"
|
||||
strokeWidth="3"
|
||||
/>
|
||||
|
||||
{/* Step Title */}
|
||||
<h3 className="font-poppins font-normal text-2xl text-gray-900 mb-4">
|
||||
{step.title}
|
||||
</h3>
|
||||
{/* Shine effect on pin head */}
|
||||
<circle
|
||||
cx="393"
|
||||
cy="2063"
|
||||
r="7"
|
||||
fill="white"
|
||||
opacity="0.6"
|
||||
/>
|
||||
|
||||
{/* Step Description */}
|
||||
<p className="text-gray-600 leading-relaxed mb-6 text-center font-light">
|
||||
{step.description}
|
||||
</p>
|
||||
{/* Inner glow */}
|
||||
<circle
|
||||
cx="400"
|
||||
cy="2070"
|
||||
r="18"
|
||||
fill="none"
|
||||
stroke="#FF8A8C"
|
||||
strokeWidth="2"
|
||||
opacity="0.4"
|
||||
/>
|
||||
|
||||
{/* Features List */}
|
||||
{step.features && (
|
||||
<div className="grid grid-cols-2 gap-3 mb-6">
|
||||
{step.features.map((feature, featureIndex) => (
|
||||
<motion.div
|
||||
key={featureIndex}
|
||||
className="flex items-center gap-2 text-left"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: isActive ? 1 : 0.7, x: 0 }}
|
||||
transition={{ duration: 0.3, delay: featureIndex * 0.1 }}
|
||||
>
|
||||
<div className="w-4 h-4 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<Check className="w-2.5 h-2.5 text-green-600" />
|
||||
</div>
|
||||
<span className="text-gray-700 text-sm font-light">{feature}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* Checkmark on pin */}
|
||||
<path
|
||||
d="M 393 2070 L 397 2075 L 407 2063"
|
||||
stroke="white"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
{/* Action Button */}
|
||||
{isActive && (
|
||||
<motion.div
|
||||
className="mt-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.3 }}
|
||||
>
|
||||
<Button
|
||||
className="bg-primary hover:bg-primary/90 text-white px-6 py-2 rounded-xl font-normal"
|
||||
onClick={onPassesClick}
|
||||
>
|
||||
Get Started
|
||||
<ChevronRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Spacer for the opposite side */}
|
||||
<div className={`${isLeft ? 'col-span-6 col-start-7' : 'col-span-6'}`} />
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Define gradients */}
|
||||
<defs>
|
||||
<linearGradient id="pinHeadGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#FF6B6E" />
|
||||
<stop offset="50%" stopColor="#F95F62" />
|
||||
<stop offset="100%" stopColor="#E53E41" />
|
||||
</linearGradient>
|
||||
<linearGradient id="pinGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#999999" />
|
||||
<stop offset="100%" stopColor="#666666" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</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-32">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
className="text-center mb-8"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h1 className="font-poppins text-4xl md:text-5xl lg:text-6xl leading-tight">
|
||||
<span className="font-light">How it</span>{' '}
|
||||
<span className="font-semibold">works</span>
|
||||
</h1>
|
||||
</motion.div>
|
||||
|
||||
{/* Steps Container */}
|
||||
<div className="space-y-32 lg:space-y-48">
|
||||
{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">
|
||||
{/* Make the most of every attraction section */}
|
||||
<AttractionHassleFreeSection />
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MobileAppPromotion } from './MobileAppPromotion';
|
||||
import { MelbourneFAQ } from './MelbourneFAQ';
|
||||
import { Footer } from './Footer';
|
||||
import { Layout } from '../Layout';
|
||||
import { PersonalizedTourHero } from './PersonalizedTourHero';
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
@@ -179,11 +180,10 @@ function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHo
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
>
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-300 ${
|
||||
currentSlide === index
|
||||
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
|
||||
@@ -213,11 +213,10 @@ function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHo
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={`font-poppins font-semibold transition-all duration-300 ${
|
||||
currentSlide === index
|
||||
className={`font-poppins font-semibold transition-all duration-300 ${currentSlide === index
|
||||
? 'text-2xl'
|
||||
: 'text-xl'
|
||||
}`}
|
||||
}`}
|
||||
style={{
|
||||
color: currentSlide === index ? '#F95F62' : '#000000',
|
||||
opacity: currentSlide === index ? 1 : 0.6
|
||||
@@ -228,9 +227,8 @@ function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHo
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div className={`transition-all duration-300 ${
|
||||
currentSlide === index ? 'opacity-100' : 'opacity-70'
|
||||
}`}>
|
||||
<div className={`transition-all duration-300 ${currentSlide === index ? 'opacity-100' : 'opacity-70'
|
||||
}`}>
|
||||
<p className="font-poppins text-sm text-black/90 whitespace-nowrap max-w-[120px]">
|
||||
{slide.highlight}
|
||||
</p>
|
||||
@@ -301,85 +299,85 @@ export function MelbournePage({
|
||||
onSignOutClick={onSignOutClick}
|
||||
user={user}
|
||||
>
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="min-h-screen bg-background">
|
||||
|
||||
{/* Hero Banner Carousel */}
|
||||
<HeroBannerCarousel
|
||||
{/* Hero Banner Carousel */}
|
||||
{/* <HeroBannerCarousel
|
||||
onCheckoutClick={onCheckoutClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onEsimsClick={onEsimsClick}
|
||||
onHotelDiscountsClick={onHotelDiscountsClick}
|
||||
/>
|
||||
/> */}
|
||||
<PersonalizedTourHero />
|
||||
{/* Main Content */}
|
||||
<main>
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
{/* Features Grid */}
|
||||
<motion.section
|
||||
className="grid md:grid-cols-3 gap-8 py-16"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
>
|
||||
{[
|
||||
{
|
||||
title: "50+ Attractions",
|
||||
description: "Access to Melbourne's top museums, galleries, and experiences",
|
||||
icon: "🏛️"
|
||||
},
|
||||
{
|
||||
title: "Instant QR Access",
|
||||
description: "Skip the lines with digital tickets on your phone",
|
||||
icon: "📱"
|
||||
},
|
||||
{
|
||||
title: "Save up to 50%",
|
||||
description: "Exclusive discounts and special offers for cardholders",
|
||||
icon: "💰"
|
||||
}
|
||||
].map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
className="text-center p-8 bg-white rounded-2xl shadow-lg border border-gray-100"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 + index * 0.1 }}
|
||||
whileHover={{ scale: 1.02, y: -5 }}
|
||||
>
|
||||
<div className="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 className="font-poppins text-xl font-semibold text-gray-900 mb-3">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{feature.description}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Main Content */}
|
||||
<main>
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
{/* Features Grid */}
|
||||
<motion.section
|
||||
className="grid md:grid-cols-3 gap-8 py-16"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
>
|
||||
{[
|
||||
{
|
||||
title: "50+ Attractions",
|
||||
description: "Access to Melbourne's top museums, galleries, and experiences",
|
||||
icon: "🏛️"
|
||||
},
|
||||
{
|
||||
title: "Instant QR Access",
|
||||
description: "Skip the lines with digital tickets on your phone",
|
||||
icon: "📱"
|
||||
},
|
||||
{
|
||||
title: "Save up to 50%",
|
||||
description: "Exclusive discounts and special offers for cardholders",
|
||||
icon: "💰"
|
||||
}
|
||||
].map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
className="text-center p-8 bg-white rounded-2xl shadow-lg border border-gray-100"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 + index * 0.1 }}
|
||||
whileHover={{ scale: 1.02, y: -5 }}
|
||||
>
|
||||
<div className="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 className="font-poppins text-xl font-semibold text-gray-900 mb-3">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{feature.description}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.section>
|
||||
</div>
|
||||
</main>
|
||||
{/* Melbourne Attractions Section */}
|
||||
<MelbourneAttractions />
|
||||
|
||||
{/* Melbourne Attractions Section */}
|
||||
<MelbourneAttractions />
|
||||
{/* Melbourne Card Comparison Section */}
|
||||
<MelbourneCardComparison onCheckoutClick={onCheckoutClick} />
|
||||
|
||||
{/* Melbourne Card Comparison Section */}
|
||||
<MelbourneCardComparison onCheckoutClick={onCheckoutClick} />
|
||||
{/* Melbourne Tour Overview Section */}
|
||||
<MelbourneTourOverview />
|
||||
|
||||
{/* Melbourne Tour Overview Section */}
|
||||
<MelbourneTourOverview />
|
||||
{/* Melbourne Blogs Section */}
|
||||
<MelbourneBlogs />
|
||||
|
||||
{/* Melbourne Blogs Section */}
|
||||
<MelbourneBlogs />
|
||||
{/* Enhanced Testimonials Section */}
|
||||
<EnhancedTestimonials />
|
||||
|
||||
{/* Enhanced Testimonials Section */}
|
||||
<EnhancedTestimonials />
|
||||
{/* Mobile App Promotion Section */}
|
||||
<MobileAppPromotion />
|
||||
|
||||
{/* Mobile App Promotion Section */}
|
||||
<MobileAppPromotion />
|
||||
|
||||
{/* Melbourne FAQ Section */}
|
||||
<MelbourneFAQ />
|
||||
</div>
|
||||
{/* Melbourne FAQ Section */}
|
||||
<MelbourneFAQ />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import Frame1597884853 from '../imports/Frame1597884853';
|
||||
import { Button } from './ui/button';
|
||||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
import { CTAButton } from './CTAButton';
|
||||
import logoImage from '../assets/cit-logo.png';
|
||||
import logoImage from '../assets/cityLogo.png';
|
||||
|
||||
interface NavbarProps {
|
||||
activeCity: string;
|
||||
|
||||
268
src/components/PersonalizedTourHero.tsx
Normal file
268
src/components/PersonalizedTourHero.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
import { motion } from 'motion/react';
|
||||
import { Check, Clock } from 'lucide-react';
|
||||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
import exampleImage from 'figma:asset/9404cd260e0be9427a5438a687bca92b4e4740ac.png';
|
||||
|
||||
interface PersonalizedTourHeroProps {
|
||||
onCreateItineraryClick?: () => void;
|
||||
}
|
||||
|
||||
export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTourHeroProps) {
|
||||
// Mock activity data
|
||||
const activities = [
|
||||
{
|
||||
id: 1,
|
||||
image: 'https://images.unsplash.com/photo-1624138784614-87fd1b6528f8?w=400',
|
||||
title: '1h1 Marriott Mussions With Balcony he',
|
||||
time: '9 Days at 15:00',
|
||||
status: 'added',
|
||||
statusText: 'Added'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
image: 'https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?w=400',
|
||||
title: 'Universal Studios Singapore - Universal Express',
|
||||
time: '8 Days',
|
||||
status: 'adding',
|
||||
statusText: 'Adding...'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: 'Upgrade my Buy1 At1dfls visit to the SKY',
|
||||
status: 'pending'
|
||||
}
|
||||
];
|
||||
|
||||
// Left column thumbnail images
|
||||
const leftThumbnails = [
|
||||
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=300',
|
||||
'https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=300',
|
||||
'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=300',
|
||||
'https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?w=300',
|
||||
'https://images.unsplash.com/photo-1530789253388-582c481c54b0?w=300',
|
||||
'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=300',
|
||||
'https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=300',
|
||||
'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=300'
|
||||
];
|
||||
|
||||
// Right column thumbnail images
|
||||
const rightThumbnails = [
|
||||
'https://images.unsplash.com/photo-1504893524553-b855bce32c67?w=300',
|
||||
'https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=300',
|
||||
'https://images.unsplash.com/photo-1500835556837-99ac94a94552?w=300',
|
||||
'https://images.unsplash.com/photo-1502602898657-3e91760cbb34?w=300',
|
||||
'https://images.unsplash.com/photo-1493246507139-91e8fad9978e?w=300',
|
||||
'https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=300',
|
||||
'https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=300',
|
||||
'https://images.unsplash.com/photo-1500835556837-99ac94a94552?w=300'
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full min-h-screen overflow-hidden flex items-center"
|
||||
style={{
|
||||
backgroundImage: 'url(https://images.unsplash.com/photo-1708554139375-b8a8c00a5f57?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBza3lsaW5lJTIwY2l0eXNjYXBlfGVufDF8fHx8MTc2MTMwMjU1M3ww&ixlib=rb-4.1.0&q=80&w=1080)',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}}
|
||||
>
|
||||
{/* Background Overlay */}
|
||||
<div className="absolute inset-0 bg-black/48 backdrop-blur-[1px]"></div>
|
||||
{/* Left Thumbnail Column */}
|
||||
<div className="absolute left-0 top-0 bottom-0 w-24 md:w-32 lg:w-40 overflow-hidden">
|
||||
<motion.div
|
||||
className="flex flex-col gap-4"
|
||||
animate={{ y: [0, -50, 0] }}
|
||||
transition={{
|
||||
duration: 20,
|
||||
repeat: Infinity,
|
||||
ease: "linear"
|
||||
}}
|
||||
>
|
||||
{[...leftThumbnails, ...leftThumbnails].map((img, index) => (
|
||||
<div
|
||||
key={`left-${index}`}
|
||||
className="relative w-16 h-16 md:w-20 md:h-20 lg:w-24 lg:h-24 rounded-lg overflow-hidden shadow-md flex-shrink-0 mx-auto"
|
||||
>
|
||||
<ImageWithFallback
|
||||
src={img}
|
||||
alt={`Destination ${index + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Right Thumbnail Column */}
|
||||
<div className="absolute right-0 top-0 bottom-0 w-24 md:w-32 lg:w-40 overflow-hidden">
|
||||
<motion.div
|
||||
className="flex flex-col gap-4"
|
||||
animate={{ y: [-50, 0, -50] }}
|
||||
transition={{
|
||||
duration: 20,
|
||||
repeat: Infinity,
|
||||
ease: "linear"
|
||||
}}
|
||||
>
|
||||
{[...rightThumbnails, ...rightThumbnails].map((img, index) => (
|
||||
<div
|
||||
key={`right-${index}`}
|
||||
className="relative w-16 h-16 md:w-20 md:h-20 lg:w-24 lg:h-24 rounded-lg overflow-hidden shadow-md flex-shrink-0 mx-auto"
|
||||
>
|
||||
<ImageWithFallback
|
||||
src={img}
|
||||
alt={`Destination ${index + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 w-full px-4 py-16 md:py-20 lg:py-24">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
{/* Heading */}
|
||||
<motion.div
|
||||
className="text-center mb-8 md:mb-12"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h1 className="font-poppins text-3xl sm:text-4xl md:text-5xl lg:text-6xl leading-tight mb-3">
|
||||
<span className="font-normal text-white">Your Tour,</span>
|
||||
<br />
|
||||
<span className="font-normal text-white">Perfectly </span>
|
||||
<span className="font-bold italic" style={{ color: '#F95F62' }}>
|
||||
Personalised!
|
||||
</span>
|
||||
</h1>
|
||||
<p className="font-poppins text-base md:text-lg text-white font-normal mt-4">
|
||||
Explore Experience, AI-powered mult-day tours.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Activity Cards */}
|
||||
<motion.div
|
||||
className="space-y-4 max-w-2xl mx-auto"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
{/* Activity 1 - Added */}
|
||||
<motion.div
|
||||
className="bg-white rounded-2xl shadow-lg border border-gray-200 p-4 flex items-center gap-4"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.3 }}
|
||||
>
|
||||
<div className="relative w-20 h-20 md:w-24 md:h-24 rounded-xl overflow-hidden flex-shrink-0">
|
||||
<ImageWithFallback
|
||||
src={activities[0].image}
|
||||
alt={activities[0].title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-poppins text-xs md:text-sm text-gray-500 mb-1 font-normal">
|
||||
Activity
|
||||
</p>
|
||||
<h3 className="font-poppins text-sm md:text-base font-medium text-gray-900 mb-1 truncate">
|
||||
1h1 Marriott Mussions With Balcony he
|
||||
</h3>
|
||||
<p className="font-poppins text-xs text-gray-500 font-normal">
|
||||
9 Days at 15:00
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full flex-shrink-0" style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}>
|
||||
<Check className="w-3.5 h-3.5" style={{ color: '#F95F62' }} />
|
||||
<span className="font-poppins text-xs font-medium" style={{ color: '#F95F62' }}>
|
||||
Added
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Activity 2 - Adding */}
|
||||
<motion.div
|
||||
className="bg-white rounded-2xl shadow-lg border border-gray-200 p-4 flex items-center gap-4"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
>
|
||||
<div className="relative w-20 h-20 md:w-24 md:h-24 rounded-xl overflow-hidden flex-shrink-0">
|
||||
<ImageWithFallback
|
||||
src={activities[1].image}
|
||||
alt={activities[1].title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-poppins text-xs md:text-sm text-gray-500 mb-1 font-normal">
|
||||
Activity
|
||||
</p>
|
||||
<h3 className="font-poppins text-sm md:text-base font-medium text-gray-900 mb-1 line-clamp-2">
|
||||
Universal Studios Singapore - Universal Express
|
||||
</h3>
|
||||
<p className="font-poppins text-xs text-gray-500 font-normal">
|
||||
8 Days
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full flex-shrink-0" style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}>
|
||||
<span className="font-poppins text-xs font-medium" style={{ color: '#F95F62' }}>
|
||||
Adding...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Activity 3 - Text Only */}
|
||||
<motion.div
|
||||
className="bg-white rounded-2xl shadow-lg border-2 p-6"
|
||||
style={{ borderColor: '#F95F62' }}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.5 }}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0" style={{ backgroundColor: '#F95F62' }}>
|
||||
<Clock className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<p className="font-poppins text-sm md:text-base font-medium text-gray-800">
|
||||
Upgrade my Buy1 At1dfls visit to the SKY
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<motion.div
|
||||
className="mt-8 md:mt-12 text-center"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
>
|
||||
<button
|
||||
onClick={onCreateItineraryClick}
|
||||
className="group relative px-8 py-4 md:px-10 md:py-5 rounded-full flex items-center gap-3 mx-auto overflow-hidden transition-all duration-300 hover:scale-105 shadow-xl hover:shadow-2xl font-poppins font-semibold text-base md:text-lg text-white"
|
||||
style={{ backgroundColor: '#F95F62' }}
|
||||
>
|
||||
{/* Animated shine effect */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700" />
|
||||
|
||||
<span className="relative z-10">Create Your Itinerary</span>
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user