nav changes
This commit is contained in:
11
src/App.tsx
11
src/App.tsx
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
BIN
src/assets/itinenary-animation-vid.mp4
Normal file
BIN
src/assets/itinenary-animation-vid.mp4
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
426
src/components/LandingMagicItineraryPage.tsx
Normal file
426
src/components/LandingMagicItineraryPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
201
src/components/SmartSaving.tsx
Normal file
201
src/components/SmartSaving.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
357
src/components/WhatsIncluded.tsx
Normal file
357
src/components/WhatsIncluded.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
119
src/components/WhatsIncludedHero.tsx
Normal file
119
src/components/WhatsIncludedHero.tsx
Normal 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
5
src/global.d.ts
vendored
@@ -27,3 +27,8 @@ declare module '*.gif' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.mp4' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user