diff --git a/src/App.tsx b/src/App.tsx index 889db73..d3c705b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,9 @@ function App() { // ✅ Authentication state management const [user, setUser] = useState(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() { > 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 */} - - } /> - {/* Melbourne Route */} - - - - } /> - {/* Attractions Routes */} @@ -224,12 +223,19 @@ export function AppRouter({ } /> - - + + } /> + + + + } /> + + @@ -254,18 +260,11 @@ export function AppRouter({ } /> - - - - } /> } /> - - diff --git a/src/Layout.tsx b/src/Layout.tsx index be3f9a9..a48ac14 100644 --- a/src/Layout.tsx +++ b/src/Layout.tsx @@ -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 (
@@ -28,7 +28,7 @@ export function Layout({ {}} - onSignInClick={() => onSignInClick?.()} // ✅ safe optional call + onSignInClick={() => onSignInClick?.()} onSignOutClick={onSignOutClick} isUserSignedIn={!!user} user={user} diff --git a/src/assets/itinenary-animation-vid.mp4 b/src/assets/itinenary-animation-vid.mp4 new file mode 100644 index 0000000..b759537 Binary files /dev/null and b/src/assets/itinenary-animation-vid.mp4 differ diff --git a/src/components/CitySelectionDialog.tsx b/src/components/CitySelectionDialog.tsx index ab51e1d..377e035 100644 --- a/src/components/CitySelectionDialog.tsx +++ b/src/components/CitySelectionDialog.tsx @@ -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 ( - {/* ... rest of the component remains the same ... */} Select a City Choose from our available cities to explore attractions and experiences diff --git a/src/components/FAQPage.tsx b/src/components/FAQPage.tsx index c775873..0bab334 100644 --- a/src/components/FAQPage.tsx +++ b/src/components/FAQPage.tsx @@ -192,7 +192,7 @@ export function FAQPage({
{/* Navigation */} {}} onHomeClick={onHomeClick} onSignInClick={onSignInClick} diff --git a/src/components/HowItWorksPage.tsx b/src/components/HowItWorksPage.tsx index 940ef38..5600757 100644 --- a/src/components/HowItWorksPage.tsx +++ b/src/components/HowItWorksPage.tsx @@ -1,5 +1,6 @@ -import { useState, useEffect, useRef } from 'react'; -import { motion, useInView } from 'motion/react'; +import { useState, useEffect, useRef, useCallback } from 'react'; +import { motion, useInView, AnimatePresence } from 'motion/react'; +import { ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react'; import { Button } from './ui/button'; import Navbar from './Navbar'; import { Footer } from './Footer'; @@ -9,6 +10,371 @@ import { MobileAppSection } from './MobileAppSection'; import { WhyChooseCityCards } from './WhyChooseCityCards'; import { EnhancedTestimonials } from './EnhancedTestimonials'; import { ReviewsSection } from './ReviewsSection'; +import { Layout } from '../Layout'; + +// Hero Carousel Component - Full Screen Pages +function HeroCarousel({ onCheckoutClick, onPassesClick }: { onCheckoutClick: () => void; onPassesClick: () => void }) { + const [currentSlide, setCurrentSlide] = useState(0); + const [direction, setDirection] = useState(0); + + const slides = [ + { + id: 0, + type: 'overview', + title: ( + <> + Your{' '} + + Smart, Seamless + +
+ All-in-One City Pass + + ), + description: "CityCards is your all-in-one solution for exploring cities worldwide. Get instant access to top attractions, skip long queues, and save up to 40% on your adventures—all from your phone.", + showButtons: true, + showStats: true, + gradient: "from-primary/5 via-white to-secondary/5", + image: "https://images.unsplash.com/photo-1517144447511-aebb25bbc5fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwc2t5bGluZSUyMHRyYXZlbHxlbnwxfHx8fDE3NjIxNzAxNTF8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral" + }, + { + id: 1, + type: 'feature', + title: "Instant Access", + subtitle: "QR Code Entry", + description: "Skip the ticket lines with instant QR code access. Just scan your CityCard at 50+ top attractions in every city and walk right in. No printing, no waiting, no hassle.", + icon: ( + + + + ), + gradient: "from-blue-500 to-cyan-500", + bgGradient: "from-blue-50 via-white to-cyan-50", + image: "https://images.unsplash.com/photo-1713685714770-384c5654f6be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0b3VyaXN0JTIwYXR0cmFjdGlvbnMlMjBjaXR5fGVufDF8fHx8MTc2MjI1MjMyNnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", + showButtons: true, + }, + { + id: 2, + type: 'feature', + title: "Save Big", + subtitle: "Up to 40% Off", + description: "Why pay full price? CityCards bundles the best attractions at a fraction of the cost. Save up to 40% compared to buying individual tickets—the more you explore, the more you save.", + icon: ( + + + + ), + gradient: "from-green-500 to-emerald-500", + bgGradient: "from-green-50 via-white to-emerald-50", + image: "https://images.unsplash.com/photo-1650821414031-cf7291ce938c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmF2ZWwlMjB2YWNhdGlvbiUyMHNhdmluZ3MlMjBidWRnZXR8ZW58MXx8fHwxNzYyMjUyNTIxfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", + showButtons: true, + }, + { + id: 3, + type: 'feature', + title: "Magic Itinerary", + subtitle: "AI-Powered Planning", + description: "Let AI be your personal travel assistant. Our Magic Itinerary creates customized daily plans based on your interests, optimizes your route, and adapts in real-time. Smart travel made simple.", + icon: ( + + + + ), + gradient: "from-purple-500 to-pink-500", + bgGradient: "from-purple-50 via-white to-pink-50", + image: "https://images.unsplash.com/photo-1741721816773-ff31d089c227?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbWFydHBob25lJTIwbW9iaWxlJTIwYXBwJTIwdHJhdmVsfGVufDF8fHx8MTc2MjI1MjMyNnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral", + showButtons: true, + } + ]; + + // Auto-advance carousel + useEffect(() => { + const timer = setInterval(() => { + setDirection(1); + setCurrentSlide((prev) => (prev + 1) % slides.length); + }, 6000); // Change slide every 6 seconds + + return () => clearInterval(timer); + }, [slides.length]); + + const nextSlide = () => { + setDirection(1); + setCurrentSlide((prev) => (prev + 1) % slides.length); + }; + + const prevSlide = () => { + setDirection(-1); + setCurrentSlide((prev) => (prev - 1 + slides.length) % slides.length); + }; + + const slideVariants = { + enter: (direction: number) => ({ + x: direction > 0 ? '100%' : '-100%', + opacity: 0 + }), + center: { + zIndex: 1, + x: 0, + opacity: 1 + }, + exit: (direction: number) => ({ + zIndex: 0, + x: direction < 0 ? '100%' : '-100%', + opacity: 0 + }) + }; + + const currentSlideData = slides[currentSlide]; + + return ( +
+ {/* Main Content Container - Split Layout */} +
+ + + {/* Left Side - Content Panel (60% width) */} + +
+ {currentSlideData.type === 'overview' ? ( + // Overview Slide (First Slide) + <> + + {/* Title with Badge Style */} +

+ Your + + Smart + +
+ All-in-One City Pass +

+ + {/* Description */} + + {currentSlideData.description} + +
+ + {/* CTA Buttons */} + {currentSlideData.showButtons && ( + + + + + + + + + )} + + ) : ( + // Feature Slides + <> + {/* Subtitle Badge */} + + + {currentSlideData.subtitle} + + + + {/* Title */} + + {currentSlideData.title} + + + {/* Description */} + + {currentSlideData.description} + + + {/* CTA Buttons */} + {currentSlideData.showButtons && ( + + + + + + + + + )} + + )} +
+
+ + {/* Right Side - Full Height Image (40% width) */} + + + + {/* Subtle overlay for depth */} +
+ + + +
+ + {/* Navigation Controls */} +
+ {/* Arrow Navigation */} + + + {/* Progress Dots */} +
+ {slides.map((_, index) => ( + + ))} +
+ + {/* Arrow Navigation */} + +
+ + {/* Slide Counter */} +
+ + {currentSlide + 1} / {slides.length} + +
+ + {/* Scroll Indicator (only on first slide) */} + {currentSlide === 0 && ( + + + Scroll to explore + + + + )} +
+ ); +} interface User { email: string; @@ -52,39 +418,47 @@ interface Step { const steps: Step[] = [ { number: '1', - title: 'Choose Your Pass', - description: 'Select from our Unlimited or Selective CityCard options. Get instant digital delivery with access to 50+ top Melbourne attractions. Skip the lines and start exploring right away.', + title: 'Choose City', + description: 'Browse our collection of amazing destinations and select the city you want to explore. From vibrant Melbourne to other exciting locations, find your perfect urban adventure.', images: [ - 'https://images.unsplash.com/photo-1567748534085-467f8a8a475d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmF2ZWwlMjB0aWNrZXRzJTIwcGFzc3xlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080', - 'https://images.unsplash.com/photo-1743017784681-ba0cb69e2ead?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtb2JpbGUlMjBwaG9uZSUyMHRyYXZlbCUyMGJvb2tpbmd8ZW58MXx8fHwxNzYxMTE2ODM2fDA&ixlib=rb-4.1.0&q=80&w=1080', - 'https://images.unsplash.com/photo-1677200922658-d0df5b2ac91e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhdHRyYWN0aW9uc3xlbnwxfHx8fDE3NjExMTY4Mzh8MA&ixlib=rb-4.1.0&q=80&w=1080' + 'https://images.unsplash.com/photo-1494522855154-9297ac14b55f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwc2t5bGluZXxlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080', + 'https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwdHJhdmVsfGVufDF8fHx8MTc2MTExNjgzNnww&ixlib=rb-4.1.0&q=80&w=1080', + 'https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwbGFuZG1hcmt8ZW58MXx8fHwxNzYxMTE2ODM4fDA&ixlib=rb-4.1.0&q=80&w=1080' ] }, { number: '2', - title: 'Plan Your Journey', - description: 'Use our AI-powered Magic Itinerary planner to create your perfect Melbourne experience. Get personalized recommendations, optimized routes, and real-time updates tailored to your interests.', + title: 'Get Your Pass', + description: 'Select from our Unlimited or Selective CityCard options. Get instant digital delivery with access to 50+ top attractions. Skip the lines and unlock exclusive benefits right away.', images: [ - 'https://images.unsplash.com/photo-1759802524049-2421ddaee0fe?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwbWFwJTIwcGxhbm5pbmd8ZW58MXx8fHwxNzYxMTE2ODM3fDA&ixlib=rb-4.1.0&q=80&w=1080', - 'https://images.unsplash.com/photo-1741721816773-ff31d089c227?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbWFydHBob25lJTIwYXBwJTIwdHJhdmVsfGVufDF8fHx8MTc2MTExNjgzOHww&ixlib=rb-4.1.0&q=80&w=1080' + 'https://images.unsplash.com/photo-1567748534085-467f8a8a475d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmF2ZWwlMjB0aWNrZXRzJTIwcGFzc3xlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080', + 'https://images.unsplash.com/photo-1743017784681-ba0cb69e2ead?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtb2JpbGUlMjBwaG9uZSUyMHRyYXZlbCUyMGJvb2tpbmd8ZW58MXx8fHwxNzYxMTE2ODM2fDA&ixlib=rb-4.1.0&q=80&w=1080' ] }, { number: '3', - title: 'Explore Melbourne', - description: 'Download the mobile app and start your adventure. Access offline maps, audio guides, and your digital tickets all in one place. Experience Melbourne like never before with 24/7 support at your fingertips.', + title: 'Explore with Magic Itinerary', + description: 'Use our AI-powered Magic Itinerary planner to create your perfect experience. Get personalized recommendations, optimized routes, and real-time updates. Download the app to access offline maps, audio guides, and digital tickets all in one place.', images: [ - 'https://images.unsplash.com/photo-1552333709-c465f89a4dfd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0b3VyaXN0JTIwZXhwbG9yaW5nJTIwY2l0eXxlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080' + 'https://images.unsplash.com/photo-1759802524049-2421ddaee0fe?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwbWFwJTIwcGxhbm5pbmd8ZW58MXx8fHwxNzYxMTE2ODM3fDA&ixlib=rb-4.1.0&q=80&w=1080' ] } ]; // Step Component -function StepSection({ step, index, onInView }: { step: Step; index: number; onInView: (index: number) => void }) { +function StepSection({ + step, + index, + onInView +}: { + step: Step; + index: number; + onInView: (index: number) => void; +}) { const ref = useRef(null); const isInView = useInView(ref, { - margin: '-25% 0px -25% 0px', - amount: 0.3 + margin: '-35% 0px -35% 0px', + amount: 0.5 }); // Notify parent when this step comes into view @@ -98,71 +472,116 @@ function StepSection({ step, index, onInView }: { step: Step; index: number; onI const rotations = [-3, 3, -2]; const rotation = rotations[index]; + // For alternating layout: + // Step 0 (index 0): Content on left, Image on right + // Step 1 (index 1): Content on right, Image on left + // Step 2 (index 2): Content on left, Image on right + const isContentLeft = index % 2 === 0; // Even indices: content left, image right + return ( -
+
-
- {/* Text Content */} +
+ {/* Text Content - Left side for even indices, right side for odd indices */} -
+
+ {/* Pin next to heading - shows when step is active */} + {isInView && ( + +
+
+
+
+
+
+
+ + )} + - {step.number}. + {step.number}. {step.title} {step.description}
- {/* Image Grid with Diagonal Rotation */} + {/* Image Grid with Diagonal Rotation - Right side for even indices, left side for odd indices */} {/* Pin on top */} -
+
-
-
-
-
-
+
+
+
+
+
@@ -177,7 +596,10 @@ function StepSection({ step, index, onInView }: { step: Step; index: number; onI ease: [0.34, 1.56, 0.64, 1] }} style={{ - transformOrigin: 'center center' + transformOrigin: 'center center', + transform: 'translateZ(0)', + backfaceVisibility: 'hidden', + willChange: 'transform' }} whileHover={{ rotate: 0, @@ -186,20 +608,20 @@ function StepSection({ step, index, onInView }: { step: Step; index: number; onI }} > {index === 0 && ( -
-
+
+
-
+
+
-
+
+
- {/* Navigation */} - {}} - onHomeClick={onHomeClick} - onSignInClick={onSignInClick} - onSignOutClick={onSignOutClick} - onPassesClick={onPassesClick} + + {/* Hero Section - Full Carousel */} + {/* Unified How It Works Section with Flight Path */} -
+
{/* Curved Flight Path SVG - Desktop Only */}
+ {/* Define gradients */} + + + + + + + + {/* Main curved flight path - Animated with scroll */} {/* Shadow path for depth - follows main path */} - - {/* 3D Pin marker at the end - only shows when all steps complete */} + + {/* Destination pin at end of path - appears when fully drawn */} {activeStep === 2 && ( {/* Pin shadow */} - {/* Pin body/stick - gradient effect */} - - - {/* Pin head - larger and more prominent */} - - - {/* Pin head border */} - - - {/* Shine effect on pin head */} - - - {/* Inner glow */} - - {/* Checkmark on pin */} - - - {/* Define gradients */} - - - - - - - - - - - )}
-
+
{/* Header */} -

+

How it{' '} works

{/* Steps Container */} -
+
{steps.map((step, index) => ( - {/* Make the most of every attraction section */} - - - {/* Mobile App Section */} - - {/* Why Choose CityCards Section */}
+
+ + {/* Pass Options Overview Section */} +
+
+ +

+ Choose Your + Perfect Pass +

+

+ Flexible options designed to match your travel style +

+
+ +
+ {/* Flexi Card */} + +
+

+ FLEXI CARD +

+

+ Perfect for travelers who want to explore selected attractions at their own pace with essential features. +

+
+ +
    + {[ + 'Access to selected attractions', + 'Limited number of attractions per pass', + 'Flexible validity period', + 'Priority entry where available', + 'Mobile ticket delivery' + ].map((feature) => ( +
  • +
    + + + +
    + {feature} +
  • + ))} +
+ +
+

+ ✓ Free cancellation up to 24 hours • Instant delivery +

+
+ + +
+ + {/* Unlimited Card */} + + {/* Popular Badge */} +
+
+ Most Popular +
+
+ +
+

+ UNLIMITED CARD +

+

+ The ultimate experience for adventure seekers who want unlimited access to all attractions with premium features. +

+
+ +
    + {[ + 'Unlimited access to all attractions', + 'Time-limited validity (7 days)', + 'Skip-the-line access', + 'Expert guide inclusion', + 'Mobile app access', + 'Premium customer support' + ].map((feature) => ( +
  • +
    + + + +
    + {feature} +
  • + ))} +
+ +
+

+ ✓ Free cancellation up to 24 hours • Instant delivery +

+
+ + +
+
+
+
+ +
+ {/* Magic Itinerary Teaser Section */} + {/* Enhanced Testimonials Section */} + {/* Mobile App Section */} + +
+ + {/* CTA Section */} +
+
+
+
+
+ +
+ +

+ Ready to Start Your Adventure? +

+

+ Join millions of travelers who've discovered the smarter way to explore cities +

+ +
+ + +
+
+
+
+ +
{/* Reviews Section */}
-
+ ); } diff --git a/src/components/LandingMagicItinerary.tsx b/src/components/LandingMagicItinerary.tsx index 11dc98a..cb5c0f5 100644 --- a/src/components/LandingMagicItinerary.tsx +++ b/src/components/LandingMagicItinerary.tsx @@ -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 (
- {/* Dynamic City Background */} - - - {/* City Background Image */} -
- -
- - {/* Lightened Multi-layer Gradient Overlay - Reduced opacity to show city */} -
-
-
- - + {/* Dynamic Background */} +
+ {/* Background Image as fallback */} +
+ +
+ + {/* Lightened Multi-layer Gradient Overlay */} +
+
+
+
{/* White Readability Overlay - 42% Opacity */}
- {/* Simplified Decorative Elements - Optimized */} + {/* Simplified Decorative Elements */}
- {/* Single Coral Gradient Blob - Optimized */} + {/* Single Coral Gradient Blob */} - {/* Simplified Floating Icons - Reduced from 8 to 4 */} + {/* Floating Icons */} {[...Array(4)].map((_, i) => ( {/* Header */} -
+
- {/* Card Stack Display */} + {/* Single Video Player Display */}
-
- {/* Card Stack Container */} -
- {/* Visible Card Stack - Third Card */} - -
- -
-
-

- {thirdCard.city} -

-
-
- - - {/* Visible Card Stack - Second Card */} - -
- -
-
-

- {nextCard.city} -

-
-
- - - {/* Animated Front Card */} - - + {/* Main Video Container - Full Height */} +
+
+ {/* Single Video Player - Full Height */} +
); -} +} \ No newline at end of file diff --git a/src/components/LandingMagicItineraryPage.tsx b/src/components/LandingMagicItineraryPage.tsx new file mode 100644 index 0000000..32fa27c --- /dev/null +++ b/src/components/LandingMagicItineraryPage.tsx @@ -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 ( + + + +
+ + {/* Hero Section */} + + + {/* How It Works Section */} + + + {/* Features Section */} +
+
+ {/* Section Header */} + +

+ Smart Features for{' '} + Smart Travelers +

+

+ Experience intelligent trip planning powered by AI technology +

+
+ +
+ {/* Left side - Features */} + + {[ + { + icon: , + title: 'AI-Powered Recommendations', + description: 'Our advanced AI analyzes your preferences to suggest the perfect experiences' + }, + { + icon: , + title: 'Optimized Scheduling', + description: 'Smart timing that considers opening hours, travel time, and crowd patterns' + }, + { + icon: , + title: 'Location-Based Planning', + description: 'Efficiently planned routes that minimize travel time and maximize experiences' + }, + { + icon: , + title: 'Instagram-Worthy Spots', + description: 'Discover hidden gems and perfect photo opportunities along your journey' + } + ].map((feature, index) => ( + + {/* Subtle accent line */} +
+ +
+
+ {feature.icon} +
+
+

+ {feature.title} +

+

+ {feature.description} +

+
+
+ + ))} + + + {/* Right side - Sample Itinerary */} + +
+ {/* Header */} +
+
+
+ +
+

+ Sample Day Itinerary +

+
+

+ A perfectly planned Melbourne adventure +

+
+ + {/* Timeline */} +
+ {/* Vertical line */} +
+ + {[ + { 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) => ( + + {/* Timeline dot */} +
+ {index === 0 &&
} +
+ +
+
+
+ {item.emoji} + + {item.time} + +
+ + {item.duration} + +
+

+ {item.activity} +

+
+ + ))} +
+ + {/* Decorative gradient blur */} +
+
+
+
+
+
+ + {/* Benefits Section */} +
+
+ +

Why Use Magic Itinerary?

+

+ Save time, discover more, and create unforgettable memories with personalized planning +

+
+ +
+ {[ + { + icon: , + title: 'Save Planning Time', + description: 'Skip hours of research. Get a complete itinerary in under 5 minutes.', + stat: '90% faster than manual planning' + }, + { + icon: , + title: 'Discover Hidden Gems', + description: 'Find unique experiences and local favorites you might have missed.', + stat: '50+ curated hidden spots' + }, + { + icon: , + title: 'Personalized Experience', + description: 'Every itinerary is unique, tailored specifically to your preferences.', + stat: '1000+ possible combinations' + } + ].map((benefit, index) => ( + + + +
+
+ {benefit.icon} +
+
+

+ {benefit.title} +

+

+ {benefit.description} +

+ + {benefit.stat} + +
+
+
+ ))} +
+
+
+ + {/* What's Included Section */} +
+
+ +

+ Everything You Need,{' '} + All Included +

+

+ Your Magic Itinerary comes with comprehensive features for an amazing Melbourne experience +

+
+ +
+ {[ + { + 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) => ( + +
+
+
+ {item.emoji} +
+

+ {item.title} +

+

+ {item.description} +

+
+
+
+ ))} +
+
+
+ + {/* CTA Section */} + + + {/* Mobile App Section */} + + + {/* Customer Reviews */} + + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/MagicItineraryPage.tsx b/src/components/MagicItineraryPage.tsx index e13aa68..45db6da 100644 --- a/src/components/MagicItineraryPage.tsx +++ b/src/components/MagicItineraryPage.tsx @@ -77,14 +77,14 @@ export function MagicItineraryPage({
{/* Navbar */} + activeCity="Melbourne" + onSignInClick={onSignInClick} + onSignOutClick={onSignOutClick} + user={user} + > - {/* Sub Navbar */} - {/* */} - {/* Hero Section */} -
- {/* Background gradient */} -
- -
- -

- Plan Your Perfect{' '} - - Melbourne Adventure - -

-

- 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. -

- -
-
+ {/* Hero Section */} +
+ {/* Background gradient */} +
- {/* Decorative elements */} -
-
-
- - {/* How It Works Section */} - - - {/* Features Section */} -
-
-
- {/* Left side - Features */} +
-

- Smart Features for
- Smart Travelers -

- -
- {[ - { - icon: , - title: 'AI-Powered Recommendations', - description: 'Our advanced AI analyzes your preferences to suggest the perfect experiences' - }, - { - icon: , - title: 'Optimized Scheduling', - description: 'Smart timing that considers opening hours, travel time, and crowd patterns' - }, - { - icon: , - title: 'Location-Based Planning', - description: 'Efficiently planned routes that minimize travel time and maximize experiences' - }, - { - icon: , - title: 'Instagram-Worthy Spots', - description: 'Discover hidden gems and perfect photo opportunities along your journey' - } - ].map((feature, index) => ( - -
- {feature.icon} -
-
-

{feature.title}

-

{feature.description}

-
-
- ))} -
+

+ Plan Your Perfect{' '} + + Melbourne Adventure + +

+

+ 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. +

+
+
- {/* Right side - Visual */} - -
-

Sample Itinerary

-
+ {/* Decorative elements */} +
+
+
+ + {/* How It Works Section */} + + + {/* Features Section */} +
+
+
+ {/* Left side - Features */} + +

+ Smart Features for
+ Smart Travelers +

+ +
{[ - { 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) => ( -
-
- {item.time} + { + icon: , + title: 'AI-Powered Recommendations', + description: 'Our advanced AI analyzes your preferences to suggest the perfect experiences' + }, + { + icon: , + title: 'Optimized Scheduling', + description: 'Smart timing that considers opening hours, travel time, and crowd patterns' + }, + { + icon: , + title: 'Location-Based Planning', + description: 'Efficiently planned routes that minimize travel time and maximize experiences' + }, + { + icon: , + title: 'Instagram-Worthy Spots', + description: 'Discover hidden gems and perfect photo opportunities along your journey' + } + ].map((feature, index) => ( + +
+ {feature.icon}
-
-
{item.activity}
-
{item.duration}
+
+

{feature.title}

+

{feature.description}

-
+
))}
-
+ + + {/* Right side - Visual */} + +
+

Sample Itinerary

+
+ {[ + { 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) => ( +
+
+ {item.time} +
+
+
{item.activity}
+
{item.duration}
+
+
+ ))} +
+
+
+
+
+
+ + {/* Benefits Section */} +
+
+ +

Why Use Magic Itinerary?

+

+ Save time, discover more, and create unforgettable memories with personalized planning +

+ +
+ {[ + { + icon: , + title: 'Save Planning Time', + description: 'Skip hours of research. Get a complete itinerary in under 5 minutes.', + stat: '90% faster than manual planning' + }, + { + icon: , + title: 'Discover Hidden Gems', + description: 'Find unique experiences and local favorites you might have missed.', + stat: '50+ curated hidden spots' + }, + { + icon: , + title: 'Personalized Experience', + description: 'Every itinerary is unique, tailored specifically to your preferences.', + stat: '1000+ possible combinations' + } + ].map((benefit, index) => ( + + + +
+ {benefit.icon} +
+

{benefit.title}

+

{benefit.description}

+ + {benefit.stat} + +
+
+
+ ))} +
-
-
+
- {/* Benefits Section */} -
-
- -

Why Use Magic Itinerary?

-

- Save time, discover more, and create unforgettable memories with personalized planning -

-
+ {/* What's Included Section */} +
+
+ +

What's Included

+

+ Your Magic Itinerary comes with everything you need for an amazing Melbourne experience +

+
-
- {[ - { - icon: , - title: 'Save Planning Time', - description: 'Skip hours of research. Get a complete itinerary in under 5 minutes.', - stat: '90% faster than manual planning' - }, - { - icon: , - title: 'Discover Hidden Gems', - description: 'Find unique experiences and local favorites you might have missed.', - stat: '50+ curated hidden spots' - }, - { - icon: , - title: 'Personalized Experience', - description: 'Every itinerary is unique, tailored specifically to your preferences.', - stat: '1000+ possible combinations' - } - ].map((benefit, index) => ( - - - -
- {benefit.icon} -
-

{benefit.title}

-

{benefit.description}

- - {benefit.stat} - -
-
-
- ))} +
+ {[ + { + 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) => ( + + + +

{item.title}

+

{item.description}

+
+
+
+ ))} +
-
-
+
- {/* What's Included Section */} -
-
- -

What's Included

-

- Your Magic Itinerary comes with everything you need for an amazing Melbourne experience -

-
- -
- {[ - { - 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) => ( - - - -

{item.title}

-

{item.description}

-
-
-
- ))} -
-
-
- - {/* CTA Section */} + {/* CTA Section */} - {/* Mobile App Section */} - + {/* Mobile App Section */} + - {/* Customer Reviews */} - + {/* Customer Reviews */} + - - + +
); } \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index e09f9d9..cda5985 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -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(null); const cartRef = useRef(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: 🇺🇸 }, @@ -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 && (
@@ -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({ > - {/* Navigation Items based on page type */} - {navigationItems.map((item) => ( - - {item.label} - {/* Active indicator */} - - {/* Hover background */} - - - ))} + {/* Enhanced Navigation Items with source tracking */} + {navigationItems.map((item) => { + const source = getNavigationSource(item); + + return ( +
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 */} + + {/* Hover background */} + +
+ ); + })}
{/* Right Section */}
- {/* City Dropdown */} - { - 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 */} +
+ + {activeCity && + activeCity !== 'shared' && + activeCity !== 'Landingpage' && + activeCity.toLowerCase() !== 'landingpage' ? + activeCity.charAt(0).toUpperCase() + activeCity.slice(1) : + 'Select City' } - }))} - title="Select City" - trigger={ -
- - {isLandingPage || activeCity === 'Landingpage' || !activeCity - ? 'Select City' - : activeCity.charAt(0).toUpperCase() + activeCity.slice(1) - } - - -
- } - /> +
+ +
{/* Language Dropdown */} } /> - {/* ✅ UPDATED: City Card Button with Proper Authentication Logic */} + + {/* Enhanced City Card Button with Source Tracking */}
{isUserSignedIn && user ? ( - // ✅ When user is logged in - show user dropdown { }} // Empty function since we handle click above + onClick={() => { }} className="hover:scale-105 transition-transform duration-200" />
} /> ) : ( - // ✅ When user is NOT logged in - show city selection dialog - <> -
- { }} // Empty function since we handle click above - className="hover:scale-105 transition-transform duration-200" - /> -
- - {/* ✅ City Selection Dialog with navigation to passes */} - + { }} + className="hover:scale-105 transition-transform duration-200" /> - +
)}
@@ -548,7 +674,6 @@ export default function Navbar({
- {/* Mobile Navbar - Enhanced Glassmorphism */} - {/* Mobile Menu Overlay - Enhanced Glassmorphism */} + {/* Enhanced Mobile Menu Overlay */} {isMobileMenuOpen && ( <> @@ -658,23 +783,27 @@ export default function Navbar({
- {/* Navigation Links based on page type */} + {/* Enhanced Navigation Links with source tracking */}

Navigation

- {navigationItems.map((item) => ( - 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 }} - > - {item.label} - - ))} + {navigationItems.map((item) => { + const source = getNavigationSource(item); + + return ( + 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 }} + > + {item.displayLabel} + + ); + })}
{/* City Selection in Mobile Menu */} @@ -683,7 +812,7 @@ export default function Navbar({ {cities.map((city) => ( 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 */} + +

+ + Free • Takes less than 2 minutes

-
+ - - {/* CTA Button */} - - - +
+ {/* Card Stack Container */} +
+ {/* Third Card (Background) */} + +
+ +
+
+ + + {/* Second Card */} + +
+ +
+
+ + + {/* Front Card (Animated) */} + + + {/* Card Image */} +
+ +
+ + {/* Category Badge */} + + + {currentCard.category} + + + + {/* Time Badge */} + +
+ + {currentCard.time} +
+
+ + {/* Attraction Name Overlay */} + +

+ {currentCard.name} +

+

+ + Melbourne, Australia +

+
+
+ + {/* Card Content */} + +
+
+
+ + {currentCardIndex + 1} + +
+ + Day {currentCardIndex + 1} Activity + +
+
+ + AI Selected +
+
+ +
+

+ Added to your personalized itinerary based on your preferences and travel style +

+
+
+ + +
+
+ + {/* Card Indicators */} + + {attractionCards.map((card, idx) => ( +
diff --git a/src/components/SmartSaving.tsx b/src/components/SmartSaving.tsx new file mode 100644 index 0000000..2ee5082 --- /dev/null +++ b/src/components/SmartSaving.tsx @@ -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 ( +
+
+ {/* Header */} +
+
+
+ + Maximum Value + +
+

+ Smart Savings +

+

+ Save up to 40% compared to buying individual tickets +

+
+ + {/* Main Content */} +
+
+ + {/* Hero Tagline Cards */} +
+
+ {/* 3 Days Card */} + + +
3
+
Days
+
+ + {/* 8 Experiences Card */} + + +
8
+
Experiences
+
+ + {/* 1 Pass Card */} + + +
1
+
Pass
+
+
+
+ + {/* Price Comparison */} +
+
+ Individual Tickets Total: + ${totalIndividualPrice} +
+ +
+ + + +
+ +
+ CityCard Price: + ${cityCardPrice} +
+ + +
+ + You Save: +
+
+
${savings}
+
({savingsPercent}% off)
+
+
+
+ + {/* Attractions Included */} +
+

+ What's Included +

+
+ {attractions.map((attraction, idx) => ( +
+
+ + {attraction.name} +
+
+
${attraction.price}
+
+
+ ))} +
+
+ + {/* Benefits */} +
+ {[ + 'Skip-the-line access', + 'Flexible 3-day validity', + 'Digital pass on phone', + 'Free 24hr cancellation' + ].map((benefit, idx) => ( +
+
+ +
+ {benefit} +
+ ))} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/WhatsIncluded.tsx b/src/components/WhatsIncluded.tsx new file mode 100644 index 0000000..afc1307 --- /dev/null +++ b/src/components/WhatsIncluded.tsx @@ -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 ( + +
+ + {/* Hero Section */} + + + {/* What's Inside Your CityCard Section */} +
+
+ {/* Section Header */} + +

+ What's Inside Your{' '} + CityCard +

+

+ Everything you need for an unforgettable journey in one card +

+
+ + {/* Features Grid */} +
+ {[ + { + icon: , + title: 'Access to Top Attractions & Tours', + description: 'Skip the lines and explore the best destinations with priority access' + }, + { + icon: , + title: 'Discounts on Select Hotels', + description: 'Save on accommodations with exclusive partner hotel deals' + }, + { + icon: , + title: 'Magic Itinerary', + description: 'Your auto-curated travel plan tailored to your preferences' + }, + { + icon: , + title: 'Signature Postcard', + description: 'Share your journey with a real postcard, mailed worldwide' + }, + { + icon: , + title: 'CityCards eSIM', + description: 'Stay connected with seamless data connectivity wherever you go' + } + ].map((feature, index) => ( + + {/* Icon */} +
+ {feature.icon} +
+ + {/* Content */} +
+

+ {feature.title} +

+

+ {feature.description} +

+
+ + {/* Hover accent */} +
+ + ))} +
+
+
+ + {/* Benefits Section */} + {/* Featured City - Melbourne Section */} +
+
+ {/* Section Header */} + +

+ Featured City + + Melbourne +

+

+ Explore Melbourne's iconic landmarks, vibrant culture, world-class dining, and hidden gems – all included with your Melbourne CityCard +

+
+ + {/* Featured Attractions Grid */} +
+ {[ + { + 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) => ( + +
+ {/* Image */} +
+ + {/* Gradient Overlay */} +
+ + {/* Category Badge */} +
+ + {attraction.category} + +
+ + {/* Attraction Name */} +
+

+ {attraction.name} +

+

+ Melbourne, Australia +

+
+
+
+ + ))} +
+ + {/* View Full List CTA */} + + +

+ Over 40+ attractions included with your Melbourne CityCard +

+
+
+
+ + {/* How It Works Section - Smart Savings */} + + + + {/* CTA Section */} +
+
+
+
+
+ +
+ +

+ Ready to Start Your Adventure? +

+

+ Join millions of travelers who've discovered the smarter way to explore cities +

+ +
+ + +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/WhatsIncludedHero.tsx b/src/components/WhatsIncludedHero.tsx new file mode 100644 index 0000000..6f8c230 --- /dev/null +++ b/src/components/WhatsIncludedHero.tsx @@ -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 ( +
+ {/* Gradient Background Elements */} +
+
+
+
+
+ + {/* Main Content */} +
+ {/* Background Image with White Opacity */} +
+ +
+
+ +
+
+ {/* Centered Content */} + + {/* Main Heading */} +

+ One pass.{' '} + + Everything you need + {' '} + to explore. +

+ +

+ Your CityCard unlocks access to best attractions, experiences, and travel essentials — all in one pass. +

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts index f7f59ba..903f3f8 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -27,3 +27,8 @@ declare module '*.gif' { const src: string; export default src; } + +declare module '*.mp4' { + const src: string; + export default src; +}