+ {/* Stamp aging and wear */}
+
+
+ {/* Stamp inner details */}
+
+
+
+ {/* Stamp text */}
+
+
+ TRAVEL
+
+
+ MEMORIES
+
+
+ 2024
+
+
+
+ {/* Stamp perforations with realistic variations */}
+ {[...Array(20)].map((_, i) => (
+
0.1 ? 1 : 0.3 // Random missing perforations
+ }}
+ />
+ ))}
+
+ {/* Stamp smudge mark */}
+
+
+
+
+ {/* Additional realistic aging effects */}
+
+
+ );
+ };
+
+ return (
+
+ {/* Background decorations */}
+
+ {/* Vintage Stamps */}
+
+
+
+
+ {/* Paper Textures */}
+
+
+
+ {/* Ink Splatters */}
+
+
+
+
+
+
+ {/* Header */}
+
+
+
+ Custom Memories
+
+
+
+ The Only Card That Sends Your
+
+
+ Holiday
+ {' '}
+ Home.
+
+
+
+
+ Transform your travel memories into beautiful, personalized postcards that capture the essence of your adventures.
+
+
+
+ {/* Centered Postcard Preview - Enhanced with Animations */}
+
+
+ {/* Interactive 3D Container */}
+
+
+
+ {/* Subtle glow effect */}
+
+
+
+ {/* Animated Edit Instructions */}
+ {editingCard && (
+
+
+ Click on any element to edit it
+
+
+
+ )}
+
+
+
+ {/* Animated Editing Panel */}
+ {editingCard && (
+
+ Edit {editingCard}
+ {editingCard === 'photo' && (
+
+ Photo URL
+ setPostcardData(prev => ({ ...prev, photo: e.target.value }))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
+ placeholder="Enter image URL"
+ />
+
+ )}
+ {editingCard === 'message' && (
+
+ Message
+
+ )}
+ {editingCard === 'date' && (
+
+ Date Text
+ setPostcardData(prev => ({ ...prev, date: e.target.value }))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
+ placeholder="Enter date text"
+ />
+
+ )}
+ {editingCard === 'label' && (
+
+ Address Label
+ setPostcardData(prev => ({ ...prev, addressLabel: e.target.value }))}
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
+ placeholder="Enter label text"
+ />
+
+ )}
+ {editingCard === 'stamp' && (
+
+
Stamp design features authentic vintage styling with realistic aging effects.
+
+ )}
+
+
+ setEditingCard(null)}
+ variant="outline"
+ size="sm"
+ className="flex-1"
+ >
+ Done
+
+
+
+
+ )}
+
+ {/* Enhanced Call to Action */}
+
+
+ Customize Your Postcard
+
+
+
+ Click on any postcard element above to edit it, or create a completely new design
+
+
+
+ {/* Enhanced Features Section */}
+
+ {[
+ {
+ icon: Palette,
+ title: "Authentic Design",
+ description: "Realistic vintage styling with aging effect, paper texture, and authentic details"
+ },
+ {
+ icon: Edit,
+ title: "Handwritten Style",
+ description: "Beautiful cursive fonts with realistic ink bleeding and natural imperfections"
+ },
+ {
+ icon: Camera,
+ title: "Easy Customization",
+ description: "Click any element to edit photos, messages, and details in real-time"
+ }
+ ].map((feature, index) => (
+
+
+
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/EnhancedTestimonials.tsx b/src/components/EnhancedTestimonials.tsx
new file mode 100644
index 0000000..64bf49c
--- /dev/null
+++ b/src/components/EnhancedTestimonials.tsx
@@ -0,0 +1 @@
+// This enhanced testimonials component (created for Melbourne page) has been removed
\ No newline at end of file
diff --git a/src/components/FeaturedCities.tsx b/src/components/FeaturedCities.tsx
new file mode 100644
index 0000000..7067897
--- /dev/null
+++ b/src/components/FeaturedCities.tsx
@@ -0,0 +1,246 @@
+import { useState } from 'react';
+import { Button } from './ui/button';
+import { Card, CardContent } from './ui/card';
+import { Badge } from './ui/badge';
+import { ChevronLeft, ChevronRight, MapPin, Star, Clock } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+
+interface City {
+ id: string;
+ name: string;
+ country: string;
+ image: string;
+ attractions: number;
+ rating: number;
+ duration: string;
+ startingPrice: number;
+ popular?: boolean;
+ description: string;
+}
+
+const cities: City[] = [
+ {
+ id: 'paris',
+ name: 'Paris',
+ country: 'France',
+ image: 'https://images.unsplash.com/photo-1431274172761-fca41d930114?q=80&w=600&auto=format&fit=crop',
+ attractions: 45,
+ rating: 4.9,
+ duration: '3-7 days',
+ startingPrice: 49,
+ popular: true,
+ description: 'City of Light with world-famous landmarks and art'
+ },
+ {
+ id: 'london',
+ name: 'London',
+ country: 'United Kingdom',
+ image: 'https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?q=80&w=600&auto=format&fit=crop',
+ attractions: 38,
+ rating: 4.8,
+ duration: '3-7 days',
+ startingPrice: 55,
+ description: 'Historic capital with royal palaces and modern culture'
+ },
+ {
+ id: 'tokyo',
+ name: 'Tokyo',
+ country: 'Japan',
+ image: 'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?q=80&w=600&auto=format&fit=crop',
+ attractions: 52,
+ rating: 4.9,
+ duration: '5-10 days',
+ startingPrice: 68,
+ popular: true,
+ description: 'Vibrant metropolis blending tradition with innovation'
+ },
+ {
+ id: 'barcelona',
+ name: 'Barcelona',
+ country: 'Spain',
+ image: 'https://images.unsplash.com/photo-1539037116277-4db20889f2d4?q=80&w=600&auto=format&fit=crop',
+ attractions: 29,
+ rating: 4.7,
+ duration: '3-5 days',
+ startingPrice: 42,
+ description: 'Architectural wonder with beaches and vibrant culture'
+ },
+ {
+ id: 'rome',
+ name: 'Rome',
+ country: 'Italy',
+ image: 'https://images.unsplash.com/photo-1552832230-c0197dd311b5?q=80&w=600&auto=format&fit=crop',
+ attractions: 34,
+ rating: 4.8,
+ duration: '3-6 days',
+ startingPrice: 45,
+ description: 'Eternal city with ancient history and timeless beauty'
+ },
+ {
+ id: 'new-york',
+ name: 'New York',
+ country: 'United States',
+ image: 'https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9?q=80&w=600&auto=format&fit=crop',
+ attractions: 67,
+ rating: 4.6,
+ duration: '4-8 days',
+ startingPrice: 75,
+ description: 'The city that never sleeps with iconic skyline'
+ }
+];
+
+export function FeaturedCities() {
+ const [currentSlide, setCurrentSlide] = useState(0);
+
+ const itemsPerSlide = 3;
+ const totalSlides = Math.ceil(cities.length / itemsPerSlide);
+
+ const nextSlide = () => {
+ setCurrentSlide((prev) => (prev + 1) % totalSlides);
+ };
+
+ const prevSlide = () => {
+ setCurrentSlide((prev) => (prev - 1 + totalSlides) % totalSlides);
+ };
+
+ const visibleCities = cities.slice(
+ currentSlide * itemsPerSlide,
+ (currentSlide + 1) * itemsPerSlide
+ );
+
+ return (
+
+
+
+
+
+ Featured Destinations
+
+
+ Discover the world's most incredible cities with our curated passes
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Desktop Grid */}
+
+ {visibleCities.map((city) => (
+
+ ))}
+
+
+ {/* Mobile Carousel */}
+
+
+
+ {cities.map((city) => (
+
+
+
+ ))}
+
+
+
+ {/* Mobile Navigation */}
+
+ {Array.from({ length: cities.length }).map((_, index) => (
+ setCurrentSlide(index)}
+ className={`w-2 h-2 rounded-full transition-colors ${
+ index === currentSlide ? 'bg-primary' : 'bg-muted'
+ }`}
+ />
+ ))}
+
+
+
+
+
+ View All Cities
+
+
+
+
+ );
+}
+
+function CityCard({ city }: { city: City }) {
+ return (
+
+
+ {city.popular && (
+
+ Popular
+
+ )}
+
+
+
+
+
+
+
{city.name}
+
{city.country}
+
+
+
+
+ {city.description}
+
+
+
+
+ {city.attractions} attractions
+
+
+
+
+ {city.rating}
+
+
+
+
+
+
+ {city.duration}
+
+
+
+ From €{city.startingPrice}
+
+
+
+
+ Explore {city.name}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
new file mode 100644
index 0000000..8e80253
--- /dev/null
+++ b/src/components/Footer.tsx
@@ -0,0 +1,60 @@
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { NewsletterSection } from './NewsletterSection';
+import { FooterBrand } from './FooterBrand';
+import { FooterNavigation } from './FooterNavigation';
+import { FooterBottom } from './FooterBottom';
+
+export function Footer() {
+ return (
+ <>
+ {/* Newsletter Subscription Section */}
+
+
+ {/* Footer with Nature Background */}
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/components/FooterBottom.tsx b/src/components/FooterBottom.tsx
new file mode 100644
index 0000000..b3ff9c8
--- /dev/null
+++ b/src/components/FooterBottom.tsx
@@ -0,0 +1,43 @@
+import { motion } from 'motion/react';
+import { Facebook, Twitter, Instagram, Youtube } from 'lucide-react';
+
+export function FooterBottom() {
+ return (
+
+
+ {/* Copyright */}
+
+ © 2024 CityCards. All rights reserved.
+
+
+ {/* Right Section - Legal Links and Social Icons */}
+
+ {/* Legal Links */}
+
+
+ {/* Social Icons - Horizontal Layout */}
+
+ {[Facebook, Twitter, Instagram, Youtube].map((Icon, index) => (
+
+
+
+ ))}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/FooterBrand.tsx b/src/components/FooterBrand.tsx
new file mode 100644
index 0000000..64b72d9
--- /dev/null
+++ b/src/components/FooterBrand.tsx
@@ -0,0 +1,67 @@
+import { motion } from 'motion/react';
+import { Apple, Play } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import cityCardsLogo from 'figma:asset/4d07c3035c8f965d162e4e0d20cb3910fd5fa6fe.png';
+
+export function FooterBrand() {
+ return (
+
+
+
+
+
+ Discover the best of every city with our curated experiences and attractions.
+ Your gateway to unforgettable urban adventures.
+
+
+ {/* Get Application Section */}
+
+
+
Get Application:
+
+
+ {/* iOS App Store Button */}
+
+
+
+ iOS
+
+
+
+ {/* Android App Store Button */}
+
+
+
+ Android
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/FooterNavigation.tsx b/src/components/FooterNavigation.tsx
new file mode 100644
index 0000000..66458eb
--- /dev/null
+++ b/src/components/FooterNavigation.tsx
@@ -0,0 +1,38 @@
+import { motion } from 'motion/react';
+import { footerSections } from '../utils/footerConstants';
+
+export function FooterNavigation() {
+ return (
+
+ {Object.entries(footerSections).map(([key, section]) => (
+
+ {section.title}
+
+ {section.links.map((link, index) => (
+
+
+ {link}
+
+
+ ))}
+
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/HandwrittenText.tsx b/src/components/HandwrittenText.tsx
new file mode 100644
index 0000000..3c1af03
--- /dev/null
+++ b/src/components/HandwrittenText.tsx
@@ -0,0 +1,227 @@
+import { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'motion/react';
+
+interface HandwrittenTextProps {
+ text: string;
+ className?: string;
+ style?: React.CSSProperties;
+ speed?: number; // Characters per second
+ startDelay?: number; // Delay before animation starts (ms)
+ onComplete?: () => void;
+ autoStart?: boolean;
+}
+
+export function HandwrittenText({
+ text,
+ className = "",
+ style = {},
+ speed = 8,
+ startDelay = 0,
+ onComplete,
+ autoStart = true
+}: HandwrittenTextProps) {
+ const [displayedText, setDisplayedText] = useState('');
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [isWriting, setIsWriting] = useState(false);
+ const [showCursor, setShowCursor] = useState(false);
+
+ // Split text into characters, preserving line breaks
+ const characters = text.split('');
+
+ useEffect(() => {
+ if (!autoStart) return;
+
+ const startTimer = setTimeout(() => {
+ setIsWriting(true);
+ setShowCursor(true);
+ }, startDelay);
+
+ return () => clearTimeout(startTimer);
+ }, [autoStart, startDelay]);
+
+ useEffect(() => {
+ if (!isWriting || currentIndex >= characters.length) {
+ if (currentIndex >= characters.length) {
+ // Hide cursor after a delay
+ const cursorTimer = setTimeout(() => {
+ setShowCursor(false);
+ setIsWriting(false);
+ onComplete?.();
+ }, 800);
+ return () => clearTimeout(cursorTimer);
+ }
+ return;
+ }
+
+ const char = characters[currentIndex];
+ const baseDelay = 1000 / speed;
+
+ // Variable delays for more natural writing
+ let delay = baseDelay;
+
+ if (char === ' ') {
+ delay = baseDelay * 0.5; // Spaces are quicker
+ } else if (char === '\n') {
+ delay = baseDelay * 2; // Line breaks take longer
+ } else if (['.', '!', '?'].includes(char)) {
+ delay = baseDelay * 1.5; // Punctuation takes a bit longer
+ } else if ([',', ';', ':'].includes(char)) {
+ delay = baseDelay * 1.2;
+ } else {
+ // Add some randomness to letter timing
+ delay = baseDelay * (0.8 + Math.random() * 0.4);
+ }
+
+ const timer = setTimeout(() => {
+ setDisplayedText(prev => prev + char);
+ setCurrentIndex(prev => prev + 1);
+ }, delay);
+
+ return () => clearTimeout(timer);
+ }, [isWriting, currentIndex, characters, speed, onComplete]);
+
+ // Reset function for external control
+ const reset = () => {
+ setDisplayedText('');
+ setCurrentIndex(0);
+ setIsWriting(false);
+ setShowCursor(false);
+ };
+
+ const start = () => {
+ reset();
+ setTimeout(() => {
+ setIsWriting(true);
+ setShowCursor(true);
+ }, startDelay);
+ };
+
+ // Expose control methods
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ (window as any).handwrittenTextControls = { reset, start };
+ }
+ }, []);
+
+ return (
+
+ {/* Main text with character-by-character animation */}
+
+ {displayedText.split('').map((char, index) => (
+
+ {char === '\n' ? '' : char}
+
+ ))}
+
+ {/* Writing cursor/pen effect */}
+
+ {showCursor && (
+
+ |
+
+ )}
+
+
+
+ {/* Ink flow effect */}
+
+
+ );
+}
+
+// Hook for controlling the animation externally
+export function useHandwrittenText(autoStart = true) {
+ const [isComplete, setIsComplete] = useState(false);
+
+ const reset = () => {
+ setIsComplete(false);
+ if (typeof window !== 'undefined' && (window as any).handwrittenTextControls) {
+ (window as any).handwrittenTextControls.reset();
+ }
+ };
+
+ const start = () => {
+ setIsComplete(false);
+ if (typeof window !== 'undefined' && (window as any).handwrittenTextControls) {
+ (window as any).handwrittenTextControls.start();
+ }
+ };
+
+ return {
+ isComplete,
+ reset,
+ start,
+ onComplete: () => setIsComplete(true)
+ };
+}
\ No newline at end of file
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..622411e
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,134 @@
+import { useState } from 'react';
+import { Button } from './ui/button';
+import { Input } from './ui/input';
+import { Sheet, SheetContent, SheetTrigger } from './ui/sheet';
+import { Search, Menu, MapPin } from 'lucide-react';
+
+interface HeaderProps {
+ activeCity?: string;
+ onCityChange?: (city: string) => void;
+}
+
+export function Header({ activeCity, onCityChange }: HeaderProps) {
+ const [isSearchOpen, setIsSearchOpen] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+
+ const cities = [
+ 'Paris', 'London', 'New York', 'Tokyo', 'Barcelona', 'Rome'
+ ];
+
+ const filteredCities = cities.filter(city =>
+ city.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const handleCitySelect = (city: string) => {
+ onCityChange?.(city);
+ setSearchQuery('');
+ setIsSearchOpen(false);
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx
new file mode 100644
index 0000000..c1203b8
--- /dev/null
+++ b/src/components/HeroSection.tsx
@@ -0,0 +1,882 @@
+import { useState, useEffect, useRef, forwardRef } from "react";
+import {
+ Star,
+ Menu,
+ X,
+ ShoppingBag,
+ ChevronDown,
+ Globe,
+} from "lucide-react";
+import { motion, AnimatePresence } from "motion/react";
+import { Button } from "./ui/button";
+import { CitySubmenu } from "./CitySubmenu";
+import Frame1597884853 from '../imports/Frame1597884853';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import cityCardsLogo from "figma:asset/4d07c3035c8f965d162e4e0d20cb3910fd5fa6fe.png";
+import heroBannerImage from 'figma:asset/d34005cfa14dc032f5b14c284f3ecd65df31444e.png';
+import logoImage from 'figma:asset/4d07c3035c8f965d162e4e0d20cb3910fd5fa6fe.png';
+import heroFriendsImage from 'figma:asset/85a189eb1e1976493720fa5345a5f387c5edcc1a.png';
+
+interface DropdownItem {
+ id: string;
+ label: string;
+ icon?: React.ReactNode;
+ action?: () => void;
+ badge?: string | number;
+}
+
+interface CartItem {
+ id: string;
+ name: string;
+ price: string;
+ image?: string;
+ quantity: number;
+}
+
+interface DropdownProps {
+ isOpen: boolean;
+ onToggle: () => void;
+ items: DropdownItem[];
+ trigger: React.ReactNode;
+ title?: string;
+ className?: string;
+}
+
+// Dropdown component with proper ref forwarding
+const Dropdown = forwardRef
(({
+ isOpen,
+ onToggle,
+ items,
+ trigger,
+ title,
+ className = ""
+}, ref) => (
+
+
+ {trigger}
+
+
+
+ {isOpen && (
+
+ {title && (
+
+
{title}
+
+ )}
+
+
+ {items.map((item, index) => (
+
{
+ item.action?.();
+ onToggle();
+ }}
+ className="w-full flex items-center justify-between px-5 py-3 text-left hover:bg-gray-50 transition-colors duration-200"
+ initial={{ opacity: 0, x: -10 }}
+ animate={{ opacity: 1, x: 0 }}
+ transition={{ delay: index * 0.05 }}
+ whileHover={{ x: 4 }}
+ >
+
+ {item.icon}
+ {item.label}
+
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+ ))}
+
+
+ )}
+
+
+));
+
+// Set display name for debugging
+Dropdown.displayName = 'Dropdown';
+
+interface HeroSectionProps {
+ onSignInClick?: () => void;
+ onPassesClick?: () => void;
+ currentPage?: string;
+}
+
+export function HeroSection({
+ onSignInClick,
+ onPassesClick,
+ currentPage,
+}: HeroSectionProps) {
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [activeLanguageDropdown, setActiveLanguageDropdown] = useState(false);
+ const [activeCartDropdown, setActiveCartDropdown] = useState(false);
+ const [showCitySubmenu, setShowCitySubmenu] = useState(true);
+
+ const languageRef = useRef(null);
+ const cartRef = useRef(null);
+
+ // Languages available
+ const languages: DropdownItem[] = [
+ { id: 'en', label: 'English', icon: 🇺🇸 },
+ { id: 'es', label: 'Español', icon: 🇪🇸 },
+ { id: 'fr', label: 'Français', icon: 🇫🇷 },
+ { id: 'de', label: 'Deutsch', icon: 🇩🇪 },
+ { id: 'it', label: 'Italiano', icon: 🇮🇹 },
+ ];
+
+ // Mock cart items
+ const cartItems: CartItem[] = [
+ { id: '1', name: 'Sydney 2-Day Pass', price: '$89', quantity: 1 },
+ { id: '2', name: 'Melbourne Premium Pass', price: '$129', quantity: 1 },
+ ];
+
+ // Section IDs for navigation
+ 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 scrollToSection = (index: number) => {
+ const sectionId = sectionIds[index];
+ const element = document.getElementById(sectionId);
+ if (element) {
+ element.scrollIntoView({ behavior: 'smooth' });
+ }
+ setIsMobileMenuOpen(false);
+ };
+
+ const closeMobileMenu = () => {
+ setIsMobileMenuOpen(false);
+ };
+
+ // Create click handlers for the navbar elements
+ const handleNavClick = (section: string) => {
+ switch (section) {
+ case 'about':
+ scrollToSection(0);
+ break;
+ case 'products':
+ onPassesClick?.();
+ break;
+ case 'attractions':
+ console.log('Navigate to attractions');
+ break;
+ case 'offer':
+ scrollToSection(5);
+ break;
+ case 'card':
+ scrollToSection(9);
+ break;
+ default:
+ break;
+ }
+ };
+
+ // Check if navigation item is active (simplified - only based on current page)
+ const isNavItemActive = (action: string) => {
+ return currentPage === action;
+ };
+
+ // Calculate cart total
+ const cartTotal = cartItems.reduce((total, item) => {
+ const price = parseFloat(item.price.replace('$', ''));
+ return total + (price * item.quantity);
+ }, 0);
+
+ // Detect scroll for navbar styling
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrolled = window.scrollY > 20;
+ setIsScrolled(scrolled);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ // Close dropdowns when clicking outside
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (languageRef.current && !languageRef.current.contains(event.target as Node)) {
+ setActiveLanguageDropdown(false);
+ }
+ if (cartRef.current && !cartRef.current.contains(event.target as Node)) {
+ setActiveCartDropdown(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ // Close mobile menu on escape key
+ useEffect(() => {
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ setIsMobileMenuOpen(false);
+ setActiveLanguageDropdown(false);
+ setActiveCartDropdown(false);
+ }
+ };
+
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, []);
+
+ return (
+
+ {/* Sticky Hero Background Container */}
+
+ {/* Enhanced Beach Vibes with Floating Elements */}
+
+ {/* Floating geometric elements inspired by the beach theme */}
+
+
+
+
+
+
+ {/* Desktop Navbar - Fixed and Sticky */}
+
+
+
+ {/* Using justify-between layout like Figma reference */}
+
+ {/* Logo Section - Increased Size */}
+
handleNavClick('about')}
+ >
+
+
+
+ {/* Navigation Links - No automatic highlighting */}
+
+ {[
+ { label: 'About Us', action: 'about' },
+ { label: 'Your Card', action: 'card' }
+ ].map((item) => (
+ handleNavClick(item.action)}
+ className={`relative px-0 py-2 text-base font-medium font-poppins transition-all duration-200 whitespace-nowrap group capitalize ${
+ isNavItemActive(item.action)
+ ? 'text-primary'
+ : 'text-foreground hover:text-primary'
+ }`}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {item.label}
+
+ {/* Active indicator - only for current page */}
+
+
+ {/* Hover background */}
+
+
+ ))}
+
+ {/* Our Products */}
+ handleNavClick('products')}
+ className="flex items-center text-foreground hover:text-primary text-base font-medium font-poppins transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50 px-2 py-1"
+ >
+ Our Products
+
+
+
+ {/* Right Section - Increased Sizes */}
+
+ {/* Language Dropdown - Increased Font */}
+
setActiveLanguageDropdown(!activeLanguageDropdown)}
+ items={languages}
+ title="Select Language"
+ trigger={
+
+
+ ENG
+
+
+ }
+ />
+
+ {/* Shopping Cart - Increased Size */}
+ setActiveCartDropdown(!activeCartDropdown)}
+ items={[
+ ...cartItems.map(item => ({
+ id: item.id,
+ label: `${item.name} - ${item.price}`,
+ badge: `${item.quantity}x`
+ })),
+ {
+ id: 'total',
+ label: `Total: $${cartTotal.toFixed(2)}`,
+ icon:
+ },
+ {
+ id: 'checkout',
+ label: 'Proceed to Checkout',
+ action: () => console.log('Checkout')
+ }
+ ]}
+ title="Shopping Cart"
+ trigger={
+
+
+
+ {cartItems.length}
+
+
+ }
+ />
+
+ {/* CTA Button with Shine Effect */}
+
+ GET A CITY CARD
+
+
+
+
+
+
+
+ {/* Medium Screen Navbar - Fixed and Sticky */}
+
+
+
+
+ {/* Logo - Increased Size */}
+
handleNavClick('about')}
+ >
+
+
+
+ {/* Navigation - Increased Font */}
+
+ {[
+ { label: 'About Us', action: 'about' },
+ { label: 'Cities', action: 'products' },
+ { label: 'Your Card', action: 'card' },
+ { label: 'Deals', action: 'offer' }
+ ].map((item) => (
+ handleNavClick(item.action)}
+ className={`relative px-0 py-1.5 text-base font-poppins font-medium transition-all duration-200 whitespace-nowrap group capitalize ${
+ isNavItemActive(item.action)
+ ? 'text-primary'
+ : 'text-foreground hover:text-primary'
+ }`}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {item.label}
+
+
+
+ ))}
+
+
+ {/* Right Section - Increased Sizes */}
+
+
setActiveLanguageDropdown(!activeLanguageDropdown)}
+ items={languages}
+ title="Select Language"
+ trigger={
+
+
+ ENG
+
+
+ }
+ />
+
+ setActiveCartDropdown(!activeCartDropdown)}
+ items={[
+ ...cartItems.map(item => ({
+ id: item.id,
+ label: `${item.name} - ${item.price}`,
+ badge: `${item.quantity}x`
+ })),
+ {
+ id: 'total',
+ label: `Total: $${cartTotal.toFixed(2)}`,
+ icon:
+ },
+ {
+ id: 'checkout',
+ label: 'Proceed to Checkout',
+ action: () => console.log('Checkout')
+ }
+ ]}
+ title="Shopping Cart"
+ trigger={
+
+
+
+ {cartItems.length}
+
+
+ }
+ />
+
+ {/* CTA Button with Shine Effect */}
+
+ GET A CITY CARD
+
+
+
+
+
+
+
+ {/* Mobile Navbar - Fixed and Sticky */}
+
+
+
+
+ {/* Mobile Logo - Increased Size */}
+
handleNavClick('about')}
+ >
+
+
+
+ {/* Mobile Actions - Increased Sizes */}
+
+ {/* Mobile Cart - Increased Size */}
+
setActiveCartDropdown(!activeCartDropdown)}
+ >
+
+
+ {cartItems.length}
+
+
+
+ {/* Mobile menu button - Increased Size */}
+
setIsMobileMenuOpen(true)}
+ className="inline-flex items-center justify-center p-2 rounded-lg text-foreground hover:text-primary hover:bg-gray-100 transition-colors duration-200"
+ aria-label="Open menu"
+ whileHover={{ scale: 1.05 }}
+ whileTap={{ scale: 0.95 }}
+ >
+
+
+
+
+
+
+
+
+ {/* Mobile Menu Overlay - Already without Arrow */}
+
+ {isMobileMenuOpen && (
+
+ {/* Backdrop */}
+
+
+ {/* Menu Panel */}
+
+ {/* Header - Increased Font */}
+
+
+ {/* Content - Increased Font Sizes */}
+
+
+ {/* Navigation Links - Increased Font, No automatic highlighting */}
+
+
Navigation
+ {[
+ { label: 'About Us', action: 'about' },
+ { label: 'Cities', action: 'products' },
+ { label: 'Your Card', action: 'card' },
+ { label: 'Deals', action: 'offer' }
+ ].map((item, index) => (
+ {
+ handleNavClick(item.action);
+ closeMobileMenu();
+ }}
+ className={`w-full text-left px-4 py-4 rounded-xl transition-all duration-200 text-xl font-poppins font-medium flex items-center justify-between group ${
+ isNavItemActive(item.action)
+ ? 'bg-primary/10 text-primary border border-primary/20'
+ : 'text-foreground hover:text-primary hover:bg-gray-50'
+ }`}
+ initial={{ opacity: 0, x: -20 }}
+ animate={{ opacity: 1, x: 0 }}
+ transition={{ delay: index * 0.1 }}
+ whileHover={{ scale: 1.02, x: 10 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {item.label}
+ {isNavItemActive(item.action) && (
+
+ )}
+
+ ))}
+
+
+ {/* Language Selection - Increased Font */}
+
+
Language
+
+ {languages.slice(0, 4).map((lang, index) => (
+
+ {lang.icon}
+ {lang.label}
+
+ ))}
+
+
+
+ {/* Shopping Cart Summary - Increased Font */}
+
+
Cart ({cartItems.length})
+
+ {cartItems.map((item, index) => (
+
+ {item.name}
+ {item.price}
+
+ ))}
+
+
+ Total:
+ ${cartTotal.toFixed(2)}
+
+
+
+
+
+
+
+ {/* Footer - CTA Button */}
+
+ {
+ onSignInClick?.();
+ closeMobileMenu();
+ }}
+ withShine={true}
+ className="w-full h-[56px] rounded-xl text-white font-medium text-lg"
+ >
+ GET A CITY CARD
+
+
+
+
+ )}
+
+
+ {/* City Submenu */}
+ {showCitySubmenu && (
+
setShowCitySubmenu(false)}
+ currentPage={currentPage}
+ onHomeClick={() => console.log('Home clicked')}
+ onMelbourneClick={() => console.log('Melbourne clicked')}
+ onAttractionsClick={() => console.log('Attractions clicked')}
+ onPassesClick={onPassesClick}
+ onBlogsClick={() => console.log('Blogs clicked')}
+ onHowItWorksClick={() => console.log('How It Works clicked')}
+ />
+ )}
+
+ {/* Hero Content */}
+
+
+
+ {/* Main Headline */}
+
+
+ CityCards.
+ See More, Spend Less.
+
+
+
+ {/* Subheading */}
+
+
+ Instant QR access to 40+ attractions,
+
+ exclusive perks, and savings up to 30%
+
+
+
+ {/* CTA Button */}
+
+
+ Explore Cities
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/HomePage.tsx b/src/components/HomePage.tsx
new file mode 100644
index 0000000..e78da13
--- /dev/null
+++ b/src/components/HomePage.tsx
@@ -0,0 +1,108 @@
+import { motion, useScroll, useSpring, useTransform } from 'motion/react';
+import { HeroSection } from './HeroSection';
+import { WhyChooseCityCards } from './WhyChooseCityCards';
+import { VarietyOfAdventures } from './VarietyOfAdventures';
+
+import { MobileAppSection } from './MobileAppSection';
+import { MagicItinerary } from './MagicItinerary';
+import { ScrollAnimatedJourney } from './ScrollAnimatedJourney';
+import { CustomPostcards } from './CustomPostcards';
+import { BookAttractionSection } from './BookAttractionSection';
+import { UpcomingCities } from './UpcomingCities';
+import { TrustSection } from './TrustSection';
+import { Footer } from './Footer';
+import { SectionWrapper } from './SectionWrapper';
+import { sectionsConfig } from '../utils/sections';
+import {
+ heroVariants,
+ staggerContainer,
+ backgroundVariants
+} from '../utils/animations';
+
+interface HomePageProps {
+ isMobile: boolean;
+ onSignInClick?: () => void;
+ onPassesClick?: () => void;
+ currentPage?: string;
+}
+
+export function HomePage({ isMobile, onSignInClick, onPassesClick, currentPage }: HomePageProps) {
+ // Smooth scroll progress for global effects
+ const { scrollYProgress } = useScroll();
+ const scaleX = useSpring(scrollYProgress, {
+ stiffness: 100,
+ damping: 30,
+ restDelta: 0.001
+ });
+
+ // Parallax effect for scroll progress
+ const progressOpacity = useTransform(scrollYProgress, [0, 0.1], [0, 1]);
+
+ const sectionComponents = [
+ WhyChooseCityCards,
+ VarietyOfAdventures,
+ ScrollAnimatedJourney,
+ MagicItinerary,
+ BookAttractionSection,
+ CustomPostcards,
+ UpcomingCities,
+ TrustSection,
+ MobileAppSection
+ ];
+
+ return (
+ <>
+ {/* Scroll Progress Indicator */}
+
+
+ {/* Main Content - No padding needed as capsule navbar floats */}
+
+ {/* 1. Hero Section - Immediate Load Animation */}
+
+
+
+
+
+
+
+
+ {/* 2-10. All Other Sections */}
+ {sectionsConfig.map((config, index) => {
+ const Component = sectionComponents[index];
+ return (
+
+
+
+ );
+ })}
+
+
+ {/* 11. Footer */}
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/components/MagicItinerary.tsx b/src/components/MagicItinerary.tsx
new file mode 100644
index 0000000..e9dc1e1
--- /dev/null
+++ b/src/components/MagicItinerary.tsx
@@ -0,0 +1,632 @@
+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 { ImageWithFallback } from './figma/ImageWithFallback';
+
+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'],
+ },
+];
+
+export function MagicItinerary() {
+ const [currentCardIndex, setCurrentCardIndex] = useState(0);
+ const [isAnimating, setIsAnimating] = useState(false);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setIsAnimating(true);
+ setTimeout(() => {
+ setCurrentCardIndex((prev) => (prev + 1) % itineraryCards.length);
+ setIsAnimating(false);
+ }, 400); // Half of the animation duration
+ }, 4000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ const currentCard = itineraryCards[currentCardIndex];
+ const nextCard = itineraryCards[(currentCardIndex + 1) % itineraryCards.length];
+ const thirdCard = itineraryCards[(currentCardIndex + 2) % itineraryCards.length];
+
+ return (
+
+ {/* Dynamic City Background */}
+
+
+ {/* City Background Image */}
+
+
+
+
+ {/* Lightened Multi-layer Gradient Overlay - Reduced opacity to show city */}
+
+
+
+
+
+
+ {/* White Readability Overlay - 42% Opacity */}
+
+
+ {/* Simplified Decorative Elements - Optimized */}
+
+ {/* Single Coral Gradient Blob - Optimized */}
+
+
+ {/* Simplified Floating Icons - Reduced from 8 to 4 */}
+ {[...Array(4)].map((_, i) => (
+
+ {i % 2 === 0 ? (
+
+ ) : (
+
+ )}
+
+ ))}
+
+
+
+ {/* Header */}
+
+
+
+
+
+ AI-Powered Magic Itinerary
+
+
+
+
+ Plan Your {' '}
+
+ Dream Journey
+
+
+ in Just {' '}
+ 3 Seconds
+
+ ✨
+
+
+
+
+ Our AI creates personalized itineraries with
+ perfectly timed activities, optimized routes, and curated experiences tailored
+ just for you.
+
+
+
+ {/* Card Stack Display */}
+
+
+ {/* Card Stack Container */}
+
+ {/* Visible Card Stack - Third Card */}
+
+
+
+
+
+
+ {thirdCard.city}
+
+
+
+
+
+ {/* Visible Card Stack - Second Card */}
+
+
+
+
+
+
+ {nextCard.city}
+
+
+
+
+
+ {/* Animated Front Card */}
+
+
+ {/* Card Image */}
+
+
+
+
+ {/* City Info Overlay */}
+
+
+ {currentCard.city}
+
+
+
+ {currentCard.country}
+
+
+
+ {/* Duration Badge */}
+
+
+
+ {currentCard.days} Days
+
+
+
+ {/* Top Left Journey Icon */}
+
+
+
+
+
+ {/* Card Content */}
+
+ {/* Highlights - Compact */}
+
+ {currentCard.highlights.map((highlight, idx) => (
+
+ {highlight}
+
+ ))}
+
+
+ {/* Day 1 Header */}
+
+
+
+ Day 1
+
+
+
+
+ {/* Day 1 Activities - Compact */}
+
+ {currentCard.activities.slice(0, 2).map((activity, idx) => (
+
+ {/* Route Line Connector */}
+ {idx < 1 && (
+
+ )}
+
+
+
+
+
{activity.name}
+
+
+ {activity.time}
+
+
+
+ ))}
+
+
+ {/* Day 2 Header */}
+
+
+
+ Day 2
+
+
+
+
+ {/* Day 2 Activities - Compact */}
+
+ {currentCard.activities.slice(2, 4).map((activity, idx) => (
+
+ {/* Route Line Connector */}
+ {idx < 1 && (
+
+ )}
+
+
+
+
+
{activity.name}
+
+
+ {activity.time}
+
+
+
+ ))}
+
+
+
+
+
+ {/* Optimized Floating Sparkles - Reduced from 8 to 3 */}
+ {[...Array(3)].map((_, i) => (
+
+
+
+ ))}
+
+
+
+ {/* Card Indicators with City Names */}
+
+ {itineraryCards.map((card, idx) => (
+ {
+ 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 && (
+
+ )}
+
+ ))}
+
+
+ {/* CTA Button - Optimized */}
+
+
+
+
+ Create My Perfect Itinerary
+
+
+
+
+
+ Free to use • No credit card required
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/MelbourneAttractions.tsx b/src/components/MelbourneAttractions.tsx
new file mode 100644
index 0000000..5e1c36f
--- /dev/null
+++ b/src/components/MelbourneAttractions.tsx
@@ -0,0 +1 @@
+// This Melbourne attractions component has been removed
\ No newline at end of file
diff --git a/src/components/MelbourneBlogs.tsx b/src/components/MelbourneBlogs.tsx
new file mode 100644
index 0000000..cfb46a7
--- /dev/null
+++ b/src/components/MelbourneBlogs.tsx
@@ -0,0 +1 @@
+// This Melbourne blogs component has been removed
\ No newline at end of file
diff --git a/src/components/MelbourneCardComparison.tsx b/src/components/MelbourneCardComparison.tsx
new file mode 100644
index 0000000..f16d09b
--- /dev/null
+++ b/src/components/MelbourneCardComparison.tsx
@@ -0,0 +1 @@
+// This Melbourne card comparison component has been removed
\ No newline at end of file
diff --git a/src/components/MelbourneFAQ.tsx b/src/components/MelbourneFAQ.tsx
new file mode 100644
index 0000000..f6df224
--- /dev/null
+++ b/src/components/MelbourneFAQ.tsx
@@ -0,0 +1 @@
+// This Melbourne FAQ component has been removed
\ No newline at end of file
diff --git a/src/components/MelbournePage.tsx b/src/components/MelbournePage.tsx
new file mode 100644
index 0000000..2b233f8
--- /dev/null
+++ b/src/components/MelbournePage.tsx
@@ -0,0 +1 @@
+// This Melbourne page component has been removed
\ No newline at end of file
diff --git a/src/components/MelbourneTourOverview.tsx b/src/components/MelbourneTourOverview.tsx
new file mode 100644
index 0000000..2424352
--- /dev/null
+++ b/src/components/MelbourneTourOverview.tsx
@@ -0,0 +1 @@
+// This Melbourne tour overview component has been removed
\ No newline at end of file
diff --git a/src/components/MobileAppPromotion.tsx b/src/components/MobileAppPromotion.tsx
new file mode 100644
index 0000000..742de47
--- /dev/null
+++ b/src/components/MobileAppPromotion.tsx
@@ -0,0 +1 @@
+// This mobile app promotion component (created for Melbourne page) has been removed
\ No newline at end of file
diff --git a/src/components/MobileAppSection.tsx b/src/components/MobileAppSection.tsx
new file mode 100644
index 0000000..5376938
--- /dev/null
+++ b/src/components/MobileAppSection.tsx
@@ -0,0 +1,642 @@
+import { ArrowRight, Smartphone, MapPin, Star, Clock, Users, Heart, Share2, Filter, Search } from 'lucide-react';
+import { motion } from 'motion/react';
+import imgFrame1597884939 from "figma:asset/5da1b0444c0d21bc7ee776c49e36e2a8ea4d3e12.png";
+
+export function MobileAppSection() {
+ // Generate a realistic QR code pattern
+ const generateQRPattern = () => {
+ const size = 21; // Standard QR code size
+ const pattern = [];
+
+ for (let i = 0; i < size * size; i++) {
+ // Create corner detection patterns
+ const row = Math.floor(i / size);
+ const col = i % size;
+
+ // Corner squares (7x7)
+ const isCornerSquare =
+ (row < 7 && col < 7) || // Top-left
+ (row < 7 && col >= 14) || // Top-right
+ (row >= 14 && col < 7); // Bottom-left
+
+ // Finder patterns within corner squares
+ const isFinderPattern = isCornerSquare && (
+ (row === 0 || row === 6 || col === 0 || col === 6) ||
+ (row >= 2 && row <= 4 && col >= 2 && col <= 4)
+ );
+
+ // Timing patterns
+ const isTimingPattern = (row === 6 && col >= 8 && col <= 12) || (col === 6 && row >= 8 && row <= 12);
+
+ // Random data pattern for other areas
+ const isDataPattern = !isCornerSquare && !isTimingPattern && Math.random() > 0.45;
+
+ pattern.push(isFinderPattern || isTimingPattern || isDataPattern);
+ }
+
+ return pattern;
+ };
+
+ const qrPattern = generateQRPattern();
+
+ return (
+
+ {/* Subtle Background Elements */}
+
+
+
+ {/* Figma Layout Implementation */}
+
+ {/* Header Section - Following Figma Layout */}
+
+ {/* Left Side - Main Heading */}
+
+
+ Your {' '}
+
+ Melbourne
+
+
+ City Card in Your {' '}
+ Pocket.
+
+
+
+ {/* Right Side - Description and Buttons */}
+
+ {/* Description Text */}
+
+ Download our mobile app and unlock instant access to premium city experiences across Australia.
+
+
+ {/* Download Buttons - Following Figma Layout */}
+
+ {/* Android Download Button */}
+
+ {/* Continuous Shine Effect */}
+
+
+ {/* Google Play Logo */}
+
+
+
+
+
Get it on
+
Google Play
+
+
+
+ {/* iOS Download Button */}
+
+ {/* Continuous Shine Effect */}
+
+
+ {/* Apple Logo */}
+
+
+
+
+
Download on the
+
App Store
+
+
+
+
+
+
+ {/* Mobile Mockups Section - Ultra-Realistic and Larger */}
+
+ {/* Mobile Mockup 1 - Enhanced Main App Screen */}
+
+
+ {/* iPhone Status Bar */}
+
+
+ 9:41
+
+
+ {/* Signal bars */}
+
+ {[1, 2, 3, 4].map((bar) => (
+
+ ))}
+
+ {/* WiFi */}
+
+
+
+ {/* Battery */}
+
+
+
+
+
+ {/* App Header */}
+
+
+
+
+
+
CityCards
+
Premium Active
+
+
+
+
+
+ {/* Location */}
+
+
+
+
Sydney, NSW
+
+
22°C ☀️
+
+
+
+ 3 active
+
+
+
+
+ {/* App Content */}
+
+ {/* Today's Deal Banner */}
+
+
+
+
TODAY ONLY
+
+
+ 8h left
+
+
+
+
+
Sydney Explorer Pass
+
Visit 3+ attractions & get 40% off
+
+
+ Activate
+
+
+ Details
+
+
+
+
+ {/* Stats Bar */}
+
+
+
+
+
+ 3
+
+
Passes used today
+
+
+
+
+
+
+ {/* Attraction Cards */}
+
+ {/* Opera House */}
+
+
+ PREMIUM
+
+
+
+
+
+
+
Sydney Opera House
+
+
+ 4.8
+ (2.4k)
+ •
+
+ 2.3km
+
+
+
+
+
+
+
+
+
+ Architecture
+
+
+ Cultural
+
+
+
+
+
+
+
+
+
+ $45
+ $75
+
+
Save $30
+
+
+ 40% OFF
+
+
+
+ Use Pass
+
+
+
+
+ {/* Harbour Bridge */}
+
+
+ POPULAR
+
+
+
+
+
+
+
Harbour Bridge Climb
+
+
+ 4.9
+ (1.8k)
+ •
+
+ 1.8km
+
+
+
+
+
+
+
+
+
+ Adventure
+
+
+ Iconic
+
+
+
+
+
+
+
+
+
+ $159
+ $289
+
+
Save $130
+
+
+ 45% OFF
+
+
+
+ Use Pass
+
+
+
+
+ {/* Botanic Gardens */}
+
+
+ FREE
+
+
+
+
+
+
+
Royal Botanic Gardens
+
+
+ 4.7
+ (956)
+ •
+
+ 1.2km
+
+
+
+
+
+
+
+
+
+ Nature
+
+
+ Family
+
+
+
+
+
+
+
+
+
+
+ {/* Bottom Navigation */}
+
+
+ {[
+ {
+ icon: (
+
+
+
+ ),
+ label: "Home",
+ active: true,
+ badge: null
+ },
+ {
+ icon: (
+
+
+
+ ),
+ label: "Map",
+ active: false,
+ badge: "3"
+ },
+ {
+ icon: (
+
+
+
+ ),
+ label: "Passes",
+ active: false,
+ badge: null
+ },
+ {
+ icon: (
+
+
+
+ ),
+ label: "Activity",
+ active: false,
+ badge: null
+ },
+ {
+ icon: (
+
+
+
+ ),
+ label: "Profile",
+ active: false,
+ badge: null
+ }
+ ].map((item, index) => (
+
+
+ {item.icon}
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+
+ {item.label}
+
+ {item.active && (
+
+ )}
+
+ ))}
+
+
+
+
+
+ {/* Mobile Mockup 2 - Map View */}
+
+
+ {/* Status Bar */}
+
+
+ 9:41
+
+
+
+ {[1, 2, 3, 4].map((bar) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {/* Map Header */}
+
+
+
+
+
+
+
+
+
+
Explore Sydney
+
3 nearby attractions
+
+
+
+
+
+
+
+
+ {/* Map Content */}
+
+ {/* Simplified Map Pattern */}
+
+
+ {/* Street lines */}
+
+
+
+
+
+
+
+
+ {/* Location Pins */}
+
+
+
+
+
+ {/* Current Location */}
+
+
+ {/* Route Line */}
+
+
+
+
+
+ {/* Bottom Card */}
+
+
+
+
+
Sydney Opera House
+
+
+ 2.3km away • 5 min walk
+
+
+
+ Go
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
new file mode 100644
index 0000000..af87edb
--- /dev/null
+++ b/src/components/Navbar.tsx
@@ -0,0 +1,736 @@
+import { useState, useEffect, useRef, forwardRef } from 'react';
+import { Menu, X, ShoppingBag, ChevronDown, Globe } from 'lucide-react';
+import { motion, AnimatePresence } from 'motion/react';
+import Frame1597884853 from '../imports/Frame1597884853';
+import { Button } from './ui/button';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import logoImage from 'figma:asset/e96a0ba8c1e8ee053e3eb462a3b4552a8657e7b6.png';
+
+interface NavbarProps {
+ activeCity: string;
+ onCityChange: (city: string) => void;
+ onSignInClick: () => void;
+ onPassesClick: () => void;
+ currentPage?: 'home' | 'signin' | 'passes';
+}
+
+interface DropdownItem {
+ id: string;
+ label: string;
+ icon?: React.ReactNode;
+ action?: () => void;
+ badge?: string | number;
+}
+
+interface CartItem {
+ id: string;
+ name: string;
+ price: string;
+ image?: string;
+ quantity: number;
+}
+
+interface DropdownProps {
+ isOpen: boolean;
+ onToggle: () => void;
+ items: DropdownItem[];
+ trigger: React.ReactNode;
+ title?: string;
+ className?: string;
+}
+
+export default function Navbar({
+ activeCity,
+ onCityChange,
+ onSignInClick,
+ onPassesClick,
+ currentPage = 'home'
+}: NavbarProps) {
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [activeLanguageDropdown, setActiveLanguageDropdown] = useState(false);
+ const [activeCartDropdown, setActiveCartDropdown] = useState(false);
+
+ const languageRef = useRef(null);
+ const cartRef = useRef(null);
+
+ // Languages available
+ const languages: DropdownItem[] = [
+ { id: 'en', label: 'English', icon: 🇺🇸 },
+ { id: 'es', label: 'Español', icon: 🇪🇸 },
+ { id: 'fr', label: 'Français', icon: 🇫🇷 },
+ { id: 'de', label: 'Deutsch', icon: 🇩🇪 },
+ { id: 'it', label: 'Italiano', icon: 🇮🇹 },
+ ];
+
+ // Mock cart items
+ const cartItems: CartItem[] = [
+ { id: '1', name: 'Sydney 2-Day Pass', price: '$89', quantity: 1 },
+ { id: '2', name: 'Melbourne Premium Pass', price: '$129', quantity: 1 },
+ ];
+
+ // Section IDs for navigation
+ 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 scrollToSection = (index: number) => {
+ const sectionId = sectionIds[index];
+ const element = document.getElementById(sectionId);
+ if (element) {
+ element.scrollIntoView({ behavior: 'smooth' });
+ }
+ setIsMobileMenuOpen(false);
+ };
+
+ const closeMobileMenu = () => {
+ setIsMobileMenuOpen(false);
+ };
+
+ // Detect scroll for navbar styling
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrolled = window.scrollY > 20;
+ setIsScrolled(scrolled);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ // Close dropdowns when clicking outside
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (languageRef.current && !languageRef.current.contains(event.target as Node)) {
+ setActiveLanguageDropdown(false);
+ }
+ if (cartRef.current && !cartRef.current.contains(event.target as Node)) {
+ setActiveCartDropdown(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ // Close mobile menu on escape key
+ useEffect(() => {
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ setIsMobileMenuOpen(false);
+ setActiveLanguageDropdown(false);
+ setActiveCartDropdown(false);
+ }
+ };
+
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, []);
+
+ // Create click handlers for the navbar elements
+ const handleNavClick = (section: string) => {
+ switch (section) {
+ case 'about':
+ scrollToSection(0);
+ break;
+ case 'products':
+ onPassesClick();
+ break;
+ case 'offer':
+ scrollToSection(5);
+ break;
+ case 'card':
+ scrollToSection(9);
+ break;
+ default:
+ break;
+ }
+ };
+
+ // Check if navigation item is active (simplified - only based on current page)
+ const isNavItemActive = (action: string) => {
+ return currentPage === action;
+ };
+
+ // Calculate cart total
+ const cartTotal = cartItems.reduce((total, item) => {
+ const price = parseFloat(item.price.replace('$', ''));
+ return total + (price * item.quantity);
+ }, 0);
+
+ // Dropdown component with proper ref forwarding
+ const Dropdown = forwardRef(({
+ isOpen,
+ onToggle,
+ items,
+ trigger,
+ title,
+ className = ""
+ }, ref) => (
+
+
+ {trigger}
+
+
+
+ {isOpen && (
+
+ {title && (
+
+
{title}
+
+ )}
+
+
+ {items.map((item, index) => (
+
{
+ item.action?.();
+ onToggle();
+ }}
+ className="w-full flex items-center justify-between px-5 py-3 text-left hover:bg-gray-50 transition-colors duration-200"
+ initial={{ opacity: 0, x: -10 }}
+ animate={{ opacity: 1, x: 0 }}
+ transition={{ delay: index * 0.05 }}
+ whileHover={{ x: 4 }}
+ >
+
+ {item.icon}
+ {item.label}
+
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+ ))}
+
+
+ )}
+
+
+ ));
+
+ // Set display name for debugging
+ Dropdown.displayName = 'Dropdown';
+
+ return (
+ <>
+ {/* Desktop Navbar - Extended to Gutter Width */}
+
+
+
+ {/* Using justify-between layout like Figma reference */}
+
+ {/* Logo Section - Increased Size */}
+
handleNavClick('about')}
+ >
+
+
+
+ {/* Navigation Links - No automatic highlighting */}
+
+ {[
+ { label: 'About Us', action: 'about' },
+ { label: 'Cities', action: 'products' },
+ { label: 'Your Card', action: 'card' },
+ { label: 'Deals', action: 'offer' }
+ ].map((item) => (
+ handleNavClick(item.action)}
+ className={`relative px-0 py-2 text-base font-medium transition-all duration-200 whitespace-nowrap group capitalize ${
+ isNavItemActive(item.action)
+ ? 'text-primary'
+ : 'text-gray-700 hover:text-gray-900'
+ }`}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {item.label}
+
+ {/* Active indicator - only for current page */}
+
+
+ {/* Hover background */}
+
+
+ ))}
+
+
+ {/* Right Section - Increased Sizes */}
+
+ {/* Language Dropdown - Increased Font */}
+
setActiveLanguageDropdown(!activeLanguageDropdown)}
+ items={languages}
+ title="Select Language"
+ trigger={
+
+
+ ENG
+
+
+ }
+ />
+
+ {/* Shopping Cart - Increased Size */}
+ setActiveCartDropdown(!activeCartDropdown)}
+ items={[
+ ...cartItems.map(item => ({
+ id: item.id,
+ label: `${item.name} - ${item.price}`,
+ badge: `${item.quantity}x`
+ })),
+ {
+ id: 'total',
+ label: `Total: $${cartTotal.toFixed(2)}`,
+ icon:
+ },
+ {
+ id: 'checkout',
+ label: 'Proceed to Checkout',
+ action: () => console.log('Checkout')
+ }
+ ]}
+ title="Shopping Cart"
+ trigger={
+
+
+
+ {cartItems.length}
+
+
+ }
+ />
+
+ {/* CTA Button with Shine Effect */}
+
+ GET A CITY CARD
+
+
+
+
+
+
+
+ {/* Medium Screen Navbar - Extended to Gutter Width */}
+
+
+
+
+ {/* Logo - Increased Size */}
+
handleNavClick('about')}
+ >
+
+
+
+ {/* Navigation - Increased Font */}
+
+ {[
+ { label: 'About Us', action: 'about' },
+ { label: 'Cities', action: 'products' },
+ { label: 'Your Card', action: 'card' },
+ { label: 'Deals', action: 'offer' }
+ ].map((item) => (
+ handleNavClick(item.action)}
+ className={`relative px-0 py-1.5 text-base font-medium transition-all duration-200 whitespace-nowrap group capitalize ${
+ isNavItemActive(item.action)
+ ? 'text-primary'
+ : 'text-gray-700 hover:text-gray-900'
+ }`}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {item.label}
+
+
+
+ ))}
+
+
+ {/* Right Section - Increased Sizes */}
+
+
setActiveLanguageDropdown(!activeLanguageDropdown)}
+ items={languages}
+ title="Select Language"
+ trigger={
+
+
+ ENG
+
+
+ }
+ />
+
+ setActiveCartDropdown(!activeCartDropdown)}
+ items={[
+ ...cartItems.map(item => ({
+ id: item.id,
+ label: `${item.name} - ${item.price}`,
+ badge: `${item.quantity}x`
+ })),
+ {
+ id: 'total',
+ label: `Total: $${cartTotal.toFixed(2)}`,
+ icon:
+ },
+ {
+ id: 'checkout',
+ label: 'Proceed to Checkout',
+ action: () => console.log('Checkout')
+ }
+ ]}
+ title="Shopping Cart"
+ trigger={
+
+
+
+ {cartItems.length}
+
+
+ }
+ />
+
+ {/* CTA Button with Shine Effect */}
+
+ GET A CITY CARD
+
+
+
+
+
+
+
+ {/* Mobile Navbar - Already Full Width */}
+
+
+
+
+ {/* Mobile Logo - Increased Size */}
+
handleNavClick('about')}
+ >
+
+
+
+ {/* Mobile Actions - Increased Sizes */}
+
+ {/* Mobile Cart - Increased Size */}
+
setActiveCartDropdown(!activeCartDropdown)}
+ >
+
+
+ {cartItems.length}
+
+
+
+ {/* Mobile menu button - Increased Size */}
+
setIsMobileMenuOpen(true)}
+ className="inline-flex items-center justify-center p-2 rounded-lg text-gray-700 hover:text-gray-900 hover:bg-gray-100 transition-colors duration-200"
+ aria-label="Open menu"
+ whileHover={{ scale: 1.05 }}
+ whileTap={{ scale: 0.95 }}
+ >
+
+
+
+
+
+
+
+
+ {/* Mobile Menu Overlay - Already without Arrow */}
+
+ {isMobileMenuOpen && (
+
+ {/* Backdrop */}
+
+
+ {/* Menu Panel */}
+
+ {/* Header - Increased Font */}
+
+
+ {/* Content - Increased Font Sizes */}
+
+
+ {/* Navigation Links - Increased Font, No automatic highlighting */}
+
+
Navigation
+ {[
+ { label: 'About Us', action: 'about' },
+ { label: 'Cities', action: 'products' },
+ { label: 'Your Card', action: 'card' },
+ { label: 'Deals', action: 'offer' }
+ ].map((item, index) => (
+ {
+ handleNavClick(item.action);
+ closeMobileMenu();
+ }}
+ className={`w-full text-left px-4 py-4 rounded-xl transition-all duration-200 text-xl font-medium flex items-center justify-between group ${
+ isNavItemActive(item.action)
+ ? 'bg-primary/10 text-primary border border-primary/20'
+ : 'text-gray-700 hover:text-gray-900 hover:bg-gray-50'
+ }`}
+ initial={{ opacity: 0, x: -20 }}
+ animate={{ opacity: 1, x: 0 }}
+ transition={{ delay: index * 0.1 }}
+ whileHover={{ scale: 1.02, x: 10 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {item.label}
+ {isNavItemActive(item.action) && (
+
+ )}
+
+ ))}
+
+
+ {/* Language Selection - Increased Font */}
+
+
Language
+
+ {languages.slice(0, 4).map((lang, index) => (
+
+ {lang.icon}
+ {lang.label}
+
+ ))}
+
+
+
+ {/* Shopping Cart Summary - Increased Font */}
+
+
Cart ({cartItems.length})
+
+ {cartItems.map((item, index) => (
+
+ {item.name}
+ {item.price}
+
+ ))}
+
+
+ Total:
+ ${cartTotal.toFixed(2)}
+
+
+
+
+
+
+
+ {/* Footer - CTA Button */}
+
+ {
+ onSignInClick();
+ closeMobileMenu();
+ }}
+ withShine={true}
+ className="w-full h-[56px] rounded-xl text-white font-medium text-lg"
+ >
+ GET A CITY CARD
+
+
+
+
+ )}
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/components/NewsletterSection.tsx b/src/components/NewsletterSection.tsx
new file mode 100644
index 0000000..f5b31c7
--- /dev/null
+++ b/src/components/NewsletterSection.tsx
@@ -0,0 +1,181 @@
+import { Button } from './ui/button';
+import { Input } from './ui/input';
+import { Mail } from 'lucide-react';
+import { motion } from 'motion/react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+
+export function NewsletterSection() {
+ return (
+
+ {/* Background Pattern */}
+
+
+ {/* Subtle Grid Pattern */}
+
+
+ {/* Floating Email Icons */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Main Content */}
+
+ {/* Main Heading with Typography Guidelines */}
+
+
+ Get
+
+
+ travel tips
+
+ &
+
+ exclusive offers.
+
+
+ We recommend you to subscribe, drop your email below to get daily update about us
+
+
+
+ {/* Newsletter Subscription Interface */}
+
+ {/* Email Subscription Bar */}
+
+
+
+
+
+
+
+
+ Subscribe Now
+
+
+
+
+
+ {/* Trust Indicators */}
+
+
+
+
No spam, unsubscribe anytime
+
+ •
+
+
+
Read our Privacy Policy
+
+ •
+
+
+
Join 10,000+ subscribers
+
+
+
+ {/* Additional Info */}
+
+ Get exclusive travel tips, destination guides, and special offers delivered to your inbox
+
+
+
+
+ {/* Bottom Decorative Elements */}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/OtherCities.tsx b/src/components/OtherCities.tsx
new file mode 100644
index 0000000..fe808cc
--- /dev/null
+++ b/src/components/OtherCities.tsx
@@ -0,0 +1,193 @@
+import { ArrowRight, Star, Clock, MapPin } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { Button } from './ui/button';
+import { motion } from 'motion/react';
+
+const otherCities = [
+ {
+ id: 1,
+ name: 'London',
+ country: 'United Kingdom',
+ rating: 4.8,
+ experiences: 180,
+ duration: '2-3 days',
+ highlight: 'Historic Landmarks',
+ image: 'https://images.unsplash.com/photo-1559788591-f5ea2371b915?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxsb25kb24lMjBicmlkZ2UlMjBjaXR5c2NhcGV8ZW58MXx8fHwxNzU2MTIzNTYyfDA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Discover centuries of history, royal palaces, and world-class museums in England\'s capital city.'
+ },
+ {
+ id: 2,
+ name: 'Hong Kong',
+ country: 'China',
+ rating: 4.7,
+ experiences: 125,
+ duration: '3-4 days',
+ highlight: 'Urban Adventure',
+ image: 'https://images.unsplash.com/photo-1698416286339-7edbf0922953?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxob25nJTIwa29uZyUyMHNreWxpbmUlMjBuaWdodHxlbnwxfHx8fDE3NTYxMjM1NjZ8MA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Experience the perfect blend of East meets West with stunning skylines and incredible cuisine.'
+ },
+ {
+ id: 3,
+ name: 'Istanbul',
+ country: 'Turkey',
+ rating: 4.6,
+ experiences: 95,
+ duration: '3-5 days',
+ highlight: 'Cultural Heritage',
+ image: 'https://images.unsplash.com/photo-1669117403979-be8e9448d9b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxpc3RhbmJ1bCUyMGJvc3Bob3J1cyUyMG1vc3F1ZXxlbnwxfHx8fDE3NTYxMjM1NzB8MA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Immerse yourself in the crossroads of Europe and Asia with stunning architecture and rich history.'
+ },
+ {
+ id: 4,
+ name: 'Cairo',
+ country: 'Egypt',
+ rating: 4.5,
+ experiences: 78,
+ duration: '4-5 days',
+ highlight: 'Ancient Wonders',
+ image: 'https://images.unsplash.com/photo-1705874930271-88eeb8f533dc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYWlybyUyMHB5cmFtaWRzJTIwYW5jaWVudHxlbnwxfHx8fDE3NTYxMjM1NzR8MA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Journey through millennia of civilization with the Great Pyramids and the treasures of the Nile.'
+ },
+ {
+ id: 5,
+ name: 'Vancouver',
+ country: 'Canada',
+ rating: 4.8,
+ experiences: 110,
+ duration: '2-3 days',
+ highlight: 'Nature & City',
+ image: 'https://images.unsplash.com/photo-1730661906876-18bfc6e95f2f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx2YW5jb3V2ZXIlMjBtb3VudGFpbnMlMjBjaXR5c2NhcGV8ZW58MXx8fHwxNzU2MTIzNTc4fDA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Where urban sophistication meets breathtaking natural beauty, from mountains to ocean.'
+ },
+ {
+ id: 6,
+ name: 'Miami',
+ country: 'United States',
+ rating: 4.6,
+ experiences: 135,
+ duration: '3-4 days',
+ highlight: 'Beach Culture',
+ image: 'https://images.unsplash.com/photo-1735825713164-192ff57874bf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtaWFtaSUyMGJlYWNoJTIwYXJ0JTIwZGVjb3xlbnwxfHx8fDE3NTYxMjM1ODR8MA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Dive into vibrant Art Deco architecture, world-famous beaches, and electric nightlife.'
+ }
+];
+
+export function OtherCities() {
+ return (
+
+
+ {/* Header */}
+
+
+ Explore {' '}
+
+ Other Cities
+
+
+
+ Discover incredible destinations around the world with our comprehensive city guides and curated experiences.
+
+
+
+ {/* Cities Grid */}
+
+ {otherCities.map((city, index) => (
+
+ {/* Image */}
+
+
+
+ {/* Gradient Overlay */}
+
+
+ {/* Rating Badge */}
+
+
+ {city.rating}
+
+
+ {/* Highlight Badge */}
+
+ {city.highlight}
+
+
+
+ {/* Content */}
+
+ {/* City Info */}
+
+
{city.name}
+
+
+ {city.country}
+
+
+
+ {/* Stats */}
+
+
+ {city.experiences}
+ experiences
+
+
+
+ {city.duration}
+
+
+
+ {/* Description */}
+
+ {city.description}
+
+
+ {/* CTA Button */}
+
+ Explore {city.name}
+
+
+
+
+ ))}
+
+
+ {/* Bottom CTA */}
+
+
+ View All Cities
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/PassComparison.tsx b/src/components/PassComparison.tsx
new file mode 100644
index 0000000..06ae832
--- /dev/null
+++ b/src/components/PassComparison.tsx
@@ -0,0 +1,138 @@
+import { Button } from './ui/button';
+import { Check } from 'lucide-react';
+
+interface Pass {
+ id: string;
+ name: string;
+ description: string;
+ price: string;
+ period: string;
+ originalPrice?: string;
+ popular?: boolean;
+ features: string[];
+ forText: string;
+}
+
+const passes: Pass[] = [
+ {
+ id: 'selective',
+ name: 'Selective Card',
+ description: 'Ideal for first-time visitors. Enjoy access to a curated selection of attractions and basic customization options to get your journey started.',
+ price: '$39',
+ period: '/24 hours',
+ originalPrice: '$65',
+ forText: 'For first-time visitors',
+ features: [
+ 'Access to 5+ top attractions',
+ 'Skip-the-line at selected venues',
+ 'Valid for 24 consecutive hours',
+ 'Mobile ticket delivery',
+ 'Free cancellation up to 24h',
+ 'Basic audio guide access'
+ ]
+ },
+ {
+ id: 'unlimited',
+ name: 'Unlimited Card',
+ description: 'Perfect for adventure seekers, providing unlimited access to all attractions, an extensive library of experiences and integration with popular travel tools.',
+ price: '$79',
+ period: '/48 hours',
+ originalPrice: '$150',
+ popular: true,
+ forText: 'For adventure seekers',
+ features: [
+ 'Unlimited access to all attractions',
+ 'Skip-the-line at every venue',
+ 'Valid for 48 consecutive hours',
+ 'Free public transport included',
+ 'Priority customer support',
+ 'Enhanced regular updates',
+ 'Perfect for exploring teams',
+ 'Integration with 25+ travel tool including Maps, Reviews, and Local Guides'
+ ]
+ }
+];
+
+export function PassComparison() {
+ return (
+
+
+
+
Pricing Plans
+
+ Choose the perfect plan for your needs
+
+
+
+
+ {passes.map((pass) => (
+
+ {pass.popular && (
+
+
+ Most Popular
+
+
+ )}
+
+
+
{pass.name}
+
+ {pass.description}
+
+
+
+ {pass.price}
+ {pass.period}
+
+ {pass.originalPrice && (
+
+ {pass.originalPrice} billed annually
+
+ )}
+
+
+ Choose plan
+
+
+
+
+
+
{pass.forText}
+
+ {pass.features.map((feature, index) => (
+
+
+
+
+ {feature}
+
+ ))}
+
+
+
+
+ ))}
+
+
+
+
+ Not sure which pass is right for you?
+
+
+ Compare All Features
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/PassesPage.tsx b/src/components/PassesPage.tsx
new file mode 100644
index 0000000..69012dc
--- /dev/null
+++ b/src/components/PassesPage.tsx
@@ -0,0 +1,221 @@
+import { Check, X, ArrowLeft } from 'lucide-react';
+import { Button } from './ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
+import cityCardsLogo from 'figma:asset/4d07c3035c8f965d162e4e0d20cb3910fd5fa6fe.png';
+
+interface PassesPageProps {
+ onBackClick?: () => void;
+}
+
+export function PassesPage({ onBackClick }: PassesPageProps) {
+ const passTypes = [
+ {
+ name: 'Selective Card',
+ description: 'Perfect for first-time visitors',
+ price: '$39',
+ period: '24 hours',
+ color: 'from-green-500 to-emerald-600',
+ features: {
+ 'Access to attractions': true,
+ 'Entry to attractions': true,
+ 'Access to experiences': true,
+ 'Entry to sites': true,
+ 'Access to venues': false,
+ 'Entry to events': 'Selective Card',
+ 'Access to locations': 'Selective Card',
+ 'Entry to activities': false,
+ 'Access to exhibits': true,
+ 'Entry to top attractions': true,
+ }
+ },
+ {
+ name: 'Unlimited Card',
+ description: 'Most popular choice',
+ price: '$79',
+ period: '48 hours',
+ color: 'from-purple-600 to-blue-600',
+ popular: true,
+ features: {
+ 'Access to attractions': true,
+ 'Entry to attractions': true,
+ 'Access to experiences': true,
+ 'Entry to sites': true,
+ 'Access to venues': true,
+ 'Entry to events': 'Unlimited Card',
+ 'Access to locations': 'Unlimited Card',
+ 'Entry to activities': true,
+ 'Access to exhibits': true,
+ 'Entry to top attractions': true,
+ }
+ }
+ ];
+
+ const allFeatures = [
+ 'Access to attractions',
+ 'Entry to attractions',
+ 'Access to experiences',
+ 'Entry to sites',
+ 'Access to venues',
+ 'Entry to events',
+ 'Access to locations',
+ 'Entry to activities',
+ 'Access to exhibits',
+ 'Entry to top attractions'
+ ];
+
+ const renderFeatureValue = (value: boolean | string) => {
+ if (typeof value === 'boolean') {
+ return value ? (
+
+ ) : (
+
+ );
+ }
+ return (
+ {value}
+ );
+ };
+
+ return (
+
+ {/* Animated Grid Background */}
+
+
+ {/* Header */}
+
+
+ {onBackClick && (
+
+
+ Back to Home
+
+ )}
+
+
+ {/* CityCards Logo */}
+
+
+
+
+
Choose Your Perfect Pass
+
+ Compare our two pass options and find the perfect fit for your travel style and budget
+
+
+
+
+
+ {/* Pass Cards Overview */}
+
+
+ {passTypes.map((pass, index) => (
+
+ {pass.popular && (
+
+
+ Most Popular
+
+
+ )}
+
+
+ {pass.name}
+ {pass.description}
+
+ {pass.price}
+ / {pass.period}
+
+
+
+
+
+ Select {pass.name}
+
+
+
+ ))}
+
+
+ {/* Detailed Comparison Table */}
+
+
+ Detailed Feature Comparison
+
+ See exactly what's included with each pass type
+
+
+
+
+
+
+
+
+ Features
+ {passTypes.map((pass, index) => (
+
+ {pass.name}
+
+ ))}
+
+
+
+
+ {allFeatures.map((feature, featureIndex) => (
+
+
+
+
+ {feature}
+
+
+ {passTypes.map((pass, passIndex) => (
+
+ {renderFeatureValue(pass.features[feature as keyof typeof pass.features])}
+
+ ))}
+
+ ))}
+
+
+
+
+
+
+ {/* Bottom CTA */}
+
+
Ready to explore?
+
+ Choose your pass and start discovering amazing attractions with skip-the-line access
+
+
+ Get Started Today
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/ScrollAnimatedJourney.tsx b/src/components/ScrollAnimatedJourney.tsx
new file mode 100644
index 0000000..071ca39
--- /dev/null
+++ b/src/components/ScrollAnimatedJourney.tsx
@@ -0,0 +1,274 @@
+import { useState, useEffect, useRef } from 'react';
+import { motion, useScroll, useTransform, useInView } from 'motion/react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+
+// Stack cards content data
+const stackCardsContent = [
+ {
+ index: 1,
+ mainHeading: "Your Journey Starts Here",
+ subtitle: "Follow these simple steps to unlock amazing experiences in your chosen city",
+ stepNumber: "1",
+ leftTitle: "Choose Your City - Left",
+ rightTitle: "Choose Your City - Right",
+ title: "Choose Your City",
+ subtitleShort: "Select Your Destination",
+ description: "Browse our collection of amazing cities and pick the perfect destination for your next adventure. Each city offers unique experiences and attractions.",
+ image: "https://images.unsplash.com/photo-1611021317203-37e3da396a29?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx8fDE3NTc0OTI0NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080"
+ },
+ {
+ index: 2,
+ stepNumber: "2",
+ leftTitle: "Select Your Pass - Left",
+ rightTitle: "Select Your Pass - Right",
+ title: "Select Your Pass",
+ subtitleShort: "Pick Your Perfect Plan",
+ description: "Choose from our flexible pass options that match your travel style and duration. From 1-day express to 7-day explorer passes.",
+ image: "https://images.unsplash.com/photo-1748459864963-c07abe863a1a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHx8fDE3NTc0OTI0ODN8MA&ixlib=rb-4.1.0&q=80&w=1080"
+ },
+ {
+ index: 3,
+ stepNumber: "3",
+ leftTitle: "Plan Your Itinerary - Left",
+ rightTitle: "Plan Your Itinerary - Right",
+ title: "Plan Your Itinerary",
+ subtitleShort: "Create Your Schedule",
+ description: "Use our smart planning tools to create the perfect itinerary with skip-the-line access to your favorite attractions and experiences.",
+ image: "https://images.unsplash.com/photo-1435527173128-983b87201f4d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHx8fDE3NTc0OTI0OTJ8MA&ixlib=rb-4.1.0&q=80&w=1080"
+ },
+ {
+ index: 4,
+ stepNumber: "4",
+ leftTitle: "Download the App - Left",
+ rightTitle: "Download the App - Right",
+ title: "Download the App",
+ subtitleShort: "Get Mobile Access",
+ description: "Download our mobile app to have instant access to your digital pass, maps, and real-time updates wherever you go.",
+ image: "https://images.unsplash.com/photo-1752392185223-c1d5d5f1d7af?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHx8fDE3NTc0OTI0NzZ8MA&ixlib=rb-4.1.0&q=80&w=1080"
+ },
+ {
+ index: 5,
+ stepNumber: "5",
+ leftTitle: "Enjoy Your Adventure - Left",
+ rightTitle: "Enjoy Your Adventure - Right",
+ title: "Enjoy Your Adventure",
+ subtitleShort: "Explore & Discover",
+ description: "Show your digital pass and enjoy instant access to attractions, tours, and experiences. No more waiting in long ticket lines.",
+ image: "https://images.unsplash.com/photo-1745121485729-37b38a72f62d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHx8fDE3NTc0OTI0ODV8MA&ixlib=rb-4.1.0&q=80&w=1080"
+ },
+ {
+ index: 6,
+ stepNumber: "6",
+ leftTitle: "Create Memories - Left",
+ rightTitle: "Create Memories - Right",
+ title: "Create Memories",
+ subtitleShort: "Share Your Journey",
+ description: "Capture amazing moments and share your experiences with friends. Rate attractions and help future travelers discover the best spots.",
+ image: "https://images.unsplash.com/photo-1594269732608-e9897da9baab?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHx8fDE3NTc0OTI0ODh8MA&ixlib=rb-4.1.0&q=80&w=1080"
+ }
+];
+
+// StackCard component for overlapping cards
+interface StackCardProps {
+ step: typeof stackCardsContent[0];
+ index: number;
+ isTablet?: boolean;
+ isMobile?: boolean;
+}
+
+function StackCard({ step, index, isTablet = false, isMobile = false }: StackCardProps) {
+ const ref = useRef(null);
+ const isInView = useInView(ref, { once: true, margin: "-50px" });
+ const { scrollYProgress } = useScroll({
+ target: ref,
+ offset: ["start end", "end start"]
+ });
+
+ // Parallax transform for the image
+ const imageY = useTransform(scrollYProgress, [0, 1], [50, -50]);
+
+ // Dynamic z-index based on scroll position and index
+ const zIndex = isInView ? 50 - index : 10 - index;
+
+ return (
+
+ {/* Background Image with Parallax */}
+
+
+
+ {/* Gradient Overlay */}
+
+
+
+
+
+ {/* Content Container */}
+
+
+ {/* Top Section - Step Number */}
+
+
+ {step.stepNumber}
+
+
+ {!isMobile && (
+
+ Step {step.stepNumber} of 6
+
+ )}
+
+
+ {/* Bottom Section - Main Content */}
+
+ {/* Left Title (Smaller text) */}
+
+ {step.leftTitle}
+
+
+ {/* Main Title */}
+
+ {step.title.split(' ').slice(0, -1).join(' ')} {' '}
+
+ {step.title.split(' ').slice(-1)[0]}
+
+
+
+ {/* Subtitle */}
+
+ {step.subtitleShort}
+
+
+ {/* Right Title (Smaller text) */}
+
+ {step.rightTitle}
+
+
+
+
+ {/* Interactive Shine Effect */}
+
+
+ );
+}
+
+// Main ScrollAnimatedJourney Component
+export function ScrollAnimatedJourney() {
+ return (
+
+ {/* Journey Section Wrapper - Creates scroll height for 6 steps */}
+
+ {/* Sticky Section Container */}
+ {/* Your Journey */}
+
+
+
+ {/* Background Effects */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/SectionWrapper.tsx b/src/components/SectionWrapper.tsx
new file mode 100644
index 0000000..877e267
--- /dev/null
+++ b/src/components/SectionWrapper.tsx
@@ -0,0 +1,55 @@
+import { motion } from 'motion/react';
+import { getVariants, getViewportSettings } from '../utils/helpers';
+import {
+ staggerContainer,
+ fastStaggerContainer,
+ backgroundVariants
+} from '../utils/animations';
+
+interface SectionWrapperProps {
+ id: string;
+ children: React.ReactNode;
+ containerType?: 'stagger' | 'fastStagger';
+ backgroundGradient?: string;
+ className?: string;
+ isMobile?: boolean;
+ variantType?: 'section' | 'card' | 'footer';
+}
+
+export function SectionWrapper({
+ id,
+ children,
+ containerType = 'stagger',
+ backgroundGradient,
+ className = '',
+ isMobile = false,
+ variantType = 'section'
+}: SectionWrapperProps) {
+ const containerVariants = containerType === 'fastStagger' ? fastStaggerContainer : staggerContainer;
+ const viewportType = variantType === 'footer' ? 'footer' : variantType;
+
+ return (
+
+ {backgroundGradient && (
+
+ )}
+
+
+ {children}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/SignInPage.tsx b/src/components/SignInPage.tsx
new file mode 100644
index 0000000..e65656c
--- /dev/null
+++ b/src/components/SignInPage.tsx
@@ -0,0 +1,341 @@
+import { useState, useEffect } from 'react';
+import { Button } from './ui/button';
+import { Input } from './ui/input';
+import { Label } from './ui/label';
+import { Facebook, Apple, ArrowLeft, ChevronLeft, ChevronRight, Lock, Gift } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import cityCardsLogo from 'figma:asset/4d07c3035c8f965d162e4e0d20cb3910fd5fa6fe.png';
+
+interface SignInPageProps {
+ onBackClick?: () => void;
+}
+
+export function SignInPage({ onBackClick }: SignInPageProps) {
+ const [mode, setMode] = useState<'login' | 'signup'>('signup');
+ const [formData, setFormData] = useState({
+ email: '',
+ password: '',
+ referralCode: ''
+ });
+ const [currentSlide, setCurrentSlide] = useState(0);
+
+ const carouselImages = [
+ {
+ src: 'https://images.unsplash.com/photo-1549144511-f099e773c147',
+ alt: 'Eiffel Tower at sunset - iconic Paris landmark',
+ title: 'Iconic Landmarks',
+ description: 'Skip the lines at world-famous attractions'
+ },
+ {
+ src: 'https://images.unsplash.com/photo-1554907984-15263bfd63bd',
+ alt: 'Grand museum interior with classical architecture',
+ title: 'World-Class Museums',
+ description: 'Explore culture and history with unlimited access'
+ },
+ {
+ src: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e',
+ alt: 'Tour group exploring scenic city overlook with guide',
+ title: 'City Tours & Experiences',
+ description: 'Discover hidden gems with guided adventures'
+ },
+ {
+ src: 'https://images.unsplash.com/photo-1520637836862-4d197d17c0a8',
+ alt: 'Sagrada Familia Barcelona - stunning architectural masterpiece',
+ title: 'Architectural Wonders',
+ description: 'Access exclusive sites and architectural marvels'
+ }
+ ];
+
+ // Auto-rotate carousel every 5 seconds
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setCurrentSlide((prev) => (prev + 1) % carouselImages.length);
+ }, 5000);
+
+ return () => clearInterval(timer);
+ }, [carouselImages.length]);
+
+ const nextSlide = () => {
+ setCurrentSlide((prev) => (prev + 1) % carouselImages.length);
+ };
+
+ const prevSlide = () => {
+ setCurrentSlide((prev) => (prev - 1 + carouselImages.length) % carouselImages.length);
+ };
+
+ const handleInputChange = (field: string, value: string) => {
+ setFormData(prev => ({ ...prev, [field]: value }));
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ console.log('Form submitted:', { mode, ...formData });
+ };
+
+ return (
+
+ {/* Left Side - Form */}
+
+
+ {/* Back Button */}
+ {onBackClick && (
+
+
+ Back to CityCards
+
+ )}
+ {/* Header */}
+
+ {/* CityCards Logo */}
+
+
+
+
+
+ {mode === 'signup' ? 'Create Your Account' : 'Welcome Back'}
+
+
+ {mode === 'signup'
+ ? 'Setting up an account takes less than one minute.'
+ : 'Sign in to access your account and continue your journey.'
+ }
+
+
+
+ {/* Toggle Buttons */}
+
+ setMode('login')}
+ className={`flex-1 py-2 px-4 rounded-full text-sm font-medium transition-all duration-200 ${
+ mode === 'login'
+ ? 'bg-white text-gray-900 shadow-sm'
+ : 'text-gray-600 hover:text-gray-900'
+ }`}
+ >
+ Login
+
+ setMode('signup')}
+ className={`flex-1 py-2 px-4 rounded-full text-sm font-medium transition-all duration-200 ${
+ mode === 'signup'
+ ? 'bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-sm'
+ : 'text-gray-600 hover:text-gray-900'
+ }`}
+ >
+ Sign Up
+
+
+
+ {/* Form */}
+
+
+ {/* Divider */}
+
+
+
+ Or Continue with
+
+
+
+ {/* Social Login Buttons */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Right Side - CityCards Attraction Carousel */}
+
+ {/* Carousel Container */}
+
+ {/* Images */}
+
+ {carouselImages.map((image, index) => (
+
+
+
+ {/* Gradient Overlay */}
+
+
+ {/* Content Overlay */}
+
+
+
{image.title}
+
{image.description}
+
+
+
Included with CityCards Pass
+
+
+
+
+ ))}
+
+
+ {/* Navigation Buttons */}
+
+
+
+
+
+
+
+
+ {/* Dot Indicators */}
+
+ {carouselImages.map((_, index) => (
+ setCurrentSlide(index)}
+ className={`w-3 h-3 rounded-full transition-all duration-300 ${
+ index === currentSlide
+ ? 'bg-white scale-110'
+ : 'bg-white/50 hover:bg-white/75'
+ }`}
+ />
+ ))}
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/TrustSection.tsx b/src/components/TrustSection.tsx
new file mode 100644
index 0000000..555a184
--- /dev/null
+++ b/src/components/TrustSection.tsx
@@ -0,0 +1,432 @@
+import { useState, useRef, useEffect } from 'react';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { motion, AnimatePresence, useMotionValue, useTransform, PanInfo } from 'motion/react';
+
+const testimonials = [
+ {
+ id: 1,
+ name: 'Sarah Mitchell',
+ role: 'Travel Blogger',
+ company: 'Wanderlust Adventures',
+ avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b1ac?q=80&w=150&auto=format&fit=crop&ixlib=rb-4.0.3',
+ quote: 'CityCards completely transformed our Australian city-hopping adventure. The curated attraction passes saved us 60% on costs and the skip-the-line access was invaluable. Every city felt like a personalized experience tailored just for us.',
+ signature: 'Sarah M.'
+ },
+ {
+ id: 2,
+ name: 'Michael Chen',
+ role: 'Travel Photographer',
+ company: 'Urban Lens Studio',
+ avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=150&auto=format&fit=crop&ixlib=rb-4.0.3',
+ quote: 'As someone who captures cities worldwide, CityCards gave me access to unique perspectives and hidden gems across Australia. The mobile city guide made discovering photo spots seamless, from Sydney\'s harbor to Melbourne\'s laneways.',
+ signature: 'Michael C.'
+ },
+ {
+ id: 3,
+ name: 'Emma Rodriguez',
+ role: 'Adventure Seeker',
+ company: 'Solo Travel Co.',
+ avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=150&auto=format&fit=crop&ixlib=rb-4.0.3',
+ quote: 'Solo traveling became effortless with CityCards. From instant bookings to local recommendations, I felt confident exploring Australian cities. The comprehensive city guides unlocked experiences I never knew existed.',
+ signature: 'Emma R.'
+ },
+ {
+ id: 4,
+ name: 'David Park',
+ role: 'Family Traveler',
+ company: 'Adventure Families',
+ avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?q=80&w=150&auto=format&fit=crop&ixlib=rb-4.0.3',
+ quote: 'Planning family trips used to be overwhelming, but CityCards simplified everything. The kids loved the interactive city experiences and we saved hours with skip-the-line access at every single attraction we visited.',
+ signature: 'David P.'
+ },
+ {
+ id: 5,
+ name: 'Lisa Thompson',
+ role: 'Business Traveler',
+ company: 'Global Solutions Inc.',
+ avatar: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=150&auto=format&fit=crop&ixlib=rb-4.0.3',
+ quote: 'Between business meetings, CityCards helped me maximize my limited free time in every city. Quick access to top attractions without the hassle of traditional booking made every business trip memorable and productive.',
+ signature: 'Lisa T.'
+ },
+ {
+ id: 6,
+ name: 'James Wilson',
+ role: 'Cultural Explorer',
+ company: 'Heritage Travels',
+ avatar: 'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?q=80&w=150&auto=format&fit=crop&ixlib=rb-4.0.3',
+ quote: 'CityCards opened doors to authentic cultural experiences I would have missed otherwise. The curated recommendations led to discoveries that became the absolute highlights of our entire cultural journey through Australia\'s diverse cities.',
+ signature: 'James W.'
+ }
+];
+
+// Custom SVG quotation marks
+const QuoteStart = ({ className }: { className?: string }) => (
+
+
+
+);
+
+const QuoteEnd = ({ className }: { className?: string }) => (
+
+
+
+);
+
+export function TrustSection() {
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [hoveredCard, setHoveredCard] = useState(null);
+ const [dragConstraints, setDragConstraints] = useState({ left: 0, right: 0 });
+ const [showNameOnProgress, setShowNameOnProgress] = useState(false);
+ const carouselRef = useRef(null);
+ const x = useMotionValue(0);
+
+ // Calculate how many cards to show based on screen size
+ const [cardsPerView, setCardsPerView] = useState(1);
+
+ useEffect(() => {
+ const updateCardsPerView = () => {
+ if (window.innerWidth >= 1200) {
+ setCardsPerView(2);
+ } else {
+ setCardsPerView(1);
+ }
+ };
+
+ updateCardsPerView();
+ window.addEventListener('resize', updateCardsPerView);
+ return () => window.removeEventListener('resize', updateCardsPerView);
+ }, []);
+
+ const totalSlides = Math.ceil(testimonials.length / cardsPerView);
+ const maxIndex = totalSlides - 1;
+
+ useEffect(() => {
+ if (carouselRef.current) {
+ const cardWidth = carouselRef.current.offsetWidth;
+ const maxDrag = -(cardWidth * maxIndex);
+ setDragConstraints({ left: maxDrag, right: 0 });
+ }
+ }, [maxIndex, cardsPerView]);
+
+ const handlePrevious = () => {
+ setCurrentIndex(prev => Math.max(0, prev - 1));
+ };
+
+ const handleNext = () => {
+ setCurrentIndex(prev => Math.min(maxIndex, prev + 1));
+ };
+
+ const handleDragEnd = (event: any, info: PanInfo) => {
+ const offset = info.offset.x;
+ const velocity = info.velocity.x;
+
+ if (Math.abs(offset) > 100 || Math.abs(velocity) > 500) {
+ if (offset > 0 && currentIndex > 0) {
+ setCurrentIndex(prev => prev - 1);
+ } else if (offset < 0 && currentIndex < maxIndex) {
+ setCurrentIndex(prev => prev + 1);
+ }
+ }
+ };
+
+ const progress = totalSlides > 1 ? (currentIndex / maxIndex) * 100 : 0;
+ const getCurrentTestimonialNames = () => {
+ const startIndex = currentIndex * cardsPerView;
+ const endIndex = Math.min(startIndex + cardsPerView, testimonials.length);
+ return testimonials.slice(startIndex, endIndex).map(t => t.name).join(', ');
+ };
+
+ return (
+
+ {/* Subtle background texture */}
+
+
+
+ {/* Header */}
+
+
+ What Our {' '}
+
+ Travelers
+ {' '}
+ Say
+
+
+
+ Real stories from real travelers who've discovered amazing cities with CityCards
+
+
+
+ {/* Carousel Container */}
+
+
+
+ {/* Carousel */}
+
+
+ {testimonials.map((testimonial, index) => {
+ const cardRotation = (index % 3 - 1) * 1.5 + (Math.random() - 0.5) * 1;
+ const cardOffset = (index % 2) * 10;
+
+ return (
+ setHoveredCard(testimonial.id)}
+ onHoverEnd={() => setHoveredCard(null)}
+ >
+ {/* Paper Card with enhanced realism */}
+
+ {/* Enhanced paper texture */}
+
+
+ {/* Paper creases and folds */}
+
+
+ {/* Corner fold effect */}
+
+
+ {/* Paperclip decoration */}
+
+
+ {/* Tape effect on left edge */}
+
+
+ {/* Enhanced quotation marks */}
+
+
+
+ {testimonial.quote}
+
+
+
+
+
+
+ {/* Enhanced Profile Section with sticker effect */}
+
+
+ {testimonial.name}
+
+
+ {testimonial.company}
+
+
+
+ {/* Enhanced signature with writing animation */}
+
+
+ {testimonial.signature}
+
+
+
+ {/* Subtle aging spots */}
+
+
+
+ {/* Pin shadow effect */}
+ {index % 3 === 0 && (
+
+ )}
+
+
+ );
+ })}
+
+
+
+ {/* Enhanced Progress Bar with hover names */}
+
+
+
+ {/* Enhanced slide indicators */}
+
+ {Array.from({ length: totalSlides }).map((_, index) => (
+ setCurrentIndex(index)}
+ className={`w-4 h-4 rounded-full transition-all duration-300 transform ${
+ index === currentIndex
+ ? 'bg-warm-coral scale-110 shadow-lg'
+ : 'bg-gray-300 hover:bg-gray-400 hover:scale-105'
+ }`}
+ style={{
+ boxShadow: index === currentIndex
+ ? '0 4px 8px rgba(249, 95, 98, 0.3), 0 2px 4px rgba(249, 95, 98, 0.2)'
+ : '0 2px 4px rgba(0,0,0,0.1)'
+ }}
+ />
+ ))}
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/UpcomingCities.tsx b/src/components/UpcomingCities.tsx
new file mode 100644
index 0000000..b5d7606
--- /dev/null
+++ b/src/components/UpcomingCities.tsx
@@ -0,0 +1,327 @@
+import { ArrowRight } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { Button } from './ui/button';
+import { useRef, useState, useEffect } from 'react';
+import Image592Traced from '../imports/Image592Traced-5025-559';
+
+const upcomingCities = [
+ {
+ id: 1,
+ name: 'Boston',
+ country: 'USA',
+ launchDate: 'Spring 2025',
+ attractions: 65,
+ description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.',
+ image: 'https://images.unsplash.com/photo-1568271667303-14b2a1a36da1?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: true
+ },
+ {
+ id: 2,
+ name: 'Rome',
+ country: 'Italy',
+ launchDate: 'Summer 2025',
+ attractions: 80,
+ image: 'https://images.unsplash.com/photo-1552832230-c0197dd311b5?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ },
+ {
+ id: 3,
+ name: 'Paris',
+ country: 'France',
+ launchDate: 'Fall 2025',
+ attractions: 95,
+ image: 'https://images.unsplash.com/photo-1502602898536-47ad22581b52?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ },
+ {
+ id: 4,
+ name: 'Dubai',
+ country: 'UAE',
+ launchDate: 'Winter 2025',
+ attractions: 70,
+ image: 'https://images.unsplash.com/photo-1512453979798-5ea266f8880c?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false,
+ badge: 'New'
+ },
+ {
+ id: 5,
+ name: 'Tokyo',
+ country: 'Japan',
+ launchDate: 'Early 2026',
+ attractions: 120,
+ image: 'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ },
+ {
+ id: 6,
+ name: 'Sydney',
+ country: 'Australia',
+ launchDate: 'Spring 2026',
+ attractions: 85,
+ image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ },
+ {
+ id: 7,
+ name: 'New York',
+ country: 'USA',
+ launchDate: 'Summer 2026',
+ attractions: 150,
+ image: 'https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false,
+ badge: 'Most Requested'
+ },
+ {
+ id: 8,
+ name: 'Singapore',
+ country: 'Singapore',
+ launchDate: 'Fall 2026',
+ attractions: 75,
+ image: 'https://images.unsplash.com/photo-1525625293386-3f8f99389edd?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ },
+ {
+ id: 9,
+ name: 'Amsterdam',
+ country: 'Netherlands',
+ launchDate: 'Winter 2026',
+ attractions: 90,
+ image: 'https://images.unsplash.com/photo-1534351590666-13e3e96b5017?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ },
+ {
+ id: 10,
+ name: 'Barcelona',
+ country: 'Spain',
+ launchDate: 'Early 2027',
+ attractions: 110,
+ image: 'https://images.unsplash.com/photo-1583422409516-2895a77efded?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
+ showHoverState: false
+ }
+];
+
+export function UpcomingCities() {
+ const scrollContainerRef = useRef(null);
+ const [isDragging, setIsDragging] = useState(false);
+ const [startX, setStartX] = useState(0);
+ const [scrollLeft, setScrollLeft] = useState(0);
+ const [showDragHint, setShowDragHint] = useState(false);
+
+ const handleMouseDown = (e: React.MouseEvent) => {
+ if (!scrollContainerRef.current) return;
+ // Only start dragging if not clicking on a button or interactive element
+ const target = e.target as HTMLElement;
+ if (target.closest('button') || target.closest('[role="button"]')) {
+ return;
+ }
+ setIsDragging(true);
+ setStartX(e.pageX - scrollContainerRef.current.offsetLeft);
+ setScrollLeft(scrollContainerRef.current.scrollLeft);
+ setShowDragHint(false);
+ };
+
+ const handleMouseLeave = () => {
+ setIsDragging(false);
+ setShowDragHint(false);
+ };
+
+ const handleMouseUp = () => {
+ setIsDragging(false);
+ };
+
+ const handleMouseMove = (e: React.MouseEvent) => {
+ if (!isDragging || !scrollContainerRef.current) return;
+ e.preventDefault();
+ const x = e.pageX - scrollContainerRef.current.offsetLeft;
+ const walk = (x - startX) * 1.5; // Reduced multiplier for smoother movement
+ scrollContainerRef.current.scrollLeft = scrollLeft - walk;
+ };
+
+ const handleMouseEnter = () => {
+ if (!isDragging) {
+ setShowDragHint(true);
+ }
+ };
+
+ useEffect(() => {
+ const handleGlobalMouseUp = () => setIsDragging(false);
+ document.addEventListener('mouseup', handleGlobalMouseUp);
+ return () => document.removeEventListener('mouseup', handleGlobalMouseUp);
+ }, []);
+
+ return (
+
+ {/* Header - Contained and aligned */}
+
+
+
+ Upcoming Cities
+
+
+ Here are lots of interesting destinations to visit, but don't be confused—they're already grouped by category.
+
+
+
+
+ {/* Cities Carousel - Aligned with header, extending to screen edge */}
+
+ {/* Drag Hint Pill */}
+ {showDragHint && (
+
+ Drag to scroll
+
+ )}
+
+
+ {upcomingCities.map((city) => (
+
+ {/* Background - Either solid color or image */}
+ {city.showHoverState ? (
+ // Boston card with image background and same layout as other cards
+ <>
+
+
+ {/* Dark overlay */}
+
+
+ {/* City name overlay - matching Rome card layout */}
+
+
{city.name}
+
+ {city.country}
+ {city.launchDate}
+
+
+
+ {/* Hover state overlay - same as other cards */}
+
+
+
{city.name}
+
{city.attractions}+ attractions
+
Coming {city.launchDate}
+
{
+ e.stopPropagation();
+ setIsDragging(false);
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ console.log('Notify Me button clicked');
+ }}
+ >
+ Notify Me
+
+
+
+
+ >
+ ) : (
+ // Image background for other cards
+ <>
+
+
+ {/* Dark overlay */}
+
+
+ {/* Badge (if present) */}
+ {city.badge && (
+
+ {city.badge}
+
+ )}
+
+ {/* City name overlay */}
+
+
{city.name}
+
+ {city.country}
+ {city.launchDate}
+
+
+
+ {/* Hover state overlay */}
+
+
+
{city.name}
+
{city.attractions}+ attractions
+
Coming {city.launchDate}
+
{
+ e.stopPropagation();
+ setIsDragging(false);
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ console.log('Notify Me button clicked');
+ }}
+ >
+ Notify Me
+
+
+
+
+ >
+ )}
+
+ ))}
+
+
+
+ {/* Bottom CTA - Contained */}
+
+
+
+
+ {/* Global Offices Section */}
+
+ {/* Global Presence Section - Exact Screenshot Recreation */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/UpcomingDestinations.tsx b/src/components/UpcomingDestinations.tsx
new file mode 100644
index 0000000..a1e0cd2
--- /dev/null
+++ b/src/components/UpcomingDestinations.tsx
@@ -0,0 +1,251 @@
+import { Calendar, Users, Compass, Bell, ArrowRight, Sparkles } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { Button } from './ui/button';
+import { motion } from 'motion/react';
+
+const upcomingDestinations = [
+ {
+ id: 1,
+ name: 'Reykjavik',
+ country: 'Iceland',
+ launchDate: 'March 2025',
+ season: 'Spring',
+ expectedAttractions: 45,
+ specialFeature: 'Northern Lights',
+ status: 'launching-soon',
+ image: 'https://images.unsplash.com/photo-1666003415555-1634ff3cd022?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyZXlramF2aWslMjBpY2VsYW5kJTIwbm9ydGhlcm4lMjBsaWdodHN8ZW58MXx8fHwxNzU2MTIzNjI1fDA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Experience the magic of Iceland with geothermal wonders, dramatic landscapes, and the enchanting Northern Lights.',
+ earlyBirdDiscount: '25%'
+ },
+ {
+ id: 2,
+ name: 'Cape Town',
+ country: 'South Africa',
+ launchDate: 'June 2025',
+ season: 'Winter',
+ expectedAttractions: 85,
+ specialFeature: 'Wildlife Safari',
+ status: 'in-development',
+ image: 'https://images.unsplash.com/photo-1635260980154-315c57168b12?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYXBlJTIwdG93biUyMHRhYmxlJTIwbW91bnRhaW58ZW58MXx8fHwxNzU2MTIzNjI5fDA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Discover the Mother City with Table Mountain adventures, wine tours, and incredible wildlife experiences.',
+ earlyBirdDiscount: '20%'
+ },
+ {
+ id: 3,
+ name: 'Oslo',
+ country: 'Norway',
+ launchDate: 'September 2025',
+ season: 'Autumn',
+ expectedAttractions: 65,
+ specialFeature: 'Fjord Tours',
+ status: 'planning',
+ image: 'https://images.unsplash.com/photo-1725993486972-f569d8038915?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxvc2xvJTIwZmpvcmQlMjBub3J3YXl8ZW58MXx8fHwxNzU2MTIzNjMzfDA&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Explore Norwegian culture, stunning fjords, and modern Scandinavian design in this beautiful Nordic capital.',
+ earlyBirdDiscount: '15%'
+ },
+ {
+ id: 4,
+ name: 'Marrakech',
+ country: 'Morocco',
+ launchDate: 'December 2025',
+ season: 'Winter',
+ expectedAttractions: 70,
+ specialFeature: 'Desert Adventure',
+ status: 'coming-soon',
+ image: 'https://images.unsplash.com/photo-1589008207338-a9c80f8a2d23?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtYXJyYWtlY2glMjBtb3JvY2NvJTIwbWVkaW5hfGVufDF8fHx8MTc1NjEyMzYzNnww&ixlib=rb-4.1.0&q=80&w=1080',
+ description: 'Immerse yourself in the imperial city\'s vibrant souks, stunning palaces, and Sahara desert adventures.',
+ earlyBirdDiscount: '30%'
+ }
+];
+
+const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'launching-soon':
+ return 'bg-green-100 text-green-800 border-green-200';
+ case 'in-development':
+ return 'bg-blue-100 text-blue-800 border-blue-200';
+ case 'planning':
+ return 'bg-yellow-100 text-yellow-800 border-yellow-200';
+ case 'coming-soon':
+ return 'bg-purple-100 text-purple-800 border-purple-200';
+ default:
+ return 'bg-gray-100 text-gray-800 border-gray-200';
+ }
+};
+
+const getStatusText = (status: string) => {
+ switch (status) {
+ case 'launching-soon':
+ return 'Launching Soon';
+ case 'in-development':
+ return 'In Development';
+ case 'planning':
+ return 'Planning Stage';
+ case 'coming-soon':
+ return 'Coming Soon';
+ default:
+ return 'Upcoming';
+ }
+};
+
+export function UpcomingDestinations() {
+ return (
+
+
+ {/* Header */}
+
+
+
+ New Destinations Coming
+
+
+
+ Upcoming {' '}
+
+ Destinations
+
+
+
+ Be the first to explore these incredible new destinations. Get early access and exclusive discounts when they launch.
+
+
+
+ {/* Destinations Grid */}
+
+ {upcomingDestinations.map((destination, index) => (
+
+
+ {/* Image Section */}
+
+
+
+ {/* Overlay */}
+
+
+ {/* Status Badge */}
+
+ {getStatusText(destination.status)}
+
+
+ {/* Discount Badge */}
+ {destination.earlyBirdDiscount && (
+
+ -{destination.earlyBirdDiscount} Early Bird
+
+ )}
+
+
+ {/* Content Section */}
+
+ {/* Header */}
+
+
{destination.name}
+
{destination.country}
+
+ {destination.description}
+
+
+
+ {/* Stats */}
+
+
+
+
+ Launch Date
+
+
{destination.launchDate}
+
+
+
+
+
+ Attractions
+
+
{destination.expectedAttractions}+ planned
+
+
+
+
+
+ Special Feature
+
+
{destination.specialFeature}
+
+
+
+ {/* CTA Buttons */}
+
+
+
+ Notify Me
+
+
+
+
+
+ Join Waitlist
+
+
+
+
+
+ ))}
+
+
+ {/* Newsletter Signup */}
+
+ Be the First to Explore
+
+ Subscribe to get exclusive early access, special discounts, and be notified the moment new destinations launch.
+
+
+
+
+
+ Subscribe
+
+
+
+
+ Join 50,000+ travelers who get first access to new destinations
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/VarietyOfAdventures.tsx b/src/components/VarietyOfAdventures.tsx
new file mode 100644
index 0000000..ee9e045
--- /dev/null
+++ b/src/components/VarietyOfAdventures.tsx
@@ -0,0 +1,358 @@
+import { Coffee, Palette, Trees, UtensilsCrossed, Music, Building2, Ship, ShoppingBag } from 'lucide-react';
+import { Button } from './ui/button';
+import { motion, AnimatePresence } from 'motion/react';
+import { useState } from 'react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+
+export function VarietyOfAdventures() {
+ const [hoveredCategory, setHoveredCategory] = useState(null);
+
+ const melbourneCategories = [
+ {
+ id: 'street-art',
+ title: 'Street Art & Laneways',
+ tourCount: '12+ tours',
+ icon: Palette,
+ image: 'https://images.unsplash.com/photo-1613910774524-0651750373ec?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBzdHJlZXQlMjBhcnQlMjBncmFmZml0aSUyMGxhbmV3YXlzfGVufDF8fHx8MTc1NjEwNTYwOHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Hosier Lane',
+ image: 'https://images.unsplash.com/photo-1582076197789-5c2af0bb51fd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxob3NpZXIlMjBsYW5lJTIwbWVsYm91cm5lJTIwc3RyZWV0JTIwYXJ0fGVufDF8fHx8MTc1NjEwNjExMnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'AC/DC Lane',
+ image: 'https://images.unsplash.com/photo-1735704197205-823cfd615e13?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhYyUyMGRjJTIwbGFuZSUyMG1lbGJvdXJuZSUyMGdyYWZmaXRpfGVufDF8fHx8MTc1NjEwNjExN3ww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'coffee-culture',
+ title: 'Coffee Culture',
+ tourCount: '8+ experiences',
+ icon: Coffee,
+ image: 'https://images.unsplash.com/photo-1681745623555-efc392301d6d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBjb2ZmZWUlMjBjdWx0dXJlJTIwY2FmZSUyMGJhcmlzdGF8ZW58MXx8fHwxNzU2MTA1NjEyfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Degraves Street',
+ image: 'https://images.unsplash.com/photo-1686052183140-088f1bd9a49b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxkZWdyYXZlcyUyMHN0cmVldCUyMG1lbGJvdXJuZSUyMGNhZmV8ZW58MXx8fHwxNzU2MTA2MTIzfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'Centre Place',
+ image: 'https://images.unsplash.com/photo-1583569695977-1e5758f793d1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjZW50cmUlMjBwbGFjZSUyMG1lbGJvdXJuZSUyMGNvZmZlZSUyMGxhbmV3YXl8ZW58MXx8fHwxNzU2MTA2MTI3fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'gardens-parks',
+ title: 'Gardens & Parks',
+ tourCount: '6+ nature spots',
+ icon: Trees,
+ image: 'https://images.unsplash.com/photo-1639481326289-1efe7cbfbfe5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zJTIwbmF0dXJlfGVufDF8fHx8MTc1NjEwNTYxNnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Royal Botanic Gardens',
+ image: 'https://images.unsplash.com/photo-1670027537688-77def132d556?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zJTIwbWVsYm91cm5lJTIwbGFrZXxlbnwxfHx8fDE3NTYxMDYxMzN8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'Fitzroy Gardens',
+ image: 'https://images.unsplash.com/photo-1735605918618-0193db0a30af?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmaXR6cm95JTIwZ2FyZGVucyUyMG1lbGJvdXJuZSUyMGNvbnNlcnZhdG9yeXxlbnwxfHx8fDE3NTYxMDYxMzl8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'food-markets',
+ title: 'Food & Markets',
+ tourCount: '10+ food spots',
+ icon: UtensilsCrossed,
+ image: 'https://images.unsplash.com/photo-1656177796132-3dab16b30652?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBxdWVlbiUyMHZpY3RvcmlhJTIwbWFya2V0JTIwZm9vZHxlbnwxfHx8fDE3NTYxMDU2MTl8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Queen Victoria Market',
+ image: 'https://images.unsplash.com/photo-1708903965305-f8439248cebd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxxdWVlbiUyMHZpY3RvcmlhJTIwbWFya2V0JTIwbWVsYm91cm5lJTIwZm9vZCUyMHN0YWxsc3xlbnwxfHx8fDE3NTYxMDYxNDV8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'South Melbourne Market',
+ image: 'https://images.unsplash.com/photo-1749229964993-f802d5203142?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzb3V0aCUyMG1lbGJvdXJuZSUyMG1hcmtldCUyMGZvb2QlMjB2ZW5kb3JzfGVufDF8fHx8MTc1NjEwNjE1MXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'music-entertainment',
+ title: 'Music & Entertainment',
+ tourCount: '15+ venues',
+ icon: Music,
+ image: 'https://images.unsplash.com/photo-1684679106461-dae134df8da6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBtdXNpYyUyMGxpdmUlMjBjb25jZXJ0JTIwdmVudWV8ZW58MXx8fHwxNzU2MTA1NjI1fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Princess Theatre',
+ image: 'https://images.unsplash.com/photo-1709063370226-1369f8d26069?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxwcmluY2VzcyUyMHRoZWF0cmUlMjBtZWxib3VybmUlMjBoaXN0b3JpYyUyMHZlbnVlfGVufDF8fHx8MTc1NjEwNjE1N3ww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'Rod Laver Arena',
+ image: 'https://images.unsplash.com/photo-1684679106461-dae134df8da6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBtdXNpYyUyMGxpdmUlMjBjb25jZXJ0JTIwdmVudWV8ZW58MXx8fHwxNzU2MTA1NjI1fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'architecture',
+ title: 'Historic Architecture',
+ tourCount: '9+ heritage sites',
+ icon: Building2,
+ image: 'https://images.unsplash.com/photo-1719447001523-7474ec81ef80?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhcmNoaXRlY3R1cmUlMjBoaXN0b3JpYyUyMGJ1aWxkaW5nc3xlbnwxfHx8fDE3NTYxMDU2Mjl8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Block Arcade',
+ image: 'https://images.unsplash.com/photo-1695657678988-5bd451215eb4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxibG9jayUyMGFyY2FkZSUyMG1lbGJvdXJuZSUyMGhlcml0YWdlJTIwc2hvcHBpbmd8ZW58MXx8fHwxNzU2MTA2MTYxfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'St Paul\'s Cathedral',
+ image: 'https://images.unsplash.com/photo-1719447001523-7474ec81ef80?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhcmNoaXRlY3R1cmUlMjBoaXN0b3JpYyUyMGJ1aWxkaW5nc3xlbnwxfHx8fDE3NTYxMDU2Mjl8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'river-cruises',
+ title: 'River & Cruises',
+ tourCount: '5+ water experiences',
+ icon: Ship,
+ image: 'https://images.unsplash.com/photo-1722943661451-1a439d092ff2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByaXZlcnNpZGUlMjB5YXJyYSUyMHJpdmVyJTIwY3J1aXNlfGVufDF8fHx8MTc1NjEwNTYzM3ww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Yarra River Cruise',
+ image: 'https://images.unsplash.com/photo-1668376212180-d4c25f929ce4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx5YXJyYSUyMHJpdmVyJTIwbWVsYm91cm5lJTIwY3J1aXNlJTIwYm9hdHN8ZW58MXx8fHwxNzU2MTA2MTY1fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'Southbank Promenade',
+ image: 'https://images.unsplash.com/photo-1722943661451-1a439d092ff2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByaXZlcnNpZGUlMjB5YXJyYSUyMHJpdmVyJTIwY3J1aXNlfGVufDF8fHx8MTc1NjEwNTYzM3ww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ },
+ {
+ id: 'shopping',
+ title: 'Shopping & Style',
+ tourCount: '7+ shopping areas',
+ icon: ShoppingBag,
+ image: 'https://images.unsplash.com/photo-1744357725934-12140170ebf3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBzaG9wcGluZyUyMGNvbGxpbnMlMjBzdHJlZXQlMjBib3V0aXF1ZXxlbnwxfHx8fDE3NTYxMDU2NDV8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+ attractions: [
+ {
+ name: 'Collins Street',
+ image: 'https://images.unsplash.com/photo-1583569695977-1e5758f793d1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjb2xsaW5zJTIwc3RyZWV0JTIwbWVsYm91cm5lJTIwYm91dGlxdWUlMjBzaG9wcGluZ3xlbnwxfHx8fDE3NTYxMDYxNjl8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ },
+ {
+ name: 'Chapel Street',
+ image: 'https://images.unsplash.com/photo-1744357725934-12140170ebf3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBzaG9wcGluZyUyMGNvbGxpbnMlMjBzdHJlZXQlMjBib3V0aXF1ZXxlbnwxfHx8fDE3NTYxMDU2NDV8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral'
+ }
+ ]
+ }
+ ];
+
+ // Create extended array for seamless infinite scroll
+ const extendedCategories = [...melbourneCategories, ...melbourneCategories, ...melbourneCategories];
+
+ return (
+
+
+ {/* Header */}
+
+
+ A Melbourne {' '}
+ Experience {' '}
+ for Every Traveller
+
+
+ From iconic laneways and world-class coffee to stunning gardens and vibrant markets,
+ discover Melbourne's unique character through curated experiences that showcase the city's soul.
+
+
+
+ {/* Full-Width Horizontal Scrolling Carousel */}
+
+ {/* Carousel Container - Full Width */}
+
+ {/* Scrolling Track */}
+
+ {/* Adventure Category Cards */}
+ {extendedCategories.map((category, index) => (
+ setHoveredCategory(`${category.id}-${index}`)}
+ onMouseLeave={() => setHoveredCategory(null)}
+ whileHover={{ scale: 1.02, y: -4 }}
+ transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
+ >
+ {/* Card Content - New Design */}
+
+ {/* Full Background Image */}
+
+
+ {/* Dark Overlay */}
+
+
+
+ {/* Bottom Content Card */}
+
+
+
+ {/* Text Content */}
+
+
+ {category.title}
+
+
+ {category.tourCount}
+
+
+
+ {/* Icon */}
+
+
+
+
+
+
+
+ {/* Subtle Animation Effect */}
+
+
+
+ {/* Popup Card - Attractions */}
+
+ {hoveredCategory === `${category.id}-${index}` && (
+
+ {/* Popup Content */}
+
+ {/* Header */}
+
+
+
+
+
+
+
{category.title}
+
Featured Attractions
+
+
+
+
+ {/* Attractions Grid */}
+
+ {category.attractions.map((attraction, idx) => (
+
+ {/* Attraction Image */}
+
+
+
+
+ {/* Attraction Info */}
+
+
+ {attraction.name}
+
+
+
+
+ Included in Pass
+
+
+
+
+ ))}
+
+
+ {/* Footer */}
+
+
+
+ + Many more attractions included
+
+
+
+
+
+ )}
+
+
+ ))}
+
+
+ {/* Gradient Fade Edges */}
+
+
+
+
+
+ {/* CTA Button */}
+
+
+ Get A City Card And Start Exploring
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/WhyChooseCityCards.tsx b/src/components/WhyChooseCityCards.tsx
new file mode 100644
index 0000000..8551ef1
--- /dev/null
+++ b/src/components/WhyChooseCityCards.tsx
@@ -0,0 +1,173 @@
+import { motion } from 'motion/react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+
+export function WhyChooseCityCards() {
+ return (
+
+
+ {/* Header */}
+
+
+
+ Save {' '}
+ Big {' '}
+ on attractions
+
+
+ Why pay $350+ buying tickets individually when Melbourne City Card gives you 40+ attractions for as little as $199?
+
+
+
+ {/* Main Content */}
+
+ {/* Polaroid Cards Layout */}
+
+
+ {/* Melbourne Card */}
+
+
+ {/* Image */}
+
+
+
+
+ {/* Polaroid Caption */}
+
+
Melbourne
+
+
+ Individual tickets:
+ $350+
+
+
+ City Card:
+ $199
+
+
+
+ Save $151+
+
+
+
+
+
+
+ {/* Decorative elements */}
+
+
+
+
+ {/* Sydney Card */}
+
+
+ {/* Image */}
+
+
+
+
+ {/* Polaroid Caption */}
+
+
Sydney
+
+
+ Individual tickets:
+ $420+
+
+
+ City Card:
+ $249
+
+
+
+ Save $171+
+
+
+
+
+
+
+ {/* Decorative elements */}
+
+
+
+
+
+ {/* Bottom Features */}
+
+
+
+ Why {' '}
+ CityCards ?
+
+
+
+
+
+ 💰
+
+
Massive Savings
+
Save up to 50% compared to individual attraction tickets
+
+
+
+
+ ⚡
+
+
Skip the Lines
+
Fast-track entry to popular attractions and experiences
+
+
+
+
+ 📱
+
+
Digital Convenience
+
Everything on your phone - no physical tickets needed
+
+
+
+
+ Start Saving Today
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/figma/ImageWithFallback.tsx b/src/components/figma/ImageWithFallback.tsx
new file mode 100644
index 0000000..0e26139
--- /dev/null
+++ b/src/components/figma/ImageWithFallback.tsx
@@ -0,0 +1,27 @@
+import React, { useState } from 'react'
+
+const ERROR_IMG_SRC =
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg=='
+
+export function ImageWithFallback(props: React.ImgHTMLAttributes) {
+ const [didError, setDidError] = useState(false)
+
+ const handleError = () => {
+ setDidError(true)
+ }
+
+ const { src, alt, style, className, ...rest } = props
+
+ return didError ? (
+
+
+
+
+
+ ) : (
+
+ )
+}
diff --git a/src/components/temp_delete_melbourne.txt b/src/components/temp_delete_melbourne.txt
new file mode 100644
index 0000000..4db5bf9
--- /dev/null
+++ b/src/components/temp_delete_melbourne.txt
@@ -0,0 +1 @@
+Placeholder file to test deletion
\ No newline at end of file
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..aa2c37b
--- /dev/null
+++ b/src/components/ui/accordion.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion@1.2.3";
+import { ChevronDownIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ );
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..68f3605
--- /dev/null
+++ b/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog@1.1.6";
+
+import { cn } from "./utils";
+import { buttonVariants } from "./button";
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ );
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000..856b94d
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..2a2f462
--- /dev/null
+++ b/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio@1.1.2";
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { AspectRatio };
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..589b166
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar@1.1.3";
+
+import { cn } from "./utils";
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 0000000..3f8eff8
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..d2adf98
--- /dev/null
+++ b/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { ChevronRight, MoreHorizontal } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return ;
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ );
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+
+ );
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ );
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..aacb468
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,72 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl font-poppins font-semibold transition-all duration-300 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2 focus-visible:ring-4 focus-visible:ring-primary/10",
+ {
+ variants: {
+ variant: {
+ default: "bg-gradient-to-r from-primary to-secondary text-white hover:from-primary/90 hover:to-secondary/90 shadow-lg hover:shadow-xl",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 shadow-lg hover:shadow-xl",
+ outline:
+ "border border-primary bg-transparent text-primary hover:bg-primary hover:text-white font-medium",
+ secondary:
+ "bg-gray-900 text-white hover:bg-primary font-medium",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline font-medium",
+ shine: "relative bg-gradient-to-r from-primary to-secondary text-white font-semibold shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105 overflow-hidden group [&>span]:text-white [&>span]:font-semibold",
+ },
+ size: {
+ default: "px-8 py-4 text-base",
+ sm: "px-6 py-3 text-sm rounded-lg",
+ lg: "px-8 py-4 text-lg",
+ xl: "px-10 py-5 text-lg",
+ icon: "size-10 rounded-lg",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+const Button = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ withShine?: boolean;
+ }
+>(({ className, variant, size, asChild = false, withShine = false, children, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+
+ const buttonContent = withShine ? (
+ <>
+ {children}
+ {/* Shine Wave Animation */}
+
+ >
+ ) : children;
+
+ return (
+
+ {buttonContent}
+
+ );
+});
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
\ No newline at end of file
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..b30236f
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react@0.487.0";
+import { DayPicker } from "react-day-picker@8.10.1";
+
+import { cn } from "./utils";
+import { buttonVariants } from "./button";
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+ : "[&:has([aria-selected])]:rounded-md",
+ ),
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "size-8 p-0 font-normal aria-selected:opacity-100",
+ ),
+ day_range_start:
+ "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_range_end:
+ "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside:
+ "day-outside text-muted-foreground aria-selected:text-muted-foreground",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_range_middle:
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ className, ...props }) => (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+
+export { Calendar };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000..5f9d58a
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import { cn } from "./utils";
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx
new file mode 100644
index 0000000..2752b36
--- /dev/null
+++ b/src/components/ui/carousel.tsx
@@ -0,0 +1,241 @@
+"use client";
+
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react@8.6.0";
+import { ArrowLeft, ArrowRight } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+import { Button } from "./button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: "horizontal" | "vertical";
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins,
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext],
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx
new file mode 100644
index 0000000..d558e19
--- /dev/null
+++ b/src/components/ui/chart.tsx
@@ -0,0 +1,353 @@
+"use client";
+
+import * as React from "react";
+import * as RechartsPrimitive from "recharts@2.15.2";
+
+import { cn } from "./utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error("useChart must be used within a ");
+ }
+
+ return context;
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<"div"> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"];
+}) {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+