808 lines
38 KiB
TypeScript
808 lines
38 KiB
TypeScript
import { motion, useAnimationControls, AnimatePresence } from 'motion/react';
|
|
import { Button } from './ui/button';
|
|
import { ArrowRight, Calendar, Thermometer, Eye, MapPin, Clock, Users, Ticket, Wand2, Plane, Sparkles } from 'lucide-react';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import Navbar from './Navbar';
|
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
|
import { MelbourneAttractions } from './MelbourneAttractions';
|
|
import { MelbourneCardComparison } from './MelbourneCardComparison';
|
|
import { MelbourneTourOverview } from './MelbourneTourOverview';
|
|
import { MelbourneBlogs } from './MelbourneBlogs';
|
|
import { CustomPostcards } from './CustomPostcards';
|
|
import { EnhancedTestimonials } from './EnhancedTestimonials';
|
|
import { MobileAppPromotion } from './MobileAppPromotion';
|
|
import { MelbourneFAQ } from './MelbourneFAQ';
|
|
import { Footer } from './Footer';
|
|
// import { MinimalHeroBanner } from './MinimalHeroBanner';
|
|
import { Layout } from '../Layout';
|
|
import { HeroBannerCarousel } from './HeroBannerCarousel';
|
|
import { HotelEsimOffers } from './HotelEsimOffers';
|
|
|
|
interface User {
|
|
email: string;
|
|
name: string;
|
|
}
|
|
|
|
interface MelbournePageProps {
|
|
onBackClick: () => void;
|
|
onHomeClick: () => void;
|
|
onAttractionsClick: () => void;
|
|
onPassesClick: () => void;
|
|
onCheckoutClick?: () => void;
|
|
onSignInClick: () => void;
|
|
onSignOutClick?: () => void;
|
|
onBlogsClick: () => void;
|
|
onHowItWorksClick: () => void;
|
|
onFAQClick: () => void;
|
|
onPrivacyPolicyClick: () => void;
|
|
onAboutUsClick: () => void;
|
|
onProfileClick?: () => void;
|
|
onCityCardsClick?: () => void;
|
|
onMagicItineraryClick?: () => void;
|
|
onSuperSavingsClick?: () => void;
|
|
onPostCardsClick?: () => void;
|
|
onOffersClick?: () => void;
|
|
onEsimsClick?: () => void;
|
|
onHotelDiscountsClick?: () => void;
|
|
onContactUsClick?: () => void;
|
|
onCreateItineraryClick?: () => void;
|
|
currentPage?: string;
|
|
user?: User | null;
|
|
}
|
|
|
|
interface ItineraryCard {
|
|
id: number;
|
|
city: string;
|
|
country: string;
|
|
days: number;
|
|
image: string;
|
|
highlights: string[];
|
|
activities: { name: string; time: string }[];
|
|
}
|
|
|
|
const itineraryCards: ItineraryCard[] = [
|
|
{
|
|
id: 1,
|
|
city: 'Melbourne',
|
|
country: 'Australia',
|
|
days: 3,
|
|
image: 'https://images.unsplash.com/photo-1720044282356-e319c686d209?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBjaXR5JTIwbGFuZG1hcmt8ZW58MXx8fHwxNzY5MTUwMzA5fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
|
|
highlights: ['Coffee Culture', 'Street Art', 'Rooftop Bars'],
|
|
activities: [
|
|
{ name: 'Breakfast at Higher Ground', time: '09:00 AM' },
|
|
{ name: 'Explore Hosier Lane', time: '11:00 AM' },
|
|
{ name: 'Royal Botanic Gardens', time: '02:00 PM' },
|
|
{ name: 'Sunset at Eureka Skydeck', time: '06:00 PM' }
|
|
]
|
|
},
|
|
{
|
|
id: 2,
|
|
city: 'Sydney',
|
|
country: 'Australia',
|
|
days: 4,
|
|
image: 'https://images.unsplash.com/photo-1523059623039-a9ed027e7fad?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBvcGVyYSUyMGhvdXNlfGVufDF8fHx8MTc2OTE1MDMwOXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
|
|
highlights: ['Opera House', 'Bondi Beach', 'Harbour Bridge'],
|
|
activities: [
|
|
{ name: 'Opera House Tour', time: '10:00 AM' },
|
|
{ name: 'Walk the Harbour Bridge', time: '01:00 PM' },
|
|
{ name: 'Ferry to Manly', time: '03:30 PM' },
|
|
{ name: 'Dinner at The Rocks', time: '07:00 PM' }
|
|
]
|
|
},
|
|
{
|
|
id: 3,
|
|
city: 'Gold Coast',
|
|
country: 'Australia',
|
|
days: 5,
|
|
image: 'https://images.unsplash.com/photo-1611473247871-8a4ef64d17ee?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxHb2xkJTIwQ29hc3QlMjBiZWFjaCUyMGF1c3RyYWxpYXxlbnwxfHx8fDE3NjkxNTAzMDl8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
|
|
highlights: ['Surfers Paradise', 'Theme Parks', 'Rainforest'],
|
|
activities: [
|
|
{ name: 'Surf Lesson', time: '08:00 AM' },
|
|
{ name: 'Q1 Observation Deck', time: '12:00 PM' },
|
|
{ name: 'Currumbin Sanctuary', time: '02:00 PM' },
|
|
{ name: 'Sunset Cruise', time: '05:30 PM' }
|
|
]
|
|
},
|
|
{
|
|
id: 4,
|
|
city: 'Cairns',
|
|
country: 'Australia',
|
|
days: 4,
|
|
image: 'https://images.unsplash.com/photo-1710171781154-678ede6f39a3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxDYWlybnMlMjBncmVhdCUyMGJhcnJpZXIlMjByZWVmfGVufDF8fHx8MTc2OTE1MDMwOXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
|
|
highlights: ['Great Barrier Reef', 'Daintree', 'Esplanade'],
|
|
activities: [
|
|
{ name: 'Reef Snorkeling', time: '09:00 AM' },
|
|
{ name: 'Lunch on the Boat', time: '01:00 PM' },
|
|
{ name: 'Esplanade Lagoon', time: '04:00 PM' },
|
|
{ name: 'Night Market', time: '07:00 PM' }
|
|
]
|
|
}
|
|
];
|
|
|
|
export function MelbournePage({
|
|
onBackClick,
|
|
onHomeClick,
|
|
onAttractionsClick,
|
|
onPassesClick,
|
|
onCheckoutClick,
|
|
onSignInClick,
|
|
onSignOutClick,
|
|
onBlogsClick,
|
|
onHowItWorksClick,
|
|
onFAQClick,
|
|
onPrivacyPolicyClick,
|
|
onAboutUsClick,
|
|
onProfileClick,
|
|
onCityCardsClick,
|
|
onMagicItineraryClick,
|
|
onSuperSavingsClick,
|
|
onPostCardsClick,
|
|
onOffersClick,
|
|
onEsimsClick,
|
|
onHotelDiscountsClick,
|
|
onContactUsClick,
|
|
onCreateItineraryClick,
|
|
currentPage,
|
|
user
|
|
}: MelbournePageProps) {
|
|
// Magic Itinerary state
|
|
const [currentCardIndex, setCurrentCardIndex] = useState(0);
|
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
|
|
const currentCard = itineraryCards[currentCardIndex];
|
|
const nextCard = itineraryCards[(currentCardIndex + 1) % itineraryCards.length];
|
|
const thirdCard = itineraryCards[(currentCardIndex + 2) % itineraryCards.length];
|
|
|
|
return (
|
|
<div className="min-h-screen bg-white">
|
|
{/* Navigation */}
|
|
<Layout
|
|
activeCity="Melbourne"
|
|
onSignInClick={onSignInClick}
|
|
onSignOutClick={onSignOutClick}
|
|
user={user}
|
|
>
|
|
|
|
{/* Hero Banner Carousel */}
|
|
<HeroBannerCarousel
|
|
onCheckoutClick={onCheckoutClick}
|
|
onPassesClick={onPassesClick}
|
|
onEsimsClick={onEsimsClick}
|
|
onHotelDiscountsClick={onHotelDiscountsClick}
|
|
/>
|
|
|
|
{/* Main Content */}
|
|
<main className="bg-gray-50/30 min-h-screen relative">
|
|
{/* Sticky Page Navigation */}
|
|
<div className="sticky top-[78px] lg:top-[94px] z-40 bg-white/80 backdrop-blur-xl border-b border-gray-100 shadow-sm">
|
|
<div className="container mx-auto px-4">
|
|
{/* horizontal scroll wrapper */}
|
|
<div className="overflow-x-auto no-scrollbar">
|
|
{/* actual flex row */}
|
|
<div className="flex items-center justify-center gap-2 py-3 min-w-max">
|
|
{[
|
|
{ id: 'overview', label: 'Overview', icon: MapPin },
|
|
{ id: 'attractions', label: 'Attractions', icon: Eye },
|
|
{ id: 'passes', label: 'City Pass', icon: Ticket },
|
|
{ id: 'tours', label: 'Tours', icon: Plane },
|
|
{ id: 'itinerary', label: 'Itinerary', icon: Wand2 },
|
|
{ id: 'faq', label: 'FAQ', icon: Sparkles }
|
|
].map((item) => (
|
|
<button
|
|
key={item.id}
|
|
onClick={() =>
|
|
document
|
|
.getElementById(item.id)
|
|
?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
}
|
|
className="flex items-center gap-2 px-5 py-2.5 rounded-full text-sm font-poppins font-medium
|
|
text-gray-600 hover:text-primary hover:bg-primary/5 transition-all whitespace-nowrap
|
|
group border border-transparent hover:border-primary/10"
|
|
>
|
|
<item.icon className="w-4 h-4 text-gray-400 group-hover:text-primary transition-colors" />
|
|
{item.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div className="container mx-auto px-4 py-12 space-y-24">
|
|
{/* Features Grid */}
|
|
<motion.section
|
|
id="overview"
|
|
className="grid md:grid-cols-3 gap-8 scroll-mt-32"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
>
|
|
{[
|
|
{
|
|
title: "50+ Top Attractions",
|
|
description: "Unlimited access to Melbourne's finest museums, zoos, and observation decks.",
|
|
icon: MapPin,
|
|
color: "text-blue-500",
|
|
bg: "bg-blue-50"
|
|
},
|
|
{
|
|
title: "Instant Digital Entry",
|
|
description: "Skip the ticket lines with a simple scan of your mobile pass.",
|
|
icon: Ticket,
|
|
color: "text-primary",
|
|
bg: "bg-primary/10"
|
|
},
|
|
{
|
|
title: "Save over 50%",
|
|
description: "Enjoy massive savings compared to buying individual attraction tickets.",
|
|
icon: Sparkles,
|
|
color: "text-emerald-500",
|
|
bg: "bg-emerald-50"
|
|
}
|
|
].map((feature, index) => (
|
|
<motion.div
|
|
key={feature.title}
|
|
className="flex flex-col items-center text-center p-8 bg-white rounded-[2rem] shadow-[0_2px_20px_-4px_rgba(0,0,0,0.05)] border border-gray-100 hover:shadow-xl hover:border-primary/20 transition-all duration-300 group cursor-default"
|
|
whileHover={{ y: -5 }}
|
|
>
|
|
<div className={`w-20 h-20 rounded-2xl ${feature.bg} flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-500 rotate-3 group-hover:rotate-6`}>
|
|
<feature.icon className={`w-8 h-8 ${feature.color}`} />
|
|
</div>
|
|
<h3 className="font-merchant text-3xl text-gray-900 mb-3">{feature.title}</h3>
|
|
<p className="font-poppins text-gray-500 font-light leading-relaxed">{feature.description}</p>
|
|
</motion.div>
|
|
))}
|
|
</motion.section>
|
|
|
|
{/* Attractions Section */}
|
|
<div id="attractions" className="scroll-mt-32">
|
|
<MelbourneAttractions />
|
|
</div>
|
|
|
|
{/* Pass Comparison */}
|
|
<div id="passes" className="scroll-mt-32">
|
|
<MelbourneCardComparison />
|
|
</div>
|
|
|
|
{/* Tour Overview */}
|
|
<div id="tours" className="scroll-mt-32">
|
|
<MelbourneTourOverview />
|
|
</div>
|
|
|
|
{/* Hotel & eSIM Offers */}
|
|
<div id="offers" className="scroll-mt-32">
|
|
<HotelEsimOffers
|
|
onEsimsClick={onEsimsClick}
|
|
onHotelDiscountsClick={onHotelDiscountsClick}
|
|
/>
|
|
</div>
|
|
|
|
{/* Blogs */}
|
|
<div id="blogs" className="scroll-mt-32">
|
|
<MelbourneBlogs />
|
|
</div>
|
|
|
|
{/* Custom Postcards */}
|
|
<CustomPostcards />
|
|
</div>
|
|
|
|
{/* Magic Itinerary Section */}
|
|
<div id="itinerary" className="scroll-mt-10">
|
|
<section className="relative py-20 lg:py-32 overflow-hidden -mt-20 pt-32 z-0">
|
|
{/* 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>
|
|
|
|
{/* White Readability Overlay - 42% Opacity */}
|
|
<div className="absolute inset-0 bg-white/42 pointer-events-none z-[10]" />
|
|
|
|
{/* Simplified Decorative Elements - Optimized */}
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none z-[15]">
|
|
{/* Single Coral Gradient Blob - Optimized */}
|
|
<motion.div
|
|
className="absolute top-20 -left-20 w-[500px] h-[500px] bg-gradient-to-br from-primary/20 via-orange-400/10 to-transparent rounded-full blur-3xl will-change-transform"
|
|
animate={{
|
|
scale: [1, 1.15, 1],
|
|
x: [0, 30, 0],
|
|
}}
|
|
transition={{ duration: 15, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
|
|
{/* Simplified Floating Icons - Reduced from 8 to 4 */}
|
|
{[...Array(4)].map((_, i) => (
|
|
<motion.div
|
|
key={i}
|
|
className="absolute will-change-transform"
|
|
style={{
|
|
top: `${25 + (i * 20)}%`,
|
|
left: `${15 + (i * 20)}%`,
|
|
}}
|
|
animate={{
|
|
y: [0, -20, 0],
|
|
opacity: [0.15, 0.35, 0.15],
|
|
}}
|
|
transition={{
|
|
duration: 6 + i * 2,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
delay: i * 0.8,
|
|
}}
|
|
>
|
|
{i % 2 === 0 ? (
|
|
<Plane className="w-6 h-6 text-primary" />
|
|
) : (
|
|
<MapPin className="w-6 h-6 text-primary" />
|
|
)}
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="container mx-auto px-4 relative z-20 flex flex-col items-center">
|
|
{/* Header */}
|
|
<div className="text-center mb-20 max-w-5xl w-full">
|
|
<motion.div
|
|
className="inline-flex items-center gap-3 bg-gradient-to-r from-primary/10 to-orange-100/50 backdrop-blur-sm px-6 py-3 rounded-full border-2 border-primary/30 shadow-xl mb-8"
|
|
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
|
whileInView={{ opacity: 1, scale: 1, y: 0 }}
|
|
transition={{ duration: 0.7, ease: [0.34, 1.56, 0.64, 1] }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<motion.div
|
|
animate={{
|
|
rotate: [0, 20, -20, 0],
|
|
scale: [1, 1.2, 1.2, 1],
|
|
}}
|
|
transition={{ duration: 2, repeat: Infinity }}
|
|
>
|
|
<Wand2 className="w-6 h-6 text-primary drop-shadow-lg" />
|
|
</motion.div>
|
|
<span className="font-poppins font-semibold text-gray-800">AI-Powered Magic Itinerary</span>
|
|
<motion.div
|
|
className="w-2 h-2 bg-primary rounded-full"
|
|
animate={{
|
|
scale: [1, 1.5, 1],
|
|
opacity: [1, 0.5, 1],
|
|
}}
|
|
transition={{ duration: 1.5, repeat: Infinity }}
|
|
/>
|
|
</motion.div>
|
|
|
|
<motion.h2
|
|
className="font-poppins text-4xl md:text-5xl lg:text-6xl mb-8 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] }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<span className="font-light">Plan Your</span>{' '}
|
|
<span className="font-bold italic bg-gradient-to-r from-primary via-orange-500 to-rose-500 bg-clip-text text-transparent drop-shadow-lg pr-2">
|
|
Dream Journey
|
|
</span>
|
|
<br />
|
|
<span className="font-normal">in Just</span>{' '}
|
|
<span className="font-bold text-primary">3 Seconds</span>
|
|
<motion.span
|
|
className="inline-block ml-2"
|
|
animate={{
|
|
rotate: [0, 10, -10, 0],
|
|
y: [0, -5, 0],
|
|
}}
|
|
transition={{ duration: 2, repeat: Infinity, delay: 0.5 }}
|
|
>
|
|
✨
|
|
</motion.span>
|
|
</motion.h2>
|
|
|
|
<motion.p
|
|
className="font-poppins text-xl md:text-2xl text-gray-700 leading-relaxed max-w-3xl mx-auto"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.3 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
Our AI creates <span className="font-semibold text-primary">personalized itineraries</span> with
|
|
perfectly timed activities, optimized routes, and <span className="font-semibold text-primary">curated experiences</span> tailored
|
|
just for you.
|
|
</motion.p>
|
|
</div>
|
|
|
|
{/* Card Stack 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="font-poppins 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="font-poppins 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],
|
|
}}
|
|
>
|
|
{/* 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"
|
|
/>
|
|
<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="font-poppins text-3xl lg:text-4xl font-bold text-white mb-2">
|
|
{currentCard.city}
|
|
</h3>
|
|
<p className="font-poppins 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-primary 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-poppins 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-primary" />
|
|
</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 }}
|
|
>
|
|
{/* 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="font-poppins px-3 py-1 bg-gradient-to-r from-primary/15 to-orange-100 text-primary border border-primary/20 rounded-full text-xs font-semibold shadow-sm transition-transform hover:scale-105"
|
|
>
|
|
{highlight}
|
|
</span>
|
|
))}
|
|
</motion.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-primary to-orange-500 rounded-full shadow-md">
|
|
<Clock className="w-3 h-3 text-white" />
|
|
<span className="font-poppins text-xs font-bold text-white">Day 1</span>
|
|
</div>
|
|
<div className="flex-1 h-px bg-gradient-to-r from-primary/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-primary/10 hover:border-primary/30 hover:shadow-md transition-all group hover:translate-x-0.5"
|
|
>
|
|
{idx < 1 && (
|
|
<div className="absolute left-4 top-full h-2 w-0.5 bg-gradient-to-b from-primary/50 to-transparent" />
|
|
)}
|
|
|
|
<div className="relative flex-shrink-0">
|
|
<div className="w-7 h-7 bg-gradient-to-br from-primary to-orange-500 rounded-full flex items-center justify-center shadow-md">
|
|
<span className="font-poppins 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="font-poppins text-xs font-semibold text-gray-900 group-hover:text-primary 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 className="font-poppins">{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="font-poppins 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"
|
|
>
|
|
{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="font-poppins 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="font-poppins 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 className="font-poppins">{activity.time}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</motion.div>
|
|
</motion.div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
|
|
{/* Optimized Floating Sparkles */}
|
|
{[...Array(3)].map((_, i) => (
|
|
<motion.div
|
|
key={i}
|
|
className="absolute pointer-events-none will-change-transform"
|
|
style={{
|
|
top: `${20 + i * 30}%`,
|
|
left: `${10 + i * 40}%`,
|
|
}}
|
|
animate={{
|
|
y: [0, -20, 0],
|
|
opacity: [0, 0.5, 0],
|
|
scale: [0, 1, 0],
|
|
}}
|
|
transition={{
|
|
duration: 3,
|
|
repeat: Infinity,
|
|
delay: i * 0.8,
|
|
ease: "easeInOut",
|
|
}}
|
|
>
|
|
<Sparkles className="w-5 h-5 text-primary" />
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Card Indicators with City Names */}
|
|
<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={`font-poppins group relative transition-all duration-300 px-4 py-2 rounded-full font-medium ${idx === currentCardIndex
|
|
? 'bg-gradient-to-r from-primary to-orange-500 text-white shadow-lg scale-110'
|
|
: 'bg-white/80 backdrop-blur-sm text-gray-600 hover:text-primary hover:bg-white border border-gray-200 hover:border-primary/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 */}
|
|
<motion.div
|
|
className="flex flex-col items-center gap-6 mt-16"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.3 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<Button
|
|
onClick={onCreateItineraryClick}
|
|
className="font-poppins py-7 px-16 rounded-full text-xl font-bold 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 shadow-2xl hover:shadow-primary/50 transition-all hover:scale-105 hover:-translate-y-1"
|
|
>
|
|
<span className="flex items-center gap-3">
|
|
<Wand2 className="w-6 h-6" />
|
|
Create My Perfect Itinerary
|
|
</span>
|
|
</Button>
|
|
|
|
<p className="font-poppins text-gray-600 text-sm flex items-center gap-2">
|
|
<Sparkles className="w-4 h-4 text-primary" />
|
|
<span>Free to use • No credit card required</span>
|
|
<Sparkles className="w-4 h-4 text-primary" />
|
|
</p>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div className="container mx-auto px-4 py-12 space-y-24">
|
|
{/* Testimonials */}
|
|
<EnhancedTestimonials />
|
|
|
|
{/* Mobile App Promotion */}
|
|
<MobileAppPromotion />
|
|
|
|
{/* FAQ */}
|
|
<div id="faq" className="scroll-mt-32">
|
|
<MelbourneFAQ />
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</Layout>
|
|
</div>
|
|
);
|
|
} |