nav changes

This commit is contained in:
priyanshuvish
2025-11-11 12:04:10 +05:30
parent fec000e0b0
commit 94c5f5641c
16 changed files with 2863 additions and 1329 deletions

View File

@@ -27,6 +27,9 @@ function App() {
// ✅ Authentication state management
const [user, setUser] = useState<User | null>(null);
const [showLoginModal, setShowLoginModal] = useState(false);
// ✅ City state management
const [activeCity, setActiveCity] = useState('');
const location = useLocation();
const navigate = useNavigate();
@@ -52,6 +55,12 @@ function App() {
setShowLoginModal(true);
};
// ✅ Handle city change
const handleCityChange = (city: string) => {
console.log('City changed to:', city);
setActiveCity(city);
};
// ✅ Handle checkout (you can expand this later)
const handleCheckoutClick = () => {
console.log('Proceeding to checkout for user:', user);
@@ -119,6 +128,8 @@ function App() {
>
<AppRouter
user={user}
activeCity={activeCity}
onCityChange={handleCityChange}
showLoginModal={showLoginModal}
onSignInClick={handleSignInClick}
onSignOutClick={handleSignOut}

View File

@@ -31,6 +31,8 @@ import { pageTransition } from './utils/animations';
import { LandingPage } from './pages/landingPage';
import ComingSoonPage from './pages/ComingSoonPage';
import { SuperSavingsPage } from './components/SuperSavingsPage';
import { WhatsIncluded } from './components/WhatsIncluded';
import { LandingMagicItineraryPage } from './components/LandingMagicItineraryPage';
// User type definition
interface User {
@@ -40,23 +42,27 @@ interface User {
interface AppRouterProps {
user: User | null;
activeCity: string;
onCityChange: (city: string) => void;
showLoginModal: boolean;
onSignInClick: () => void;
onSignOutClick: () => void;
onLoginSuccess: (userData: User) => void;
onCloseLoginModal: () => void;
onCheckoutClick?: () => void;
onCheckoutClick?: () => void;
offersSource: 'products' | 'passes';
}
export function AppRouter({
user,
activeCity,
onCityChange,
showLoginModal,
onSignInClick,
onSignOutClick,
onLoginSuccess,
onCloseLoginModal,
onCheckoutClick,
onCheckoutClick,
offersSource
}: AppRouterProps) {
const location = useLocation();
@@ -90,23 +96,16 @@ export function AppRouter({
} />
{/* Passes Route */}
<Route path="/passes" element={
<Route path="/passes" element={
<motion.div key="passes" {...pageTransition}>
<PassesPage
<PassesPage
{...commonNavHandlers}
onCheckoutClick={onCheckoutClick} // ✅ Explicitly pass checkout handler
onLoginSuccess={onLoginSuccess} // ✅ Pass login success handler
onCheckoutClick={onCheckoutClick}
onLoginSuccess={onLoginSuccess}
/>
</motion.div>
} />
{/* Melbourne Route */}
<Route path="/melbourne" element={
<motion.div key="melbourne" {...pageTransition}>
<MelbournePage {...commonNavHandlers} />
</motion.div>
} />
{/* Attractions Routes */}
<Route path="/attractions" element={
<motion.div key="attractions" {...pageTransition}>
@@ -224,12 +223,19 @@ export function AppRouter({
</motion.div>
} />
<Route path="/esims" element={
<motion.div key="esims" {...pageTransition}>
<EsimsPage {...commonNavHandlers} />
<Route path="/landing-magic-itinerary" element={
<motion.div key="landing-magic-itinerary" {...pageTransition}>
<LandingMagicItineraryPage {...commonNavHandlers} />
</motion.div>
} />
<Route path="/whats-included" element={
<motion.div key="whats-included" {...pageTransition}>
<WhatsIncluded {...commonNavHandlers} />
</motion.div>
} />
<Route path="/hotel-discounts" element={
<motion.div key="hotel-discounts" {...pageTransition}>
<HotelDiscountsPage {...commonNavHandlers} />
@@ -254,18 +260,11 @@ export function AppRouter({
</motion.div>
} />
<Route path="/faq" element={
<motion.div key="faq" {...pageTransition}>
<FAQPage {...commonNavHandlers} />
</motion.div>
} />
<Route path="/super-savings" element={
<motion.div key="super-savings" {...pageTransition}>
<SuperSavingsPage {...commonNavHandlers} />
</motion.div>
} />
</Routes>
</AnimatePresence>

View File

@@ -10,7 +10,7 @@ interface User {
interface LayoutProps {
children: ReactNode;
activeCity?: string;
onSignInClick?: () => void; // ✅ optional
onSignInClick?: () => void;
onSignOutClick?: () => void;
user?: User | null;
}
@@ -20,7 +20,7 @@ export function Layout({
activeCity = 'Melbourne',
onSignInClick,
onSignOutClick,
user
user
}: LayoutProps) {
return (
<div className="min-h-screen bg-background flex flex-col">
@@ -28,7 +28,7 @@ export function Layout({
<Navbar
activeCity={activeCity}
onCityChange={() => {}}
onSignInClick={() => onSignInClick?.()} // ✅ safe optional call
onSignInClick={() => onSignInClick?.()}
onSignOutClick={onSignOutClick}
isUserSignedIn={!!user}
user={user}

Binary file not shown.

View File

@@ -16,7 +16,7 @@ interface City {
interface CitySelectionDialogProps {
isOpen: boolean;
onClose: () => void;
onCitySelect?: (city: string) => void; // ✅ New prop for city selection callback
onCitySelect?: (cityId: string) => void; // ✅ Updated to pass cityId
}
const cities: City[] = [
@@ -45,7 +45,7 @@ export function CitySelectionDialog({
const handleCityClick = (city: City) => {
console.log('Selected city:', city.name);
// ✅ Call the onCitySelect callback if provided
// ✅ Call the onCitySelect callback if provided (passing cityId)
if (onCitySelect) {
onCitySelect(city.id);
} else {
@@ -59,7 +59,6 @@ export function CitySelectionDialog({
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-md w-full p-0 gap-0 font-poppins">
{/* ... rest of the component remains the same ... */}
<DialogTitle className="sr-only">Select a City</DialogTitle>
<DialogDescription className="sr-only">
Choose from our available cities to explore attractions and experiences

View File

@@ -192,7 +192,7 @@ export function FAQPage({
<div className="min-h-screen bg-background">
{/* Navigation */}
<Navbar
activeCity="Melbourne"
activeCity="Landingpage"
onCityChange={() => {}}
onHomeClick={onHomeClick}
onSignInClick={onSignInClick}

File diff suppressed because it is too large Load Diff

View File

@@ -1,165 +1,60 @@
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { Wand2, MapPin, Clock, DollarSign, Sparkles, Star, Navigation, Plane, Map } from 'lucide-react';
import { Button } from './ui/button';
import { MapPin, Pause, Plane, Play, Sparkles, Wand2 } from 'lucide-react';
import { motion } from 'motion/react';
import { useState } from 'react';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { Button } from './ui/button';
// Import your video from assets
import cityTourVideo from '../assets/itinenary-animation-vid.mp4';
interface ItineraryCard {
id: number;
city: string;
country: string;
days: number;
image: string;
activities: {
time: string;
name: string;
price: string;
}[];
totalCost: string;
highlights: string[];
}
const itineraryCards: ItineraryCard[] = [
{
id: 1,
city: 'Paris',
country: 'France',
days: 3,
image: 'https://images.unsplash.com/photo-1431274172761-fca41d930114?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxQYXJpcyUyMEVpZmZlbCUyMFRvd2VyfGVufDF8fHx8MTc1OTIzNTg5MXww&ixlib=rb-4.1.0&q=80&w=1080',
activities: [
{ time: '9:00 AM', name: 'Eiffel Tower', price: '$28' },
{ time: '1:00 PM', name: 'Louvre Museum', price: '$18' },
{ time: '6:00 PM', name: 'Seine River Cruise', price: '$22' },
],
totalCost: '$68',
highlights: ['Art & Culture', 'Historic Sites', 'Fine Dining'],
},
{
id: 2,
city: 'Tokyo',
country: 'Japan',
days: 4,
image: 'https://images.unsplash.com/photo-1717986439981-0c6a51130cfa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxUb2t5byUyMGNpdHklMjBza3lsaW5lfGVufDF8fHx8MTc1OTI5Nzc3NHww&ixlib=rb-4.1.0&q=80&w=1080',
activities: [
{ time: '8:00 AM', name: 'Senso-ji Temple', price: 'Free' },
{ time: '12:00 PM', name: 'Tokyo Skytree', price: '$24' },
{ time: '5:00 PM', name: 'Shibuya Crossing', price: 'Free' },
],
totalCost: '$24',
highlights: ['Modern Culture', 'Temples', 'Street Food'],
},
{
id: 3,
city: 'New York',
country: 'USA',
days: 3,
image: 'https://images.unsplash.com/photo-1698066574628-3d1a68c2f204?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxOZXclMjBZb3JrJTIwQ2l0eSUyME1hbmhhdHRhbnxlbnwxfHx8fDE3NTkyOTc3NzR8MA&ixlib=rb-4.1.0&q=80&w=1080',
activities: [
{ time: '9:00 AM', name: 'Central Park', price: 'Free' },
{ time: '2:00 PM', name: 'Empire State Building', price: '$44' },
{ time: '7:00 PM', name: 'Times Square', price: 'Free' },
],
totalCost: '$44',
highlights: ['Urban Adventure', 'Skyscrapers', 'Broadway'],
},
{
id: 4,
city: 'London',
country: 'UK',
days: 4,
image: 'https://images.unsplash.com/photo-1745016176874-cd3ed3f5bfc6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxMb25kb24lMjBCaWclMjBCZW58ZW58MXx8fHwxNzU5Mjk3Nzc1fDA&ixlib=rb-4.1.0&q=80&w=1080',
activities: [
{ time: '10:00 AM', name: 'Tower of London', price: '$35' },
{ time: '2:00 PM', name: 'British Museum', price: 'Free' },
{ time: '6:00 PM', name: 'London Eye', price: '$32' },
],
totalCost: '$67',
highlights: ['Royal Heritage', 'Museums', 'Theatre'],
},
{
id: 5,
city: 'Barcelona',
country: 'Spain',
days: 3,
image: 'https://images.unsplash.com/photo-1653677903266-1d814985b3cc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxCYXJjZWxvbmElMjBhcmNoaXRlY3R1cmV8ZW58MXx8fHwxNzU5Mjk3Nzc2fDA&ixlib=rb-4.1.0&q=80&w=1080',
activities: [
{ time: '9:30 AM', name: 'Sagrada Familia', price: '$26' },
{ time: '1:00 PM', name: 'Park Güell', price: '$14' },
{ time: '5:00 PM', name: 'La Rambla', price: 'Free' },
],
totalCost: '$40',
highlights: ['Gaudí Architecture', 'Beach', 'Tapas'],
},
{
id: 6,
city: 'Dubai',
country: 'UAE',
days: 3,
image: 'https://images.unsplash.com/photo-1537132766573-55e8b870c5d6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxEdWJhaSUyMGNpdHlzY2FwZXxlbnwxfHx8fDE3NTkyOTc3NzV8MA&ixlib=rb-4.1.0&q=80&w=1080',
activities: [
{ time: '10:00 AM', name: 'Burj Khalifa', price: '$45' },
{ time: '3:00 PM', name: 'Dubai Mall', price: 'Free' },
{ time: '7:00 PM', name: 'Desert Safari', price: '$75' },
],
totalCost: '$120',
highlights: ['Luxury', 'Desert Adventure', 'Shopping'],
},
];
// Single itinerary card with generic content
const itineraryCard: ItineraryCard = {
id: 1,
image: 'https://images.unsplash.com/photo-1488646953014-85cb44e25828?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
};
export function LandingMagicItinerary() {
const [currentCardIndex, setCurrentCardIndex] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
const [isPlaying, setIsPlaying] = useState(true);
const [videoLoaded, setVideoLoaded] = useState(false);
useEffect(() => {
const interval = setInterval(() => {
setIsAnimating(true);
setTimeout(() => {
setCurrentCardIndex((prev) => (prev + 1) % itineraryCards.length);
setIsAnimating(false);
}, 400); // Half of the animation duration
}, 4000);
const handleVideoLoad = () => {
setVideoLoaded(true);
};
return () => clearInterval(interval);
}, []);
const currentCard = itineraryCards[currentCardIndex];
const nextCard = itineraryCards[(currentCardIndex + 1) % itineraryCards.length];
const thirdCard = itineraryCards[(currentCardIndex + 2) % itineraryCards.length];
const togglePlayPause = () => {
setIsPlaying(!isPlaying);
};
return (
<section className="relative py-20 lg:py-32 overflow-hidden -mt-20 pt-32 z-[100]">
{/* Dynamic City Background */}
<AnimatePresence mode="wait">
<motion.div
key={currentCard.id}
className="absolute inset-0 overflow-hidden pointer-events-none z-[5]"
initial={{ opacity: 0, scale: 1.05 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.05 }}
transition={{ duration: 0.8, ease: "easeInOut" }}
>
{/* City Background Image */}
<div className="absolute inset-0">
<ImageWithFallback
src={currentCard.image}
alt={`${currentCard.city} background`}
className="w-full h-full object-cover"
/>
</div>
{/* Lightened Multi-layer Gradient Overlay - Reduced opacity to show city */}
<div className="absolute inset-0 bg-gradient-to-br from-rose-50/50 via-orange-50/40 to-amber-50/50" />
<div className="absolute inset-0 backdrop-blur-lg" />
<div className="absolute inset-0 bg-gradient-to-t from-white/30 via-transparent to-white/30" />
</motion.div>
</AnimatePresence>
{/* Dynamic Background */}
<div className="absolute inset-0 overflow-hidden pointer-events-none z-[5]">
{/* Background Image as fallback */}
<div className="absolute inset-0">
<ImageWithFallback
src={itineraryCard.image}
alt="City background"
className="w-full h-full object-cover"
/>
</div>
{/* Lightened Multi-layer Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-br from-rose-50/50 via-orange-50/40 to-amber-50/50" />
<div className="absolute inset-0 backdrop-blur-lg" />
<div className="absolute inset-0 bg-gradient-to-t from-white/30 via-transparent to-white/30" />
</div>
{/* White Readability Overlay - 42% Opacity */}
<div className="absolute inset-0 bg-white/42 pointer-events-none z-[10]" />
{/* Simplified Decorative Elements - Optimized */}
{/* Simplified Decorative Elements */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{/* Single Coral Gradient Blob - Optimized */}
{/* Single Coral Gradient Blob */}
<motion.div
className="absolute top-20 -left-20 w-[500px] h-[500px] bg-gradient-to-br from-warm-coral/20 via-orange-400/10 to-transparent rounded-full blur-3xl will-change-transform"
animate={{
@@ -169,7 +64,7 @@ export function LandingMagicItinerary() {
transition={{ duration: 15, repeat: Infinity, ease: "easeInOut" }}
/>
{/* Simplified Floating Icons - Reduced from 8 to 4 */}
{/* Floating Icons */}
{[...Array(4)].map((_, i) => (
<motion.div
key={i}
@@ -200,7 +95,7 @@ export function LandingMagicItinerary() {
<div className="container mx-auto px-4 relative z-[101] flex flex-col items-center">
{/* Header */}
<div className="text-center mb-20 max-w-5xl w-full">
<div className="text-center mb-16 max-w-5xl w-full">
<motion.div
className="inline-flex items-center gap-3 bg-gradient-to-r from-warm-coral/10 to-orange-100/50 backdrop-blur-sm px-6 py-3 rounded-full border-2 border-warm-coral/30 shadow-xl mb-8"
initial={{ opacity: 0, scale: 0.8, y: 20 }}
@@ -229,7 +124,7 @@ export function LandingMagicItinerary() {
</motion.div>
<motion.h2
className="text-4xl md:text-5xl lg:text-6xl mb-8 leading-tight"
className="text-4xl md:text-5xl lg:text-6xl mb-6 leading-tight"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.1, ease: [0.34, 1.56, 0.64, 1] }}
@@ -267,274 +162,58 @@ export function LandingMagicItinerary() {
</motion.p>
</div>
{/* Card Stack Display */}
{/* Single Video Player Display */}
<div className="max-w-6xl w-full">
<div className="relative flex justify-center items-center min-h-[600px] lg:min-h-[650px] perspective-1000">
{/* Card Stack Container */}
<div className="relative w-full max-w-md lg:max-w-lg" style={{ transformStyle: 'preserve-3d' }}>
{/* Visible Card Stack - Third Card */}
<motion.div
className="absolute inset-0 bg-white rounded-3xl shadow-lg overflow-hidden"
style={{
zIndex: 1,
transformStyle: 'preserve-3d'
}}
animate={{
scale: 0.88,
y: 24,
opacity: 0.4,
}}
transition={{ duration: 0.3 }}
>
<div className="relative h-64 lg:h-80 overflow-hidden">
<ImageWithFallback
src={thirdCard.image}
alt={`${thirdCard.city}, ${thirdCard.country}`}
className="w-full h-full object-cover opacity-60"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
<div className="absolute bottom-6 left-6 right-6">
<h3 className="text-2xl lg:text-3xl font-bold text-white opacity-70">
{thirdCard.city}
</h3>
</div>
</div>
</motion.div>
{/* Visible Card Stack - Second Card */}
<motion.div
className="absolute inset-0 bg-white rounded-3xl shadow-xl overflow-hidden"
style={{
zIndex: 2,
transformStyle: 'preserve-3d'
}}
animate={{
scale: 0.94,
y: 12,
opacity: 0.7,
}}
transition={{ duration: 0.3 }}
>
<div className="relative h-64 lg:h-80 overflow-hidden">
<ImageWithFallback
src={nextCard.image}
alt={`${nextCard.city}, ${nextCard.country}`}
className="w-full h-full object-cover opacity-80"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
<div className="absolute bottom-6 left-6 right-6">
<h3 className="text-2xl lg:text-3xl font-bold text-white opacity-80">
{nextCard.city}
</h3>
</div>
</div>
</motion.div>
{/* Animated Front Card */}
<AnimatePresence mode="popLayout">
<motion.div
key={currentCard.id}
className="relative bg-white rounded-3xl shadow-2xl overflow-hidden"
style={{
zIndex: 3,
transformStyle: 'preserve-3d'
}}
initial={{
scale: 0.94,
y: 12,
opacity: 0.7,
}}
animate={{
scale: 1,
opacity: 1,
y: 0,
}}
exit={{
scale: 1.05,
opacity: 0,
x: -100,
rotateZ: -5,
}}
transition={{
duration: 0.6,
ease: [0.34, 1.56, 0.64, 1], // Bounce easing
}}
<div className="relative flex justify-center items-center">
{/* Main Video Container - Full Height */}
<div className="relative w-full max-w-6xl rounded-3xl overflow-hidden shadow-2xl bg-black">
<div className="relative w-full h-full">
{/* Single Video Player - Full Height */}
<video
autoPlay
muted
loop
playsInline
onLoadedData={handleVideoLoad}
className="w-full h-full object-contain max-h-[70vh]"
>
{/* Card Image */}
<div className="relative h-64 lg:h-80 overflow-hidden">
<ImageWithFallback
src={currentCard.image}
alt={`${currentCard.city}, ${currentCard.country}`}
className="w-full h-full object-cover"
<source src={cityTourVideo} type="video/mp4" />
Your browser does not support the video tag.
</video>
{/* Loading State */}
{!videoLoaded && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-900">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
className="w-12 h-12 border-4 border-warm-coral/30 border-t-warm-coral rounded-full"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
{/* City Info Overlay */}
<motion.div
className="absolute bottom-6 left-6 right-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5, ease: [0.34, 1.56, 0.64, 1] }}
>
<h3 className="text-3xl lg:text-4xl font-bold text-white mb-2">
{currentCard.city}
</h3>
<p className="text-white/90 text-lg flex items-center gap-2">
<MapPin className="w-5 h-5" />
{currentCard.country}
</p>
</motion.div>
{/* Duration Badge */}
<motion.div
className="absolute top-6 right-6 bg-gradient-to-r from-warm-coral to-orange-500 px-4 py-2 rounded-full shadow-xl"
initial={{ scale: 0, opacity: 0, rotate: -180 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
transition={{ delay: 0.3, duration: 0.6, type: "spring", stiffness: 200, damping: 12 }}
>
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-white" />
<span className="font-bold text-white">{currentCard.days} Days</span>
</div>
</motion.div>
{/* Top Left Journey Icon */}
<motion.div
className="absolute top-6 left-6 bg-white/95 backdrop-blur-sm p-3 rounded-full shadow-lg"
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2, duration: 0.5, type: "spring" }}
>
<Plane className="w-5 h-5 text-warm-coral" />
</motion.div>
</div>
)}
{/* Card Content */}
<motion.div
className="p-4 lg:p-5"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3, duration: 0.5 }}
{/* Video Overlay Content - Minimal */}
<div className="absolute inset-0">
{/* Play/Pause Control */}
<motion.button
onClick={togglePlayPause}
className="absolute top-6 right-6 bg-white/20 backdrop-blur-md hover:bg-white/30 p-4 rounded-full shadow-2xl border border-white/30 transition-all hover:scale-110"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.6, duration: 0.5 }}
>
{/* Highlights - Compact */}
<motion.div
className="flex flex-wrap gap-1.5 mb-4"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.4 }}
>
{currentCard.highlights.map((highlight, idx) => (
<span
key={idx}
className="px-3 py-1 bg-gradient-to-r from-warm-coral/15 to-orange-100 text-warm-coral border border-warm-coral/20 rounded-full text-xs font-semibold shadow-sm transition-transform hover:scale-105"
>
{highlight}
</span>
))}
</motion.div>
{isPlaying ? (
<Pause className="w-6 h-6 text-white" />
) : (
<Play className="w-6 h-6 text-white" />
)}
</motion.button>
</div>
</div>
{/* Day 1 Header */}
<motion.div
className="flex items-center gap-2 mb-2.5"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.35, duration: 0.4 }}
>
<div className="flex items-center gap-1.5 px-3 py-1 bg-gradient-to-r from-warm-coral to-orange-500 rounded-full shadow-md">
<Clock className="w-3 h-3 text-white" />
<span className="text-xs font-bold text-white">Day 1</span>
</div>
<div className="flex-1 h-px bg-gradient-to-r from-warm-coral/30 to-transparent" />
</motion.div>
{/* Day 1 Activities - Compact */}
<motion.div
className="space-y-2 mb-3"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4, duration: 0.4 }}
>
{currentCard.activities.slice(0, 2).map((activity, idx) => (
<div
key={idx}
className="relative flex items-center gap-2.5 p-2.5 bg-gradient-to-r from-white to-orange-50/30 rounded-xl border border-warm-coral/10 hover:border-warm-coral/30 hover:shadow-md transition-all group hover:translate-x-0.5"
>
{/* Route Line Connector */}
{idx < 1 && (
<div className="absolute left-4 top-full h-2 w-0.5 bg-gradient-to-b from-warm-coral/50 to-transparent" />
)}
<div className="relative flex-shrink-0">
<div className="w-7 h-7 bg-gradient-to-br from-warm-coral to-orange-500 rounded-full flex items-center justify-center shadow-md">
<span className="text-white font-bold text-xs">{idx + 1}</span>
</div>
<div className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-green-400 rounded-full border border-white" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-semibold text-gray-900 group-hover:text-warm-coral transition-colors leading-tight mb-0.5">{activity.name}</p>
<div className="flex items-center gap-1.5 text-xs text-gray-500">
<Clock className="w-2.5 h-2.5" />
<span>{activity.time}</span>
</div>
</div>
</div>
))}
</motion.div>
{/* Day 2 Header */}
<motion.div
className="flex items-center gap-2 mb-2.5"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.45, duration: 0.4 }}
>
<div className="flex items-center gap-1.5 px-3 py-1 bg-gradient-to-r from-orange-500 to-rose-500 rounded-full shadow-md">
<Clock className="w-3 h-3 text-white" />
<span className="text-xs font-bold text-white">Day 2</span>
</div>
<div className="flex-1 h-px bg-gradient-to-r from-orange-500/30 to-transparent" />
</motion.div>
{/* Day 2 Activities - Compact */}
<motion.div
className="space-y-2"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5, duration: 0.4 }}
>
{currentCard.activities.slice(2, 4).map((activity, idx) => (
<div
key={idx + 2}
className="relative flex items-center gap-2.5 p-2.5 bg-gradient-to-r from-white to-rose-50/30 rounded-xl border border-orange-500/10 hover:border-orange-500/30 hover:shadow-md transition-all group hover:translate-x-0.5"
>
{/* Route Line Connector */}
{idx < 1 && (
<div className="absolute left-4 top-full h-2 w-0.5 bg-gradient-to-b from-orange-500/50 to-transparent" />
)}
<div className="relative flex-shrink-0">
<div className="w-7 h-7 bg-gradient-to-br from-orange-500 to-rose-500 rounded-full flex items-center justify-center shadow-md">
<span className="text-white font-bold text-xs">{idx + 1}</span>
</div>
<div className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-green-400 rounded-full border border-white" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-semibold text-gray-900 group-hover:text-orange-500 transition-colors leading-tight mb-0.5">{activity.name}</p>
<div className="flex items-center gap-1.5 text-xs text-gray-500">
<Clock className="w-2.5 h-2.5" />
<span>{activity.time}</span>
</div>
</div>
</div>
))}
</motion.div>
</motion.div>
</motion.div>
</AnimatePresence>
{/* Optimized Floating Sparkles - Reduced from 8 to 3 */}
{/* Floating Sparkles */}
{[...Array(3)].map((_, i) => (
<motion.div
key={i}
@@ -561,49 +240,9 @@ export function LandingMagicItinerary() {
</div>
</div>
{/* Card Indicators with City Names */}
{/* CTA Button */}
<motion.div
className="flex flex-wrap justify-center gap-3 mt-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 1 }}
viewport={{ once: true }}
>
{itineraryCards.map((card, idx) => (
<motion.button
key={card.id}
onClick={() => {
setIsAnimating(true);
setTimeout(() => {
setCurrentCardIndex(idx);
setIsAnimating(false);
}, 400);
}}
className={`group relative transition-all duration-300 px-4 py-2 rounded-full font-medium ${
idx === currentCardIndex
? 'bg-gradient-to-r from-warm-coral to-orange-500 text-white shadow-lg scale-110'
: 'bg-white/80 backdrop-blur-sm text-gray-600 hover:text-warm-coral hover:bg-white border border-gray-200 hover:border-warm-coral/30 hover:scale-105'
}`}
whileHover={{ y: -2 }}
whileTap={{ scale: 0.95 }}
>
{card.city}
{idx === currentCardIndex && (
<motion.div
className="absolute -bottom-1 left-1/2 w-1.5 h-1.5 bg-white rounded-full"
layoutId="activeIndicator"
initial={false}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
style={{ x: '-50%' }}
/>
)}
</motion.button>
))}
</motion.div>
{/* CTA Button - Optimized */}
<motion.div
className="flex flex-col items-center gap-6 mt-16"
className="flex flex-col items-center gap-6 mt-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
@@ -611,10 +250,10 @@ export function LandingMagicItinerary() {
>
<Button
withShine={true}
className="py-7 px-16 rounded-full text-xl font-bold bg-gradient-to-r from-warm-coral via-orange-500 to-rose-500 hover:from-warm-coral/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-warm-coral/50 transition-all hover:scale-105 hover:-translate-y-1"
className="py-6 px-14 rounded-full text-lg font-bold bg-gradient-to-r from-warm-coral via-orange-500 to-rose-500 hover:from-warm-coral/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-warm-coral/50 transition-all hover:scale-105 hover:-translate-y-1"
>
<span className="flex items-center gap-3">
<Wand2 className="w-6 h-6" />
<Wand2 className="w-5 h-5" />
Create My Perfect Itinerary
</span>
</Button>
@@ -629,4 +268,4 @@ export function LandingMagicItinerary() {
</div>
</section>
);
}
}

View File

@@ -0,0 +1,426 @@
import React from 'react';
import { motion } from 'motion/react';
import { ArrowLeft, Sparkles, MapPin, Clock, Users, Calendar, Star, Zap, Heart, Camera } from 'lucide-react';
import { Button } from './ui/button';
import { Card, CardContent } from './ui/card';
import { Badge } from './ui/badge';
import Navbar from './Navbar';
import { Footer } from './Footer';
import { MobileAppSection } from './MobileAppSection';
import { EnhancedTestimonials } from './EnhancedTestimonials';
import { HowItWorks } from './HowItWorks';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { PersonalizedTourHero } from './PersonalizedTourHero';
import { Layout } from '../Layout';
interface User {
email: string;
name: string;
}
interface LandingMagicItineraryPageProps {
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;
onSuperSavingsClick?: () => void;
onContactUsClick?: () => void;
onEsimsClick?: () => void;
onHotelDiscountsClick?: () => void;
onCreateItineraryClick: () => void;
onViewItineraryClick: () => void;
currentPage: string;
user: User | null;
}
export function LandingMagicItineraryPage({
onBackClick,
onHomeClick,
onMelbourneClick,
onPassesClick,
onCheckoutClick,
onSignInClick,
onSignOutClick,
onAttractionsClick,
onBlogsClick,
onHowItWorksClick,
onFAQClick,
onPrivacyPolicyClick,
onAboutUsClick,
onProfileClick,
onCityCardsClick,
onMagicItineraryClick,
onPostCardsClick,
onOffersClick,
onSuperSavingsClick,
onContactUsClick,
onEsimsClick,
onHotelDiscountsClick,
onCreateItineraryClick,
onViewItineraryClick,
currentPage,
user
}: LandingMagicItineraryPageProps) {
return (
<Layout activeCity="Landingpage" onSignInClick={onSignInClick} onSignOutClick={onSignOutClick} user={user}>
<div className="min-h-screen bg-background">
{/* Hero Section */}
<PersonalizedTourHero
onCreateItineraryClick={onCreateItineraryClick}
/>
{/* How It Works Section */}
<HowItWorks />
{/* Features Section */}
<section className="py-16 bg-gradient-to-br from-gray-50 to-gray-100">
<div className="container mx-auto px-4">
{/* Section Header */}
<motion.div
className="text-center mb-16 max-w-3xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl leading-tight mb-4">
<span className="font-light text-gray-800">Smart Features for</span>{' '}
<span className="font-bold" style={{ color: '#F95F62' }}>Smart Travelers</span>
</h2>
<p className="font-poppins text-base md:text-lg font-normal text-gray-600 leading-relaxed">
Experience intelligent trip planning powered by AI technology
</p>
</motion.div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-start">
{/* Left side - Features */}
<motion.div
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="space-y-5"
>
{[
{
icon: <Sparkles className="w-5 h-5" style={{ color: '#F95F62' }} />,
title: 'AI-Powered Recommendations',
description: 'Our advanced AI analyzes your preferences to suggest the perfect experiences'
},
{
icon: <Clock className="w-5 h-5" style={{ color: '#F95F62' }} />,
title: 'Optimized Scheduling',
description: 'Smart timing that considers opening hours, travel time, and crowd patterns'
},
{
icon: <MapPin className="w-5 h-5" style={{ color: '#F95F62' }} />,
title: 'Location-Based Planning',
description: 'Efficiently planned routes that minimize travel time and maximize experiences'
},
{
icon: <Camera className="w-5 h-5" style={{ color: '#F95F62' }} />,
title: 'Instagram-Worthy Spots',
description: 'Discover hidden gems and perfect photo opportunities along your journey'
}
].map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.1 }}
className="group relative bg-white rounded-2xl p-6 shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-gray-200"
>
{/* Subtle accent line */}
<div
className="absolute top-0 left-0 w-1 h-full rounded-l-2xl transition-all duration-300 group-hover:w-1.5"
style={{ backgroundColor: '#F95F62' }}
/>
<div className="flex items-start gap-4 ml-2">
<div
className="flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center shadow-sm transition-all duration-300 group-hover:scale-110 group-hover:shadow-md"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}
>
{feature.icon}
</div>
<div className="flex-1">
<h3 className="font-poppins text-lg md:text-xl font-semibold leading-snug text-gray-900 mb-2">
{feature.title}
</h3>
<p className="font-poppins text-sm md:text-base font-normal leading-relaxed text-gray-600">
{feature.description}
</p>
</div>
</div>
</motion.div>
))}
</motion.div>
{/* Right side - Sample Itinerary */}
<motion.div
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="relative"
>
<div className="bg-white rounded-3xl p-8 shadow-lg border border-gray-100 overflow-hidden">
{/* Header */}
<div className="mb-8">
<div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}>
<Calendar className="w-5 h-5" style={{ color: '#F95F62' }} />
</div>
<h3 className="font-poppins text-xl md:text-2xl font-semibold leading-snug text-gray-900">
Sample Day Itinerary
</h3>
</div>
<p className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
A perfectly planned Melbourne adventure
</p>
</div>
{/* Timeline */}
<div className="relative space-y-4">
{/* Vertical line */}
<div className="absolute left-[15px] top-3 bottom-3 w-0.5 bg-gray-200" />
{[
{ time: '9:00 AM', activity: 'Coffee at Degraves Street', duration: '30 min', emoji: '☕' },
{ time: '10:00 AM', activity: 'Royal Botanic Gardens', duration: '2 hours', emoji: '🌿' },
{ time: '1:00 PM', activity: 'Lunch at Queen Victoria Market', duration: '1 hour', emoji: '🍽️' },
{ time: '3:00 PM', activity: 'Street Art Tour in Hosier Lane', duration: '1.5 hours', emoji: '🎨' },
{ time: '6:00 PM', activity: 'Sunset at Eureka Skydeck', duration: '1 hour', emoji: '🌅' }
].map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, delay: 0.5 + index * 0.1 }}
className="relative flex items-start gap-4 pl-10"
>
{/* Timeline dot */}
<div
className="absolute left-0 w-8 h-8 rounded-full border-4 border-white flex items-center justify-center shadow-sm"
style={{ backgroundColor: index === 0 ? '#F95F62' : '#ffffff', borderColor: '#F95F62' }}
>
{index === 0 && <div className="w-2 h-2 rounded-full bg-white" />}
</div>
<div className="flex-1 bg-gray-50 rounded-xl p-4 hover:bg-gray-100 transition-colors duration-200">
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex items-center gap-2">
<span className="text-lg">{item.emoji}</span>
<span className="font-poppins text-sm font-semibold" style={{ color: '#F95F62' }}>
{item.time}
</span>
</div>
<span className="font-poppins text-xs font-medium text-gray-500 bg-white px-2 py-1 rounded-full">
{item.duration}
</span>
</div>
<p className="font-poppins text-sm md:text-base font-medium text-gray-900 leading-snug">
{item.activity}
</p>
</div>
</motion.div>
))}
</div>
{/* Decorative gradient blur */}
<div
className="absolute -bottom-20 -right-20 w-40 h-40 rounded-full blur-3xl opacity-20 pointer-events-none"
style={{ backgroundColor: '#F95F62' }}
/>
</div>
</motion.div>
</div>
</div>
</section>
{/* Benefits Section */}
<section className="py-16">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-center mb-12"
>
<h2 className="font-merchant text-3xl mb-4">Why Use Magic Itinerary?</h2>
<p className="text-gray-600 font-poppins max-w-2xl mx-auto">
Save time, discover more, and create unforgettable memories with personalized planning
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{
icon: <Clock className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Save Planning Time',
description: 'Skip hours of research. Get a complete itinerary in under 5 minutes.',
stat: '90% faster than manual planning'
},
{
icon: <Star className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Discover Hidden Gems',
description: 'Find unique experiences and local favorites you might have missed.',
stat: '50+ curated hidden spots'
},
{
icon: <Heart className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Personalized Experience',
description: 'Every itinerary is unique, tailored specifically to your preferences.',
stat: '1000+ possible combinations'
}
].map((benefit, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.1 }}
>
<Card className="h-full text-center hover:shadow-lg transition-shadow duration-300 border-gray-100">
<CardContent className="p-8">
<div className="flex justify-center mb-6">
<div
className="w-12 h-12 rounded-full flex items-center justify-center"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}
>
{benefit.icon}
</div>
</div>
<h3 className="font-poppins text-xl md:text-2xl font-semibold leading-snug text-gray-900 mb-3">
{benefit.title}
</h3>
<p className="font-poppins text-base font-normal leading-relaxed text-gray-600 mb-4">
{benefit.description}
</p>
<Badge
className="border-none font-poppins text-sm font-medium"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)', color: '#F95F62' }}
>
{benefit.stat}
</Badge>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</section>
{/* What's Included Section */}
<section className="py-20 bg-white">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-center mb-16 max-w-3xl mx-auto"
>
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl leading-tight mb-4">
<span className="font-light text-gray-800">Everything You Need,</span>{' '}
<span className="font-bold" style={{ color: '#F95F62' }}>All Included</span>
</h2>
<p className="font-poppins text-base md:text-lg font-normal leading-relaxed text-gray-600">
Your Magic Itinerary comes with comprehensive features for an amazing Melbourne experience
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{[
{
emoji: '⏰',
title: 'Detailed Timeline',
description: 'Hour-by-hour schedule with optimal timing'
},
{
emoji: '🚇',
title: 'Transportation Tips',
description: 'Best routes and transport options between locations'
},
{
emoji: '⭐',
title: 'Local Recommendations',
description: 'Insider tips on food, shopping, and experiences'
},
{
emoji: '💰',
title: 'Budget Planning',
description: 'Estimated costs and money-saving suggestions'
},
{
emoji: '☔',
title: 'Weather Backup Plans',
description: 'Alternative indoor activities for rainy days'
},
{
emoji: '📸',
title: 'Photo Opportunities',
description: 'Best spots and times for Instagram-worthy shots'
},
{
emoji: '🏛️',
title: 'Cultural Insights',
description: 'Local history and interesting facts about each location'
},
{
emoji: '🔔',
title: 'Real-time Updates',
description: 'Live information on closures, events, and crowds'
}
].map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.05 }}
className="group"
>
<div className="h-full bg-white rounded-2xl p-6 border border-gray-100 hover:border-gray-200 transition-all duration-300 hover:shadow-lg">
<div className="flex flex-col items-center text-center">
<div className="text-5xl mb-4 transition-all duration-300 group-hover:scale-110">
{item.emoji}
</div>
<h3 className="font-poppins text-lg font-semibold leading-snug text-gray-900 mb-2">
{item.title}
</h3>
<p className="font-poppins text-sm font-normal leading-relaxed text-gray-600">
{item.description}
</p>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* CTA Section */}
{/* Mobile App Section */}
<MobileAppSection />
{/* Customer Reviews */}
<EnhancedTestimonials />
</div>
</Layout>
);
}

View File

@@ -77,14 +77,14 @@ export function MagicItineraryPage({
<div className="min-h-screen bg-background">
{/* Navbar */}
<Layout
activeCity="Melbourne"
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
>
activeCity="Melbourne"
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
>
{/* Sub Navbar */}
{/* <SubNavbar
{/* Sub Navbar */}
{/* <SubNavbar
activeTab="magic-itinerary"
onCityCardsClick={onCityCardsClick}
onMagicItineraryClick={onMagicItineraryClick}
@@ -94,275 +94,275 @@ export function MagicItineraryPage({
onHotelDiscountsClick={onHotelDiscountsClick}
/> */}
{/* Hero Section */}
<section className="relative pt-52 pb-20 overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-secondary/5 to-background"></div>
<div className="container mx-auto px-4 relative z-10">
<motion.div
className="max-w-4xl mx-auto text-center"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<h1 className="font-merchant text-4xl md:text-5xl lg:text-6xl leading-tight mb-6">
<span className="font-light">Plan Your Perfect</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Melbourne Adventure
</span>
</h1>
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 mb-8 max-w-2xl mx-auto">
Let our AI create a personalized itinerary just for you. Answer a few questions about your preferences,
and we'll craft the perfect Melbourne experience tailored to your interests.
</p>
<Button
onClick={onCreateItineraryClick}
className="bg-primary hover:bg-primary/90 text-white px-8 py-6 font-poppins font-semibold text-lg"
>
Create My Magic Itinerary
</Button>
</motion.div>
</div>
{/* Hero Section */}
<section className="relative pt-52 pb-20 overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-secondary/5 to-background"></div>
{/* Decorative elements */}
<div className="absolute top-20 left-10 w-20 h-20 bg-primary/10 rounded-full blur-xl"></div>
<div className="absolute bottom-20 right-10 w-32 h-32 bg-secondary/10 rounded-full blur-xl"></div>
</section>
{/* How It Works Section */}
<HowItWorks />
{/* Features Section */}
<section className="py-16 bg-gradient-to-br from-gray-50 to-gray-100">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{/* Left side - Features */}
<div className="container mx-auto px-4 relative z-10">
<motion.div
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="max-w-4xl mx-auto text-center"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<h2 className="font-merchant text-3xl mb-6">
<span className="text-gray-900">Smart Features for</span><br />
<span className="bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">Smart Travelers</span>
</h2>
<div className="space-y-6">
{[
{
icon: <Sparkles className="w-6 h-6 text-purple-600" />,
title: 'AI-Powered Recommendations',
description: 'Our advanced AI analyzes your preferences to suggest the perfect experiences'
},
{
icon: <Clock className="w-6 h-6 text-pink-600" />,
title: 'Optimized Scheduling',
description: 'Smart timing that considers opening hours, travel time, and crowd patterns'
},
{
icon: <MapPin className="w-6 h-6 text-purple-600" />,
title: 'Location-Based Planning',
description: 'Efficiently planned routes that minimize travel time and maximize experiences'
},
{
icon: <Camera className="w-6 h-6 text-pink-600" />,
title: 'Instagram-Worthy Spots',
description: 'Discover hidden gems and perfect photo opportunities along your journey'
}
].map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.1 }}
className="flex items-start space-x-4"
>
<div className="flex-shrink-0 w-12 h-12 bg-white rounded-lg flex items-center justify-center shadow-md">
{feature.icon}
</div>
<div>
<h3 className="font-merchant text-lg mb-2">{feature.title}</h3>
<p className="text-gray-600 font-poppins">{feature.description}</p>
</div>
</motion.div>
))}
</div>
<h1 className="font-merchant text-4xl md:text-5xl lg:text-6xl leading-tight mb-6">
<span className="font-light">Plan Your Perfect</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Melbourne Adventure
</span>
</h1>
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 mb-8 max-w-2xl mx-auto">
Let our AI create a personalized itinerary just for you. Answer a few questions about your preferences,
and we'll craft the perfect Melbourne experience tailored to your interests.
</p>
<Button
onClick={onCreateItineraryClick}
className="bg-primary hover:bg-primary/90 text-white px-8 py-6 font-poppins font-semibold text-lg"
>
Create My Magic Itinerary
</Button>
</motion.div>
</div>
{/* Right side - Visual */}
<motion.div
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="relative"
>
<div className="bg-gradient-to-br from-purple-500 to-pink-500 rounded-2xl p-8 text-white">
<h3 className="font-merchant text-2xl mb-6">Sample Itinerary</h3>
<div className="space-y-4">
{/* Decorative elements */}
<div className="absolute top-20 left-10 w-20 h-20 bg-primary/10 rounded-full blur-xl"></div>
<div className="absolute bottom-20 right-10 w-32 h-32 bg-secondary/10 rounded-full blur-xl"></div>
</section>
{/* How It Works Section */}
<HowItWorks />
{/* Features Section */}
<section className="py-16 bg-gradient-to-br from-gray-50 to-gray-100">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{/* Left side - Features */}
<motion.div
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<h2 className="font-merchant text-3xl mb-6">
<span className="text-gray-900">Smart Features for</span><br />
<span className="bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">Smart Travelers</span>
</h2>
<div className="space-y-6">
{[
{ time: '9:00 AM', activity: 'Coffee at Degraves Street', duration: '30 min' },
{ time: '10:00 AM', activity: 'Royal Botanic Gardens', duration: '2 hours' },
{ time: '1:00 PM', activity: 'Lunch at Queen Victoria Market', duration: '1 hour' },
{ time: '3:00 PM', activity: 'Street Art Tour in Hosier Lane', duration: '1.5 hours' },
{ time: '6:00 PM', activity: 'Sunset at Eureka Skydeck', duration: '1 hour' }
].map((item, index) => (
<div key={index} className="flex items-center space-x-4 bg-white/10 rounded-lg p-3">
<div className="text-sm font-bold bg-white/20 px-2 py-1 rounded">
{item.time}
{
icon: <Sparkles className="w-6 h-6 text-purple-600" />,
title: 'AI-Powered Recommendations',
description: 'Our advanced AI analyzes your preferences to suggest the perfect experiences'
},
{
icon: <Clock className="w-6 h-6 text-pink-600" />,
title: 'Optimized Scheduling',
description: 'Smart timing that considers opening hours, travel time, and crowd patterns'
},
{
icon: <MapPin className="w-6 h-6 text-purple-600" />,
title: 'Location-Based Planning',
description: 'Efficiently planned routes that minimize travel time and maximize experiences'
},
{
icon: <Camera className="w-6 h-6 text-pink-600" />,
title: 'Instagram-Worthy Spots',
description: 'Discover hidden gems and perfect photo opportunities along your journey'
}
].map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.1 }}
className="flex items-start space-x-4"
>
<div className="flex-shrink-0 w-12 h-12 bg-white rounded-lg flex items-center justify-center shadow-md">
{feature.icon}
</div>
<div className="flex-1">
<div className="font-medium">{item.activity}</div>
<div className="text-xs text-white/80">{item.duration}</div>
<div>
<h3 className="font-merchant text-lg mb-2">{feature.title}</h3>
<p className="text-gray-600 font-poppins">{feature.description}</p>
</div>
</div>
</motion.div>
))}
</div>
</div>
</motion.div>
{/* Right side - Visual */}
<motion.div
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="relative"
>
<div className="bg-gradient-to-br from-purple-500 to-pink-500 rounded-2xl p-8 text-white">
<h3 className="font-merchant text-2xl mb-6">Sample Itinerary</h3>
<div className="space-y-4">
{[
{ time: '9:00 AM', activity: 'Coffee at Degraves Street', duration: '30 min' },
{ time: '10:00 AM', activity: 'Royal Botanic Gardens', duration: '2 hours' },
{ time: '1:00 PM', activity: 'Lunch at Queen Victoria Market', duration: '1 hour' },
{ time: '3:00 PM', activity: 'Street Art Tour in Hosier Lane', duration: '1.5 hours' },
{ time: '6:00 PM', activity: 'Sunset at Eureka Skydeck', duration: '1 hour' }
].map((item, index) => (
<div key={index} className="flex items-center space-x-4 bg-white/10 rounded-lg p-3">
<div className="text-sm font-bold bg-white/20 px-2 py-1 rounded">
{item.time}
</div>
<div className="flex-1">
<div className="font-medium">{item.activity}</div>
<div className="text-xs text-white/80">{item.duration}</div>
</div>
</div>
))}
</div>
</div>
</motion.div>
</div>
</div>
</section>
{/* Benefits Section */}
<section className="py-16">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-center mb-12"
>
<h2 className="font-merchant text-3xl mb-4">Why Use Magic Itinerary?</h2>
<p className="text-gray-600 font-poppins max-w-2xl mx-auto">
Save time, discover more, and create unforgettable memories with personalized planning
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{
icon: <Clock className="w-12 h-12 text-purple-600" />,
title: 'Save Planning Time',
description: 'Skip hours of research. Get a complete itinerary in under 5 minutes.',
stat: '90% faster than manual planning'
},
{
icon: <Star className="w-12 h-12 text-pink-600" />,
title: 'Discover Hidden Gems',
description: 'Find unique experiences and local favorites you might have missed.',
stat: '50+ curated hidden spots'
},
{
icon: <Heart className="w-12 h-12 text-purple-600" />,
title: 'Personalized Experience',
description: 'Every itinerary is unique, tailored specifically to your preferences.',
stat: '1000+ possible combinations'
}
].map((benefit, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.1 }}
>
<Card className="h-full text-center hover:shadow-lg transition-shadow duration-300">
<CardContent className="p-8">
<div className="mb-4">
{benefit.icon}
</div>
<h3 className="font-merchant text-xl mb-3">{benefit.title}</h3>
<p className="text-gray-600 font-poppins mb-4">{benefit.description}</p>
<Badge className="bg-gradient-to-r from-purple-100 to-pink-100 text-purple-700 border-none">
{benefit.stat}
</Badge>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</div>
</section>
</section>
{/* Benefits Section */}
<section className="py-16">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-center mb-12"
>
<h2 className="font-merchant text-3xl mb-4">Why Use Magic Itinerary?</h2>
<p className="text-gray-600 font-poppins max-w-2xl mx-auto">
Save time, discover more, and create unforgettable memories with personalized planning
</p>
</motion.div>
{/* What's Included Section */}
<section className="py-16 bg-gradient-to-br from-purple-50 to-pink-50">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-center mb-12"
>
<h2 className="font-merchant text-3xl mb-4">What's Included</h2>
<p className="text-gray-600 font-poppins max-w-2xl mx-auto">
Your Magic Itinerary comes with everything you need for an amazing Melbourne experience
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{
icon: <Clock className="w-12 h-12 text-purple-600" />,
title: 'Save Planning Time',
description: 'Skip hours of research. Get a complete itinerary in under 5 minutes.',
stat: '90% faster than manual planning'
},
{
icon: <Star className="w-12 h-12 text-pink-600" />,
title: 'Discover Hidden Gems',
description: 'Find unique experiences and local favorites you might have missed.',
stat: '50+ curated hidden spots'
},
{
icon: <Heart className="w-12 h-12 text-purple-600" />,
title: 'Personalized Experience',
description: 'Every itinerary is unique, tailored specifically to your preferences.',
stat: '1000+ possible combinations'
}
].map((benefit, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.1 }}
>
<Card className="h-full text-center hover:shadow-lg transition-shadow duration-300">
<CardContent className="p-8">
<div className="mb-4">
{benefit.icon}
</div>
<h3 className="font-merchant text-xl mb-3">{benefit.title}</h3>
<p className="text-gray-600 font-poppins mb-4">{benefit.description}</p>
<Badge className="bg-gradient-to-r from-purple-100 to-pink-100 text-purple-700 border-none">
{benefit.stat}
</Badge>
</CardContent>
</Card>
</motion.div>
))}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{[
{
title: 'Detailed Timeline',
description: 'Hour-by-hour schedule with optimal timing'
},
{
title: 'Transportation Tips',
description: 'Best routes and transport options between locations'
},
{
title: 'Local Recommendations',
description: 'Insider tips on food, shopping, and experiences'
},
{
title: 'Budget Planning',
description: 'Estimated costs and money-saving suggestions'
},
{
title: 'Weather Backup Plans',
description: 'Alternative indoor activities for rainy days'
},
{
title: 'Photo Opportunities',
description: 'Best spots and times for Instagram-worthy shots'
},
{
title: 'Cultural Insights',
description: 'Local history and interesting facts about each location'
},
{
title: 'Real-time Updates',
description: 'Live information on closures, events, and crowds'
}
].map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.05 }}
>
<Card className="h-full hover:shadow-md transition-shadow duration-300">
<CardContent className="p-6 text-center">
<h3 className="font-merchant text-lg mb-2">{item.title}</h3>
<p className="text-gray-600 font-poppins text-sm">{item.description}</p>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</div>
</section>
</section>
{/* What's Included Section */}
<section className="py-16 bg-gradient-to-br from-purple-50 to-pink-50">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-center mb-12"
>
<h2 className="font-merchant text-3xl mb-4">What's Included</h2>
<p className="text-gray-600 font-poppins max-w-2xl mx-auto">
Your Magic Itinerary comes with everything you need for an amazing Melbourne experience
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{[
{
title: 'Detailed Timeline',
description: 'Hour-by-hour schedule with optimal timing'
},
{
title: 'Transportation Tips',
description: 'Best routes and transport options between locations'
},
{
title: 'Local Recommendations',
description: 'Insider tips on food, shopping, and experiences'
},
{
title: 'Budget Planning',
description: 'Estimated costs and money-saving suggestions'
},
{
title: 'Weather Backup Plans',
description: 'Alternative indoor activities for rainy days'
},
{
title: 'Photo Opportunities',
description: 'Best spots and times for Instagram-worthy shots'
},
{
title: 'Cultural Insights',
description: 'Local history and interesting facts about each location'
},
{
title: 'Real-time Updates',
description: 'Live information on closures, events, and crowds'
}
].map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 + index * 0.05 }}
>
<Card className="h-full hover:shadow-md transition-shadow duration-300">
<CardContent className="p-6 text-center">
<h3 className="font-merchant text-lg mb-2">{item.title}</h3>
<p className="text-gray-600 font-poppins text-sm">{item.description}</p>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</section>
{/* CTA Section */}
{/* CTA Section */}
{/* Mobile App Section */}
<MobileAppSection />
{/* Mobile App Section */}
<MobileAppSection />
{/* Customer Reviews */}
<EnhancedTestimonials />
{/* Customer Reviews */}
<EnhancedTestimonials />
</Layout>
</Layout>
</div>
);
}

View File

@@ -45,6 +45,18 @@ interface DropdownProps {
className?: string;
}
interface NavigationItem {
id: number;
label: string;
path: string;
displayLabel: string;
source: 'landing' | 'melbourne';
position: number;
isShared?: boolean;
landingLabel?: string;
melbourneLabel?: string;
}
export default function Navbar({
activeCity,
onCityChange,
@@ -60,6 +72,9 @@ export default function Navbar({
const [activeUserDropdown, setActiveUserDropdown] = useState(false);
const [activeCityDropdown, setActiveCityDropdown] = useState(false);
const [isCityDialogOpen, setIsCityDialogOpen] = useState(false);
const [navigationSource, setNavigationSource] = useState<'landing' | 'melbourne'>('landing');
const [dialogSource, setDialogSource] = useState<'navbar' | 'cta'>('navbar');
const languageRef = useRef<HTMLDivElement>(null);
const cartRef = useRef<HTMLDivElement>(null);
@@ -69,22 +84,200 @@ export default function Navbar({
const location = useLocation();
const navigate = useNavigate();
const handleOpenCityDialog = () => {
// More flexible navigation configuration
const navigationConfig = {
landing: [
// Position 1 - Shared item
{
label: 'Discover',
path: '/how-it-works',
isShared: true,
landingLabel: 'Discover',
melbourneLabel: 'How It Works'
},
// Position 2
{
label: 'Magic Itinerary',
path: '/landing-magic-itinerary',
isShared: false
},
// Position 3
{
label: 'Whats Included',
path: '/whats-included',
isShared: false
},
// Position 4 - Shared item
{
label: 'Your Card',
path: '/passes',
isShared: true,
landingLabel: 'Your Card',
melbourneLabel: 'Your Card'
},
// Position 5
{
label: 'FAQ',
path: '/faq',
isShared: false
},
],
melbourne: [
// Position 1
{
label: 'Attractions',
path: '/attractions',
isShared: false
},
// Position 2
{
label: 'Magic Itinerary',
path: '/magic-itinerary',
isShared: false
},
// Position 3
{
label: 'Super Savings',
path: '/super-savings',
isShared: false
},
// Position 4 - Shared item
{
label: 'How It Works',
path: '/how-it-works',
isShared: true,
landingLabel: 'Discover',
melbourneLabel: 'How It Works'
},
// Position 5 - Shared item
{
label: 'Your Card',
path: '/passes',
isShared: true,
landingLabel: 'Your Card',
melbourneLabel: 'Your Card'
}
]
};
// Check if we're on landing page
const isLandingPage = location.pathname === '/';
// Auto-detect navigation source based on activeCity and current page
const getAutoNavigationSource = (): 'landing' | 'melbourne' => {
// If activeCity is explicitly set to 'shared', detect from context
if (activeCity.toLowerCase() === 'shared') {
// Check if we're on Melbourne-specific pages
const isMelbournePage =
location.pathname === '/melbourne' ||
location.pathname.startsWith('/attractions') ||
location.pathname === '/magic-itinerary' ||
location.pathname === '/super-savings';
return isMelbournePage ? 'melbourne' : 'landing';
}
// If activeCity is explicitly Melbourne, use melbourne source
if (activeCity.toLowerCase() === 'melbourne') {
return 'melbourne';
}
// Default to landing
return 'landing';
};
// Get navigation items based on current context
const getNavigationItems = (): NavigationItem[] => {
const currentSource = getAutoNavigationSource();
const items = currentSource === 'landing' ?
navigationConfig.landing : navigationConfig.melbourne;
return items.map((item, index) => ({
...item,
id: index + 1,
displayLabel: currentSource === 'landing'
? (item.landingLabel || item.label)
: (item.melbourneLabel || item.label),
source: currentSource,
position: index + 1
}));
};
const navigationItems = getNavigationItems();
// Enhanced active state logic with proper typing
const isNavItemActive = (item: NavigationItem): boolean => {
const currentPath = location.pathname;
const currentSource = getAutoNavigationSource();
// Special handling for shared paths
if (item.isShared) {
return currentPath === item.path && currentSource === item.source;
}
// Default active state for other paths
return currentPath === item.path;
};
const handleOpenCityDialogFromNavbar = () => {
setDialogSource('navbar');
setIsCityDialogOpen(true);
};
const handleOpenCityDialogFromCTA = () => {
setDialogSource('cta');
setIsCityDialogOpen(true);
};
const handleCloseCityDialog = () => {
setIsCityDialogOpen(false);
setDialogSource('navbar');
};
const handleCitySelectFromNavbar = (cityId: string) => {
console.log('City selected from navbar:', cityId);
onCityChange(cityId);
if (cityId.toLowerCase() === 'melbourne') {
setNavigationSource('melbourne');
navigate('/melbourne');
} else {
setNavigationSource('landing');
navigate('/passes');
}
handleCloseCityDialog();
};
const handleCitySelectFromCTA = (cityId: string) => {
console.log('City selected from CTA:', cityId);
onCityChange(cityId);
if (!isUserSignedIn) {
setNavigationSource('landing');
navigate('/passes');
} else {
if (cityId.toLowerCase() === 'melbourne') {
setNavigationSource('melbourne');
navigate('/melbourne');
} else {
setNavigationSource('landing');
navigate('/passes');
}
}
handleCloseCityDialog();
};
// ✅ Handle city selection from dialog
const handleCitySelect = (cityId: string) => {
console.log('City selected in navbar:', cityId);
// Navigate to passes page when city is selected
navigate('/passes');
if (dialogSource === 'cta') {
handleCitySelectFromCTA(cityId);
} else {
handleCitySelectFromNavbar(cityId);
}
};
// Available cities
// Available cities for mobile dropdown
const cities = [
{ id: 'melbourne', label: 'Melbourne' },
{ id: 'sydney', label: 'Sydney' },
@@ -92,33 +285,6 @@ export default function Navbar({
{ id: 'perth', label: 'Perth' }
];
// Check if we're on landing page
const isLandingPage = location.pathname === '/';
// Landing page navigation items
const landingPageNavigationItems = [
{ label: 'CityCards', path: '/citycards' },
{ label: 'Postcards', path: '/postcards' },
{ label: 'eSIMs', path: '/esims' },
{ label: 'Hotel Discount', path: '/hotel-discounts' },
{ label: 'Offers', path: '/offers' }
];
// Melbourne-specific navigation items (without /melbourne in paths)
const melbourneNavigationItems = [
{ label: 'Attractions', path: '/attractions' },
{ label: 'Magic Itinerary', path: '/magic-itinerary' },
{ label: 'Super Savings', path: '/super-savings' },
{ label: 'How It Works', path: '/how-it-works' },
{ label: 'Your Card', path: '/passes' }
];
// Get current navigation items based on page and city
const navigationItems = isLandingPage
? landingPageNavigationItems
: (activeCity.toLowerCase() === 'melbourne' ? melbourneNavigationItems : landingPageNavigationItems);
// Languages available
const languages: DropdownItem[] = [
{ id: 'en', label: 'English', icon: <span className="text-base">🇺🇸</span> },
@@ -156,35 +322,24 @@ export default function Navbar({
label: 'Proceed to Checkout',
action: () => {
navigate('/checkout');
setActiveCartDropdown(false); // Close dropdown after navigation
setActiveCartDropdown(false);
}
}
];
const scrollToSection = (index: number) => {
const sectionIds = [
'hero-section',
'why-choose-section',
'variety-adventures-section',
'how-it-works-section',
'magic-itinerary-section',
'book-attraction-section',
'custom-postcards-section',
'upcoming-cities-section',
'trust-section',
'mobile-app-section'
];
const sectionId = sectionIds[index];
const element = document.getElementById(sectionId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
const closeMobileMenu = () => {
setIsMobileMenuOpen(false);
};
const closeMobileMenu = () => {
setIsMobileMenuOpen(false);
// Enhanced navigation handler with proper typing
const handleNavClick = (path: string) => {
navigate(path);
closeMobileMenu();
};
// Get navigation source for an item with proper typing
const getNavigationSource = (item: NavigationItem): 'landing' | 'melbourne' => {
return item.source;
};
// Detect scroll for navbar styling
@@ -198,27 +353,17 @@ export default function Navbar({
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const handleNavClick = (path: string) => {
navigate(path);
closeMobileMenu();
};
const isNavItemActive = (path: string) => {
return location.pathname === path;
};
// Handle city change
const handleCityChange = (city: string) => {
console.log('City selected:', city); // Debug log
// Handle city change for mobile dropdown
const handleMobileCityChange = (city: string) => {
console.log('City selected:', city);
onCityChange(city);
setActiveCityDropdown(false);
// Navigate based on city selection
if (city.toLowerCase() === 'melbourne') {
console.log('Navigating to Melbourne'); // Debug log
setNavigationSource('melbourne');
navigate('/melbourne');
} else {
console.log('Navigating to coming soon'); // Debug log
setNavigationSource('landing');
navigate('/comming-soon');
}
};
@@ -265,7 +410,7 @@ export default function Navbar({
exit={{ opacity: 0, y: 10, scale: 0.95 }}
transition={{ duration: 0.2, ease: [0.25, 0.1, 0.25, 1] }}
className="absolute top-full left-0 mt-2 bg-white/95 backdrop-blur-xl rounded-2xl shadow-xl border border-white/20 min-w-[200px] overflow-hidden z-50"
onClick={(e) => e.stopPropagation()} // Prevent clicks inside from closing
onClick={(e) => e.stopPropagation()}
>
{title && (
<div className="px-5 py-4 border-b border-gray-100/50">
@@ -284,7 +429,6 @@ export default function Navbar({
if (item.action) {
item.action();
}
// Don't call onToggle here - let the backdrop handle closing
}}
className="px-4 py-2.5 hover:bg-gray-50/80 cursor-pointer transition-colors duration-200"
>
@@ -309,6 +453,9 @@ export default function Navbar({
// Set display name for debugging
Dropdown.displayName = 'Dropdown';
// Get current navigation source for display
const currentSource = getAutoNavigationSource();
return (
<>
{/* Desktop Navbar - Enhanced Glassmorphism */}
@@ -341,9 +488,9 @@ export default function Navbar({
>
<Link to="/">
<ImageWithFallback
src={activeCity?.toLowerCase() === 'melbourne' ? melbourneLogo : logoImage}
src={currentSource === 'melbourne' ? melbourneLogo : logoImage}
alt={
activeCity?.toLowerCase() === 'melbourne'
currentSource === 'melbourne'
? 'Melbourne CityCards Logo'
: 'CityCards Logo'
}
@@ -355,75 +502,64 @@ export default function Navbar({
<div className="absolute -translate-x-1/2 flex items-center gap-5"
style={{ left: '45%', }}
>
{/* Navigation Items based on page type */}
{navigationItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={`relative px-0 py-2 text-base font-medium transition-all duration-200 whitespace-nowrap group ${isNavItemActive(item.path)
? 'text-primary'
: 'text-gray-700 hover:text-gray-900'
}`}
>
{item.label}
{/* Active indicator */}
<motion.div
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-secondary rounded-full"
initial={{ width: 0 }}
animate={{
width: isNavItemActive(item.path) ? "100%" : 0,
opacity: isNavItemActive(item.path) ? 1 : 0
}}
whileHover={{
width: "100%",
opacity: 1
}}
transition={{ duration: 0.2 }}
/>
{/* Hover background */}
<motion.div
className="absolute inset-0 bg-gray-100/50 backdrop-blur-sm rounded-lg -z-10"
initial={{ scale: 0, opacity: 0 }}
whileHover={{ scale: 1, opacity: 0.5 }}
transition={{ duration: 0.2 }}
/>
</Link>
))}
{/* Enhanced Navigation Items with source tracking */}
{navigationItems.map((item) => {
const source = getNavigationSource(item);
return (
<div
key={`${item.path}-${source}-${item.id}`}
onClick={() => handleNavClick(item.path)}
className={`relative px-0 py-2 text-base font-medium transition-all duration-200 whitespace-nowrap group cursor-pointer ${isNavItemActive(item)
? 'text-primary'
: 'text-gray-700 hover:text-gray-900'
}`}
>
{item.displayLabel}
{/* Active indicator */}
<motion.div
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-secondary rounded-full"
initial={{ width: 0 }}
animate={{
width: isNavItemActive(item) ? "100%" : 0,
opacity: isNavItemActive(item) ? 1 : 0
}}
whileHover={{
width: "100%",
opacity: 1
}}
transition={{ duration: 0.2 }}
/>
{/* Hover background */}
<motion.div
className="absolute inset-0 bg-gray-100/50 backdrop-blur-sm rounded-lg -z-10"
initial={{ scale: 0, opacity: 0 }}
whileHover={{ scale: 1, opacity: 0.5 }}
transition={{ duration: 0.2 }}
/>
</div>
);
})}
</div>
{/* Right Section */}
<div className="flex items-center gap-1">
{/* City Dropdown */}
<Dropdown
ref={cityRef}
isOpen={activeCityDropdown}
onToggle={() => {
console.log('City dropdown toggled');
setTimeout(() => {
setActiveCityDropdown(!activeCityDropdown);
}, 50);
}}
items={cities.map(city => ({
id: 'city-change',
label: city.label,
action: () => {
console.log('City action called:', city.id);
handleCityChange(city.id);
{/* City Selector - Uses Navbar Handler */}
<div
className="flex items-center space-x-1.5 text-gray-700 hover:text-gray-900 text-base font-medium transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50/50 px-2 py-1"
onClick={handleOpenCityDialogFromNavbar}
>
<span>
{activeCity &&
activeCity !== 'shared' &&
activeCity !== 'Landingpage' &&
activeCity.toLowerCase() !== 'landingpage' ?
activeCity.charAt(0).toUpperCase() + activeCity.slice(1) :
'Select City'
}
}))}
title="Select City"
trigger={
<div className="flex items-center space-x-1.5 text-gray-700 hover:text-gray-900 text-base font-medium transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50/50 px-2 py-1">
<span>
{isLandingPage || activeCity === 'Landingpage' || !activeCity
? 'Select City'
: activeCity.charAt(0).toUpperCase() + activeCity.slice(1)
}
</span>
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${activeCityDropdown ? 'rotate-180' : ''}`} />
</div>
}
/>
</span>
<ChevronDown className="w-3.5 h-3.5" />
</div>
{/* Language Dropdown */}
<Dropdown
@@ -461,11 +597,11 @@ export default function Navbar({
</div>
}
/>
{/* ✅ UPDATED: City Card Button with Proper Authentication Logic */}
{/* Enhanced City Card Button with Source Tracking */}
<div className="flex items-center gap-3 pl-2">
<div className="relative">
{isUserSignedIn && user ? (
// ✅ When user is logged in - show user dropdown
<Dropdown
ref={userRef}
isOpen={activeUserDropdown}
@@ -512,33 +648,23 @@ export default function Navbar({
>
<CTAButton
user={user}
onClick={() => { }} // Empty function since we handle click above
onClick={() => { }}
className="hover:scale-105 transition-transform duration-200"
/>
</div>
}
/>
) : (
// ✅ When user is NOT logged in - show city selection dialog
<>
<div
className="cursor-pointer"
onClick={handleOpenCityDialog}
>
<CTAButton
user={null}
onClick={() => { }} // Empty function since we handle click above
className="hover:scale-105 transition-transform duration-200"
/>
</div>
{/* ✅ City Selection Dialog with navigation to passes */}
<CitySelectionDialog
isOpen={isCityDialogOpen}
onClose={handleCloseCityDialog}
onCitySelect={handleCitySelect}
<div
className="cursor-pointer"
onClick={handleOpenCityDialogFromCTA}
>
<CTAButton
user={null}
onClick={() => { }}
className="hover:scale-105 transition-transform duration-200"
/>
</>
</div>
)}
</div>
</div>
@@ -548,7 +674,6 @@ export default function Navbar({
</div>
</motion.nav>
{/* Mobile Navbar - Enhanced Glassmorphism */}
<nav className="fixed top-0 w-full z-50 lg:hidden">
<div className={`transition-all duration-500 ease-out border-b shadow-sm ${isScrolled
@@ -582,15 +707,15 @@ export default function Navbar({
items={cities.map(city => ({
id: 'city-change',
label: city.label,
action: () => handleCityChange(city.id)
action: () => handleMobileCityChange(city.id)
}))}
title="Select City"
trigger={
<div className="flex items-center space-x-1 text-gray-700 hover:text-gray-900 text-sm font-medium transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50/50 px-2 py-1">
<span>
{isLandingPage
? 'Select City'
: activeCity.charAt(0).toUpperCase() + activeCity.slice(1)
{activeCity && activeCity !== 'shared' ?
activeCity.charAt(0).toUpperCase() + activeCity.slice(1) :
currentSource === 'melbourne' ? 'Melbourne' : 'Select City'
}
</span>
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${activeCityDropdown ? 'rotate-180' : ''}`} />
@@ -626,7 +751,7 @@ export default function Navbar({
</div>
</nav>
{/* Mobile Menu Overlay - Enhanced Glassmorphism */}
{/* Enhanced Mobile Menu Overlay */}
<AnimatePresence>
{isMobileMenuOpen && (
<>
@@ -658,23 +783,27 @@ export default function Navbar({
</div>
<div className="p-6 space-y-6">
{/* Navigation Links based on page type */}
{/* Enhanced Navigation Links with source tracking */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 px-4">Navigation</h3>
{navigationItems.map((item) => (
<motion.button
key={item.path}
onClick={() => handleNavClick(item.path)}
className={`w-full flex items-center justify-between py-3 px-4 rounded-lg text-left transition-colors duration-200 ${isNavItemActive(item.path)
? 'bg-primary/10 text-primary font-medium'
: 'text-gray-700 hover:bg-gray-100/70 hover:text-gray-900'
}`}
whileHover={{ x: 4 }}
whileTap={{ scale: 0.98 }}
>
<span className="text-base">{item.label}</span>
</motion.button>
))}
{navigationItems.map((item) => {
const source = getNavigationSource(item);
return (
<motion.button
key={`${item.path}-${source}-${item.id}`}
onClick={() => handleNavClick(item.path)}
className={`w-full flex items-center justify-between py-3 px-4 rounded-lg text-left transition-colors duration-200 ${isNavItemActive(item)
? 'bg-primary/10 text-primary font-medium'
: 'text-gray-700 hover:bg-gray-100/70 hover:text-gray-900'
}`}
whileHover={{ x: 4 }}
whileTap={{ scale: 0.98 }}
>
<span className="text-base">{item.displayLabel}</span>
</motion.button>
);
})}
</div>
{/* City Selection in Mobile Menu */}
@@ -683,7 +812,7 @@ export default function Navbar({
{cities.map((city) => (
<motion.button
key={city.id}
onClick={() => handleCityChange(city.id)}
onClick={() => handleMobileCityChange(city.id)}
className={`w-full flex items-center justify-between py-3 px-4 rounded-lg text-left transition-colors duration-200 ${activeCity === city.id
? 'bg-primary/10 text-primary font-medium'
: 'text-gray-700 hover:bg-gray-100/70 hover:text-gray-900'
@@ -698,7 +827,7 @@ export default function Navbar({
{/* Mobile CTA Button */}
<Button
onClick={() => setIsCityDialogOpen(true)}
onClick={handleOpenCityDialogFromCTA}
withShine={true}
size="lg"
className="min-w-[180px] font-poppins font-semibold rounded-full"
@@ -710,6 +839,13 @@ export default function Navbar({
</>
)}
</AnimatePresence>
{/* Enhanced City Selection Dialog */}
<CitySelectionDialog
isOpen={isCityDialogOpen}
onClose={handleCloseCityDialog}
onCitySelect={handleCitySelect}
/>
</>
);
}

View File

@@ -1,266 +1,374 @@
import { motion } from 'motion/react';
import { Check, Clock } from 'lucide-react';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { Sparkles, MapPin, Calendar, Wand2, Clock } from 'lucide-react';
import { ImageWithFallback } from './figma/ImageWithFallback';
import exampleImage from 'figma:asset/9404cd260e0be9427a5438a687bca92b4e4740ac.png';
interface PersonalizedTourHeroProps {
onCreateItineraryClick?: () => void;
}
interface AttractionCard {
id: number;
name: string;
image: string;
time: string;
category: string;
}
export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTourHeroProps) {
// Mock activity data
const activities = [
const attractionCards: AttractionCard[] = [
{
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'
name: 'SEA LIFE Melbourne Aquarium',
image: 'https://images.unsplash.com/photo-1696693886265-e73512cdf712?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhcXVhcml1bSUyMHNlYSUyMGxpZmV8ZW58MXx8fHwxNzYyMzI2NDk5fDA&ixlib=rb-4.1.0&q=80&w=1080',
time: '2-3 hours',
category: 'Marine Life'
},
{
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...'
name: 'Eureka Skydeck 88',
image: 'https://images.unsplash.com/photo-1720044109127-0aee490512be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxldXJla2ElMjBza3lkZWNrJTIwbWVsYm91cm5lfGVufDF8fHx8MTc2MjMyNjUwMHww&ixlib=rb-4.1.0&q=80&w=1080',
time: '1-2 hours',
category: 'Observation Deck'
},
{
id: 3,
text: 'Upgrade my Buy1 At1dfls visit to the SKY',
status: 'pending'
name: 'Royal Botanic Gardens',
image: 'https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zJTIwbWVsYm91cm5lfGVufDF8fHx8MTc2MjMyNjUwMHww&ixlib=rb-4.1.0&q=80&w=1080',
time: '2-4 hours',
category: 'Nature & Parks'
},
{
id: 4,
name: 'Melbourne Zoo',
image: 'https://images.unsplash.com/photo-1681429477985-30dc7b88dd5b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjB6b28lMjBhbmltYWxzfGVufDF8fHx8MTc2MjMyNjUwMXww&ixlib=rb-4.1.0&q=80&w=1080',
time: '3-5 hours',
category: 'Wildlife'
},
{
id: 5,
name: 'National Gallery of Victoria',
image: 'https://images.unsplash.com/photo-1642888619334-55c0179590a3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxuYXRpb25hbCUyMGdhbGxlcnklMjB2aWN0b3JpYXxlbnwxfHx8fDE3NjIzMjY1MDF8MA&ixlib=rb-4.1.0&q=80&w=1080',
time: '2-3 hours',
category: 'Art & Culture'
}
];
// 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'
];
const [currentCardIndex, setCurrentCardIndex] = useState(0);
// 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'
];
useEffect(() => {
const timer = setInterval(() => {
setCurrentCardIndex((prev) => (prev + 1) % attractionCards.length);
}, 3000);
return () => clearInterval(timer);
}, [attractionCards.length]);
const currentCard = attractionCards[currentCardIndex];
const nextCard = attractionCards[(currentCardIndex + 1) % attractionCards.length];
const thirdCard = attractionCards[(currentCardIndex + 2) % attractionCards.length];
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 className="relative w-full min-h-[90vh] overflow-hidden flex items-center bg-gradient-to-br from-orange-50 via-white to-rose-50">
{/* Gradient Background Elements */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute top-0 right-0 w-96 h-96 bg-gradient-to-br from-primary/10 to-transparent rounded-full blur-3xl"></div>
<div className="absolute bottom-0 left-0 w-96 h-96 bg-gradient-to-tr from-orange-100/30 to-transparent rounded-full blur-3xl"></div>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-gradient-to-br from-rose-50/50 to-transparent rounded-full blur-3xl"></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 */}
<div className="relative z-10 w-full px-4 pt-32 md:pt-40 pb-16 md:pb-20">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Left Content */}
<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 }}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
transition={{ duration: 0.6 }}
className="text-left"
>
<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"
{/* Badge */}
<motion.div
className="inline-flex items-center gap-2 bg-gradient-to-r from-primary/10 to-orange-100/50 backdrop-blur-sm px-5 py-2 rounded-full border-2 border-primary/30 shadow-lg mb-6"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6 }}
>
<motion.div
animate={{
rotate: [0, 15, -15, 0],
scale: [1, 1.15, 1.15, 1],
}}
transition={{ duration: 2, repeat: Infinity }}
>
<Wand2 className="w-5 h-5 text-primary drop-shadow-lg" />
</motion.div>
<span className="font-poppins font-semibold text-gray-800">AI-Powered Planning</span>
<motion.div
className="w-1.5 h-1.5 bg-primary rounded-full"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1],
}}
transition={{ duration: 1.5, repeat: Infinity }}
/>
</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>
</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>
{/* Main Heading */}
<h1 className="font-poppins text-4xl sm:text-5xl md:text-6xl leading-tight mb-6">
<span className="font-light">Create Your</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-primary via-orange-500 to-rose-500 bg-clip-text text-transparent">
Magic Itinerary
</span>
</h1>
{/* 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 className="font-poppins text-lg md:text-xl font-normal leading-relaxed text-gray-600 mb-8">
Let AI craft a personalized journey tailored to your interests, timeline, and travel style. Get the perfect itinerary in minutes.
</p>
{/* Quick Features */}
<div className="space-y-3 mb-8">
{[
{ icon: <Sparkles className="w-5 h-5" />, text: 'AI-powered smart suggestions' },
{ icon: <MapPin className="w-5 h-5" />, text: '40+ top Melbourne attractions' },
{ icon: <Calendar className="w-5 h-5" />, text: 'Flexible & customizable plans' }
].map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.2 + index * 0.1 }}
className="flex items-center gap-3"
>
<div className="flex-shrink-0 text-primary">
{feature.icon}
</div>
<span className="font-poppins text-base font-normal text-gray-700">
{feature.text}
</span>
</motion.div>
))}
</div>
{/* CTA Button */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
className="flex flex-col sm:flex-row gap-4 items-start sm:items-center"
>
<button
onClick={onCreateItineraryClick}
className="group relative px-8 py-4 rounded-lg flex items-center gap-2 overflow-hidden transition-all duration-300 hover:scale-105 shadow-lg hover:shadow-xl font-poppins font-semibold text-base text-white bg-gradient-to-r from-primary via-orange-500 to-rose-500 hover:from-primary/90 hover:via-orange-500/90 hover:to-rose-500/90"
>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700" />
<Wand2 className="w-5 h-5 relative z-10 group-hover:rotate-12 transition-transform duration-300" />
<span className="relative z-10">Create My Itinerary</span>
</button>
<p className="font-poppins text-sm text-gray-600 font-normal flex items-center gap-2">
<Sparkles className="w-4 h-4 text-primary" />
<span>Free Takes less than 2 minutes</span>
</p>
</div>
</motion.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' }}
{/* Right - Animated Card Stack */}
<motion.div
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="relative hidden lg:block"
>
{/* 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 className="relative flex justify-center items-center min-h-[500px]" style={{ perspective: '1000px' }}>
{/* Card Stack Container */}
<div className="relative w-full max-w-md" style={{ transformStyle: 'preserve-3d' }}>
{/* Third Card (Background) */}
<motion.div
className="absolute inset-0 bg-white rounded-2xl shadow-lg overflow-hidden"
style={{
zIndex: 1,
transformStyle: 'preserve-3d'
}}
animate={{
scale: 0.88,
y: 20,
opacity: 0.4,
}}
transition={{ duration: 0.3 }}
>
<div className="relative h-56 overflow-hidden">
<ImageWithFallback
src={thirdCard.image}
alt={thirdCard.name}
className="w-full h-full object-cover opacity-60"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
</div>
</motion.div>
{/* Second Card */}
<motion.div
className="absolute inset-0 bg-white rounded-2xl shadow-xl overflow-hidden"
style={{
zIndex: 2,
transformStyle: 'preserve-3d'
}}
animate={{
scale: 0.94,
y: 10,
opacity: 0.7,
}}
transition={{ duration: 0.3 }}
>
<div className="relative h-56 overflow-hidden">
<ImageWithFallback
src={nextCard.image}
alt={nextCard.name}
className="w-full h-full object-cover opacity-80"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
</div>
</motion.div>
{/* Front Card (Animated) */}
<AnimatePresence mode="popLayout">
<motion.div
key={currentCard.id}
className="relative bg-white rounded-2xl shadow-2xl overflow-hidden border border-gray-100"
style={{
zIndex: 3,
transformStyle: 'preserve-3d'
}}
initial={{
scale: 0.94,
y: 10,
opacity: 0.7,
}}
animate={{
scale: 1,
opacity: 1,
y: 0,
}}
exit={{
scale: 1.05,
opacity: 0,
x: -80,
rotateZ: -5,
}}
transition={{
duration: 0.5,
ease: [0.34, 1.56, 0.64, 1],
}}
>
{/* Card Image */}
<div className="relative h-56 overflow-hidden">
<ImageWithFallback
src={currentCard.image}
alt={currentCard.name}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
{/* Category Badge */}
<motion.div
className="absolute top-4 left-4 bg-white/90 backdrop-blur-sm px-3 py-1.5 rounded-full shadow-lg"
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2, duration: 0.4, type: "spring" }}
>
<span className="font-poppins font-semibold text-primary text-xs">
{currentCard.category}
</span>
</motion.div>
{/* Time Badge */}
<motion.div
className="absolute top-4 right-4 bg-gradient-to-r from-primary to-orange-500 px-3 py-1.5 rounded-full shadow-lg"
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.25, duration: 0.4, type: "spring" }}
>
<div className="flex items-center gap-1.5">
<Clock className="w-3.5 h-3.5 text-white" />
<span className="font-poppins font-semibold text-white text-xs">{currentCard.time}</span>
</div>
</motion.div>
{/* Attraction Name Overlay */}
<motion.div
className="absolute bottom-4 left-4 right-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.15, duration: 0.4 }}
>
<h3 className="font-poppins font-semibold text-xl text-white leading-tight mb-1">
{currentCard.name}
</h3>
<p className="font-poppins font-normal text-sm text-white/90 flex items-center gap-1.5">
<MapPin className="w-4 h-4" />
Melbourne, Australia
</p>
</motion.div>
</div>
{/* Card Content */}
<motion.div
className="p-5"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.25, duration: 0.4 }}
>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-gradient-to-br from-primary to-orange-500 rounded-full flex items-center justify-center">
<span className="font-poppins text-white font-semibold text-sm">
{currentCardIndex + 1}
</span>
</div>
<span className="font-poppins text-sm font-medium text-gray-600">
Day {currentCardIndex + 1} Activity
</span>
</div>
<div className="flex items-center gap-1">
<Sparkles className="w-4 h-4 text-primary" />
<span className="font-poppins text-xs font-medium text-primary">AI Selected</span>
</div>
</div>
<div className="bg-gradient-to-r from-primary/5 to-orange-50/50 rounded-xl p-4 border border-primary/10">
<p className="font-poppins text-sm font-normal text-gray-700 leading-relaxed">
Added to your personalized itinerary based on your preferences and travel style
</p>
</div>
</motion.div>
</motion.div>
</AnimatePresence>
</div>
</div>
{/* Card Indicators */}
<motion.div
className="flex flex-wrap justify-center gap-2 mt-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
>
{attractionCards.map((card, idx) => (
<button
key={card.id}
onClick={() => setCurrentCardIndex(idx)}
className={`transition-all duration-300 w-2 h-2 rounded-full ${
idx === currentCardIndex
? 'bg-gradient-to-r from-primary to-orange-500 scale-125 w-6'
: 'bg-gray-300 hover:bg-gray-400'
}`}
/>
))}
</motion.div>
</motion.div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,201 @@
import { Ticket, TrendingDown, Sparkles, DollarSign, Calendar, Check } from 'lucide-react';
import { motion } from 'motion/react';
const attractions = [
{ name: 'Royal Botanic Gardens', price: 25 },
{ name: 'Eureka Skydeck', price: 32 },
{ name: 'Queen Victoria Market Tour', price: 45 },
{ name: 'Melbourne Zoo', price: 42 },
{ name: 'Street Art Tour', price: 55 },
{ name: 'Harbour Cruise', price: 38 },
{ name: 'Royal Exhibition Building', price: 25 },
{ name: 'Melbourne Museum', price: 28 }
];
export function SmartSaving() {
const totalIndividualPrice = attractions.reduce((sum, attr) => sum + attr.price, 0);
const cityCardPrice = 99;
const savings = totalIndividualPrice - cityCardPrice;
const savingsPercent = Math.round((savings / totalIndividualPrice) * 100);
return (
<section className="py-12 md:py-16 bg-gradient-to-br from-green-50/30 to-blue-50/30">
<div className="container mx-auto px-4">
{/* Header */}
<div className="text-center mb-10">
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-primary/10 to-secondary/10 px-4 py-2 rounded-full mb-4">
<div className="w-2 h-2 bg-gradient-to-r from-primary to-secondary rounded-full"></div>
<span className="font-poppins text-sm font-medium bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Maximum Value
</span>
</div>
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl leading-tight text-gray-900 mb-3">
<span className="font-semibold" style={{ color: '#F95F62' }}>Smart Savings</span>
</h2>
<p className="font-poppins text-lg leading-relaxed font-normal text-gray-600 max-w-3xl mx-auto">
Save up to 40% compared to buying individual tickets
</p>
</div>
{/* Main Content */}
<div className="max-w-4xl mx-auto">
<div className="bg-white rounded-3xl p-8 border border-gray-100 shadow-lg">
{/* Hero Tagline Cards */}
<div className="mb-8">
<div className="grid grid-cols-3 gap-4 max-w-md mx-auto">
{/* 3 Days Card */}
<motion.div
className="rounded-2xl p-4 text-center"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.08)' }}
animate={{
boxShadow: [
'0 4px 6px rgba(249, 95, 98, 0.1)',
'0 8px 16px rgba(249, 95, 98, 0.2)',
'0 4px 6px rgba(249, 95, 98, 0.1)'
],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
>
<Calendar className="w-8 h-8 mx-auto mb-2" style={{ color: '#F95F62' }} />
<div className="font-poppins font-semibold text-2xl mb-1" style={{ color: '#F95F62' }}>3</div>
<div className="font-poppins text-xs font-medium text-gray-600">Days</div>
</motion.div>
{/* 8 Experiences Card */}
<motion.div
className="rounded-2xl p-4 text-center"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.08)' }}
animate={{
boxShadow: [
'0 4px 6px rgba(249, 95, 98, 0.1)',
'0 8px 16px rgba(249, 95, 98, 0.2)',
'0 4px 6px rgba(249, 95, 98, 0.1)'
],
}}
transition={{
duration: 2,
repeat: Infinity,
delay: 0.3,
ease: "easeInOut"
}}
>
<Ticket className="w-8 h-8 mx-auto mb-2" style={{ color: '#F95F62' }} />
<div className="font-poppins font-semibold text-2xl mb-1" style={{ color: '#F95F62' }}>8</div>
<div className="font-poppins text-xs font-medium text-gray-600">Experiences</div>
</motion.div>
{/* 1 Pass Card */}
<motion.div
className="rounded-2xl p-4 text-center"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.08)' }}
animate={{
boxShadow: [
'0 4px 6px rgba(249, 95, 98, 0.1)',
'0 8px 16px rgba(249, 95, 98, 0.2)',
'0 4px 6px rgba(249, 95, 98, 0.1)'
],
}}
transition={{
duration: 2,
repeat: Infinity,
delay: 0.6,
ease: "easeInOut"
}}
>
<Sparkles className="w-8 h-8 mx-auto mb-2" style={{ color: '#F95F62' }} />
<div className="font-poppins font-semibold text-2xl mb-1" style={{ color: '#F95F62' }}>1</div>
<div className="font-poppins text-xs font-medium text-gray-600">Pass</div>
</motion.div>
</div>
</div>
{/* Price Comparison */}
<div className="max-w-md mx-auto space-y-3 mb-8">
<div className="flex items-center justify-between py-2 border-b border-gray-100">
<span className="font-poppins font-normal text-sm text-gray-600">Individual Tickets Total:</span>
<span className="font-poppins font-semibold text-lg text-gray-900">${totalIndividualPrice}</span>
</div>
<div className="flex justify-center -my-1">
<motion.div
animate={{ y: [0, 5, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
>
<TrendingDown className="w-5 h-5" style={{ color: '#F95F62' }} />
</motion.div>
</div>
<div className="flex items-center justify-between py-2 border-b border-gray-100">
<span className="font-poppins font-normal text-sm text-gray-600">CityCard Price:</span>
<span className="font-poppins font-semibold text-lg" style={{ color: '#F95F62' }}>${cityCardPrice}</span>
</div>
<motion.div
className="flex items-center justify-between py-3 px-4 rounded-xl"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}
animate={{
backgroundColor: ['rgba(249, 95, 98, 0.1)', 'rgba(249, 95, 98, 0.18)', 'rgba(249, 95, 98, 0.1)'],
}}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
>
<div className="flex items-center gap-2">
<DollarSign className="w-5 h-5" style={{ color: '#F95F62' }} />
<span className="font-poppins font-semibold text-sm text-gray-900">You Save:</span>
</div>
<div className="text-right">
<div className="font-poppins font-semibold text-xl" style={{ color: '#F95F62' }}>${savings}</div>
<div className="font-poppins text-xs font-medium" style={{ color: '#F95F62' }}>({savingsPercent}% off)</div>
</div>
</motion.div>
</div>
{/* Attractions Included */}
<div className="mb-6">
<h3 className="font-poppins font-semibold text-gray-900 mb-4 text-center">
What's Included
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 max-w-2xl mx-auto">
{attractions.map((attraction, idx) => (
<div
key={idx}
className="flex items-center justify-between p-2.5 rounded-lg bg-gray-50"
>
<div className="flex items-center gap-2">
<Ticket className="w-4 h-4 text-gray-600" />
<span className="font-poppins text-sm font-normal text-gray-900">{attraction.name}</span>
</div>
<div className="text-right">
<div className="font-poppins text-xs font-medium text-gray-400 line-through">${attraction.price}</div>
</div>
</div>
))}
</div>
</div>
{/* Benefits */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 max-w-2xl mx-auto">
{[
'Skip-the-line access',
'Flexible 3-day validity',
'Digital pass on phone',
'Free 24hr cancellation'
].map((benefit, idx) => (
<div key={idx} className="flex items-center gap-2">
<div className="w-4 h-4 rounded-full flex items-center justify-center" style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}>
<Check className="w-2.5 h-2.5" style={{ color: '#F95F62' }} />
</div>
<span className="font-poppins text-sm font-normal text-gray-700">{benefit}</span>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,357 @@
import { ArrowRight, Hotel, Mail, MapPin, Sparkles, Ticket, Wifi } from 'lucide-react';
import { motion } from 'motion/react';
import { Layout } from '../Layout';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { SmartSaving } from './SmartSaving';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { WhatsIncludedHero } from './WhatsIncludedHero';
interface User {
email: string;
name: string;
}
interface WhatsIncludedProps {
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;
onSuperSavingsClick?: () => void;
onContactUsClick?: () => void;
onEsimsClick?: () => void;
onHotelDiscountsClick?: () => void;
onCreateItineraryClick: () => void;
onViewItineraryClick: () => void;
currentPage: string;
user: User | null;
}
export function WhatsIncluded({
onBackClick,
onHomeClick,
onMelbourneClick,
onPassesClick,
onCheckoutClick,
onSignInClick,
onSignOutClick,
onAttractionsClick,
onBlogsClick,
onHowItWorksClick,
onFAQClick,
onPrivacyPolicyClick,
onAboutUsClick,
onProfileClick,
onCityCardsClick,
onMagicItineraryClick,
onPostCardsClick,
onOffersClick,
onSuperSavingsClick,
onContactUsClick,
onEsimsClick,
onHotelDiscountsClick,
onCreateItineraryClick,
onViewItineraryClick,
currentPage,
user
}: WhatsIncludedProps) {
return (
<Layout
activeCity="Landingpage"
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
>
<div className="min-h-screen bg-background">
{/* Hero Section */}
<WhatsIncludedHero
onCreateItineraryClick={onCreateItineraryClick}
/>
{/* What's Inside Your CityCard Section */}
<section className="py-16 bg-gradient-to-br from-gray-50 to-gray-100">
<div className="container mx-auto px-4">
{/* Section Header */}
<motion.div
className="text-center mb-16 max-w-3xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl leading-tight mb-4">
<span className="font-light text-gray-800">What's Inside Your</span>{' '}
<span className="font-semibold" style={{ color: '#F95F62' }}>CityCard</span>
</h2>
<p className="font-poppins text-base md:text-lg font-normal text-gray-600 leading-relaxed">
Everything you need for an unforgettable journey in one card
</p>
</motion.div>
{/* Features Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
{[
{
icon: <Ticket className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Access to Top Attractions & Tours',
description: 'Skip the lines and explore the best destinations with priority access'
},
{
icon: <Hotel className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Discounts on Select Hotels',
description: 'Save on accommodations with exclusive partner hotel deals'
},
{
icon: <Sparkles className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Magic Itinerary',
description: 'Your auto-curated travel plan tailored to your preferences'
},
{
icon: <Mail className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'Signature Postcard',
description: 'Share your journey with a real postcard, mailed worldwide'
},
{
icon: <Wifi className="w-6 h-6" style={{ color: '#F95F62' }} />,
title: 'CityCards eSIM',
description: 'Stay connected with seamless data connectivity wherever you go'
}
].map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 + index * 0.1 }}
className="group relative bg-white rounded-2xl p-6 shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-gray-200"
>
{/* Icon */}
<div
className="w-14 h-14 rounded-xl flex items-center justify-center mb-4 shadow-sm transition-all duration-300 group-hover:scale-110 group-hover:shadow-md"
style={{ backgroundColor: 'rgba(249, 95, 98, 0.1)' }}
>
{feature.icon}
</div>
{/* Content */}
<div>
<h3 className="font-poppins text-lg md:text-xl font-semibold leading-snug text-gray-900 mb-2">
{feature.title}
</h3>
<p className="font-poppins text-sm md:text-base font-normal leading-relaxed text-gray-600">
{feature.description}
</p>
</div>
{/* Hover accent */}
<div
className="absolute bottom-0 left-0 right-0 h-1 rounded-b-2xl transition-all duration-300 opacity-0 group-hover:opacity-100"
style={{ backgroundColor: '#F95F62' }}
/>
</motion.div>
))}
</div>
</div>
</section>
{/* Benefits Section */}
{/* Featured City - Melbourne Section */}
<section className="py-16 bg-white">
<div className="container mx-auto px-4">
{/* Section Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-center mb-12 max-w-4xl mx-auto"
>
<h2 className="font-poppins text-3xl md:text-4xl lg:text-5xl leading-tight mb-4">
<span className="font-semibold text-gray-800">Featured City</span>
<span className="font-normal text-gray-400 mx-3">—</span>
<span className="font-semibold" style={{ color: '#F95F62' }}>Melbourne</span>
</h2>
<p className="font-poppins text-base md:text-lg font-normal text-gray-600 leading-relaxed max-w-3xl mx-auto">
Explore Melbourne's iconic landmarks, vibrant culture, world-class dining, and hidden gems all included with your Melbourne CityCard
</p>
</motion.div>
{/* Featured Attractions Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
{[
{
id: 1,
name: "Royal Botanic Gardens",
category: "Gardens",
image: "https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zfGVufDF8fHx8MTc1NzMzNzc4OXww&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 2,
name: "Federation Square",
category: "Landmarks",
image: "https://images.unsplash.com/photo-1639655001512-e4b58d4874b8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBmZWRlcmF0aW9uJTIwc3F1YXJlfGVufDF8fHx8MTc1NzMzNzc5Mnww&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 3,
name: "Queen Victoria Market",
category: "Markets",
image: "https://images.unsplash.com/photo-1676454953709-e0be46f62490?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBxdWVlbiUyMHZpY3RvcmlhJTIwbWFya2V0fGVufDF8fHx8MTc1NzMzNzc5Nnww&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 4,
name: "Eureka Skydeck",
category: "Views",
image: "https://images.unsplash.com/photo-1629677713183-29248e1268d7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBldXJla2ElMjB0b3dlcnxlbnwxfHx8fDE3NTczMzc4MDB8MA&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 5,
name: "St Kilda Beach & Pier",
category: "Beach",
image: "https://images.unsplash.com/photo-1674732954456-159835c0a46b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBzdCUyMGtpbGRhJTIwYmVhY2h8ZW58MXx8fHwxNzU3MzM3ODAzfDA&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 6,
name: "Melbourne Laneways",
category: "Street Art",
image: "https://images.unsplash.com/photo-1705120624704-0970afc29fea?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBsYW5ld2F5cyUyMHN0cmVldCUyMGFydHxlbnwxfHx8fDE3NTczMzc4MDd8MA&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 7,
name: "Melbourne Zoo",
category: "Wildlife",
image: "https://images.unsplash.com/photo-1681429477985-30dc7b88dd5b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjB6b28lMjBhbmltYWxzfGVufDF8fHx8MTc1NzMzNzgxMHww&ixlib=rb-4.1.0&q=80&w=1080"
},
{
id: 8,
name: "Royal Exhibition Building",
category: "Heritage",
image: "https://images.unsplash.com/photo-1720523794299-c3b445d71a51?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGV4aGliaXRpb24lMjBidWlsZGluZ3xlbnwxfHx8fDE3NTczMzc4MTR8MA&ixlib=rb-4.1.0&q=80&w=1080"
}
].map((attraction, index) => (
<motion.div
key={attraction.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 + index * 0.05 }}
className="group cursor-pointer"
>
<div className="relative rounded-2xl overflow-hidden shadow-md hover:shadow-2xl transition-all duration-300">
{/* Image */}
<div className="relative h-64 overflow-hidden">
<ImageWithFallback
src={attraction.image}
alt={attraction.name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" />
{/* Category Badge */}
<div className="absolute top-4 right-4">
<Badge
className="border-none font-poppins font-medium text-xs backdrop-blur-sm"
style={{ backgroundColor: 'rgba(255, 255, 255, 0.95)', color: '#F95F62' }}
>
{attraction.category}
</Badge>
</div>
{/* Attraction Name */}
<div className="absolute bottom-0 left-0 right-0 p-5">
<h3 className="font-poppins text-lg md:text-xl font-semibold leading-snug text-white mb-1">
{attraction.name}
</h3>
<p className="font-poppins text-sm font-normal text-white/90">
Melbourne, Australia
</p>
</div>
</div>
</div>
</motion.div>
))}
</div>
{/* View Full List CTA */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
className="text-center"
>
<Button
onClick={onMelbourneClick}
size="lg"
className="font-poppins font-semibold px-8 py-6 text-base hover:shadow-xl transition-all duration-300"
style={{ backgroundColor: '#F95F62' }}
>
View Full List on Melbourne CityCard
<MapPin className="ml-2 w-5 h-5" />
</Button>
<p className="font-poppins text-sm font-normal text-gray-600 mt-4">
Over 40+ attractions included with your Melbourne CityCard
</p>
</motion.div>
</div>
</section>
{/* How It Works Section - Smart Savings */}
<SmartSaving />
{/* 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>
</Layout>
);
}

View File

@@ -0,0 +1,119 @@
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { Sparkles, MapPin, Calendar, Wand2, Clock } from 'lucide-react';
import { ImageWithFallback } from './figma/ImageWithFallback';
interface WhatsIncludedHeroProps {
onCreateItineraryClick?: () => void;
}
interface AttractionCard {
id: number;
name: string;
image: string;
time: string;
category: string;
}
export function WhatsIncludedHero({ onCreateItineraryClick }: WhatsIncludedHeroProps) {
const attractionCards: AttractionCard[] = [
{
id: 1,
name: 'SEA LIFE Melbourne Aquarium',
image: 'https://images.unsplash.com/photo-1696693886265-e73512cdf712?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhcXVhcml1bSUyMHNlYSUyMGxpZmV8ZW58MXx8fHwxNzYyMzI2NDk5fDA&ixlib=rb-4.1.0&q=80&w=1080',
time: '2-3 hours',
category: 'Marine Life'
},
{
id: 2,
name: 'Eureka Skydeck 88',
image: 'https://images.unsplash.com/photo-1720044109127-0aee490512be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxldXJla2ElMjBza3lkZWNrJTIwbWVsYm91cm5lfGVufDF8fHx8MTc2MjMyNjUwMHww&ixlib=rb-4.1.0&q=80&w=1080',
time: '1-2 hours',
category: 'Observation Deck'
},
{
id: 3,
name: 'Royal Botanic Gardens',
image: 'https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zJTIwbWVsYm91cm5lfGVufDF8fHx8MTc2MjMyNjUwMHww&ixlib=rb-4.1.0&q=80&w=1080',
time: '2-4 hours',
category: 'Nature & Parks'
},
{
id: 4,
name: 'Melbourne Zoo',
image: 'https://images.unsplash.com/photo-1681429477985-30dc7b88dd5b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjB6b28lMjBhbmltYWxzfGVufDF8fHx8MTc2MjMyNjUwMXww&ixlib=rb-4.1.0&q=80&w=1080',
time: '3-5 hours',
category: 'Wildlife'
},
{
id: 5,
name: 'National Gallery of Victoria',
image: 'https://images.unsplash.com/photo-1642888619334-55c0179590a3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxuYXRpb25hbCUyMGdhbGxlcnklMjB2aWN0b3JpYXxlbnwxfHx8fDE3NjIzMjY1MDF8MA&ixlib=rb-4.1.0&q=80&w=1080',
time: '2-3 hours',
category: 'Art & Culture'
}
];
const [currentCardIndex, setCurrentCardIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCurrentCardIndex((prev) => (prev + 1) % attractionCards.length);
}, 3000);
return () => clearInterval(timer);
}, [attractionCards.length]);
const currentCard = attractionCards[currentCardIndex];
const nextCard = attractionCards[(currentCardIndex + 1) % attractionCards.length];
const thirdCard = attractionCards[(currentCardIndex + 2) % attractionCards.length];
return (
<div className="relative w-full min-h-[90vh] overflow-hidden flex items-center bg-gradient-to-br from-orange-50 via-white to-rose-50">
{/* Gradient Background Elements */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute top-0 right-0 w-96 h-96 bg-gradient-to-br from-primary/10 to-transparent rounded-full blur-3xl"></div>
<div className="absolute bottom-0 left-0 w-96 h-96 bg-gradient-to-tr from-orange-100/30 to-transparent rounded-full blur-3xl"></div>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-gradient-to-br from-rose-50/50 to-transparent rounded-full blur-3xl"></div>
</div>
{/* Main Content */}
<div className="relative z-10 w-full px-4 pt-32 md:pt-40 pb-16 md:pb-20">
{/* Background Image with White Opacity */}
<div className="absolute inset-0 z-0">
<ImageWithFallback
src="https://images.unsplash.com/photo-1635398039910-53f913ce7847?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhdXN0cmFsaWElMjBhcmNoaXRlY3R1cmV8ZW58MXx8fHwxNzYyNDkyNjI0fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
alt="Melbourne cityscape"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-white/85" />
</div>
<div className="max-w-7xl mx-auto relative z-10">
<div className="flex items-center justify-center min-h-[60vh]">
{/* Centered Content */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-center max-w-4xl"
>
{/* Main Heading */}
<h1 className="font-poppins text-4xl sm:text-5xl md:text-6xl leading-tight mb-6">
<span className="font-light">One pass.</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-primary via-orange-500 to-rose-500 bg-clip-text text-transparent">
Everything you need
</span>{' '}
<span className="font-light">to explore.</span>
</h1>
<p className="font-poppins text-lg md:text-xl font-normal leading-relaxed text-gray-600">
Your CityCard unlocks access to best attractions, experiences, and travel essentials all in one pass.
</p>
</motion.div>
</div>
</div>
</div>
</div>
);
}

5
src/global.d.ts vendored
View File

@@ -27,3 +27,8 @@ declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.mp4' {
const src: string;
export default src;
}