From 90f0cb953fde856bb7f1af435034c7a3eabf5371 Mon Sep 17 00:00:00 2001 From: priyanshuvish Date: Tue, 28 Oct 2025 19:35:56 +0530 Subject: [PATCH] revamp how it works --- src/components/HowItWorksPage.tsx | 645 ++++++++++++++---------- src/components/MelbournePage.tsx | 192 ++++--- src/components/Navbar.tsx | 2 +- src/components/PersonalizedTourHero.tsx | 268 ++++++++++ 4 files changed, 755 insertions(+), 352 deletions(-) create mode 100644 src/components/PersonalizedTourHero.tsx diff --git a/src/components/HowItWorksPage.tsx b/src/components/HowItWorksPage.tsx index 5167423..940ef38 100644 --- a/src/components/HowItWorksPage.tsx +++ b/src/components/HowItWorksPage.tsx @@ -1,17 +1,14 @@ import { useState, useEffect, useRef } from 'react'; -import { motion } from 'motion/react'; -import { Check, ChevronRight, MapPin, Calendar, Smartphone, CreditCard } from 'lucide-react'; +import { motion, useInView } from 'motion/react'; import { Button } from './ui/button'; -import { Card, CardContent } from './ui/card'; -import { Badge } from './ui/badge'; import Navbar from './Navbar'; -// import { CitySubmenu } from './CitySubmenu'; +import { Footer } from './Footer'; +import { ImageWithFallback } from './figma/ImageWithFallback'; import { AttractionHassleFreeSection } from './AttractionHassleFreeSection'; import { MobileAppSection } from './MobileAppSection'; import { WhyChooseCityCards } from './WhyChooseCityCards'; import { EnhancedTestimonials } from './EnhancedTestimonials'; import { ReviewsSection } from './ReviewsSection'; -import { Footer } from './Footer'; interface User { email: string; @@ -40,48 +37,229 @@ interface HowItWorksPageProps { onContactUsClick?: () => void; onEsimsClick?: () => void; onHotelDiscountsClick?: () => void; + onSuperSavingsClick?: () => void; currentPage: string; user: User | null; } -interface StepItem { - id: string; +interface Step { + number: string; title: string; description: string; - icon: React.ReactNode; - isMainStep: boolean; - parentStep?: string; - image?: string; - features?: string[]; + images: string[]; } -const steps: StepItem[] = [ +const steps: Step[] = [ { - id: 'pick-pass', - title: 'Pick Your Pass', - description: 'Choose between our Selective or Unlimited pass options. Each designed to match your travel style and give you the freedom to explore Melbourne your way.', - icon: , - isMainStep: true, - features: ['Selective or Unlimited options', 'Instant digital delivery', 'Skip-the-line access', 'Mobile-friendly tickets'] + number: '1', + title: 'Choose Your Pass', + description: 'Select from our Unlimited or Selective CityCard options. Get instant digital delivery with access to 50+ top Melbourne attractions. Skip the lines and start exploring right away.', + images: [ + 'https://images.unsplash.com/photo-1567748534085-467f8a8a475d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0cmF2ZWwlMjB0aWNrZXRzJTIwcGFzc3xlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080', + 'https://images.unsplash.com/photo-1743017784681-ba0cb69e2ead?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtb2JpbGUlMjBwaG9uZSUyMHRyYXZlbCUyMGJvb2tpbmd8ZW58MXx8fHwxNzYxMTE2ODM2fDA&ixlib=rb-4.1.0&q=80&w=1080', + 'https://images.unsplash.com/photo-1677200922658-d0df5b2ac91e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBhdHRyYWN0aW9uc3xlbnwxfHx8fDE3NjExMTY4Mzh8MA&ixlib=rb-4.1.0&q=80&w=1080' + ] }, { - id: 'plan-itinerary', + number: '2', title: 'Plan Your Journey', - description: 'Use our smart planning tools to create your perfect Melbourne itinerary. Get personalized recommendations based on your interests and available time.', - icon: , - isMainStep: true, - features: ['AI-powered suggestions', 'Real-time crowd updates', 'Weather-based planning', 'Flexible scheduling'] + description: 'Use our AI-powered Magic Itinerary planner to create your perfect Melbourne experience. Get personalized recommendations, optimized routes, and real-time updates tailored to your interests.', + images: [ + 'https://images.unsplash.com/photo-1759802524049-2421ddaee0fe?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjaXR5JTIwbWFwJTIwcGxhbm5pbmd8ZW58MXx8fHwxNzYxMTE2ODM3fDA&ixlib=rb-4.1.0&q=80&w=1080', + 'https://images.unsplash.com/photo-1741721816773-ff31d089c227?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzbWFydHBob25lJTIwYXBwJTIwdHJhdmVsfGVufDF8fHx8MTc2MTExNjgzOHww&ixlib=rb-4.1.0&q=80&w=1080' + ] }, { - id: 'explore-enjoy', - title: 'Explore & Enjoy', - description: 'Experience Melbourne like never before with our mobile app, offline maps, and 24/7 support. Every moment is designed to be effortless and memorable.', - icon: , - isMainStep: true, - features: ['Mobile app included', 'Offline map access', '24/7 customer support', 'Audio guide content'] + number: '3', + title: 'Explore Melbourne', + description: 'Download the mobile app and start your adventure. Access offline maps, audio guides, and your digital tickets all in one place. Experience Melbourne like never before with 24/7 support at your fingertips.', + images: [ + 'https://images.unsplash.com/photo-1552333709-c465f89a4dfd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0b3VyaXN0JTIwZXhwbG9yaW5nJTIwY2l0eXxlbnwxfHx8fDE3NjExMTY4Mzd8MA&ixlib=rb-4.1.0&q=80&w=1080' + ] } ]; +// Step Component +function StepSection({ step, index, onInView }: { step: Step; index: number; onInView: (index: number) => void }) { + const ref = useRef(null); + const isInView = useInView(ref, { + margin: '-25% 0px -25% 0px', + amount: 0.3 + }); + + // Notify parent when this step comes into view + useEffect(() => { + if (isInView) { + onInView(index); + } + }, [isInView, index, onInView]); + + // Rotation angles for diagonal effect + const rotations = [-3, 3, -2]; + const rotation = rotations[index]; + + return ( +
+ +
+ {/* Text Content */} + +
+ + {step.number}. + {step.title} + + + {step.description} + +
+
+ + {/* Image Grid with Diagonal Rotation */} + + {/* Pin on top */} +
+
+
+
+
+
+
+
+
+
+ + + + {index === 0 && ( +
+
+ +
+
+ )} + + {index === 1 && ( +
+
+ +
+
+ )} + + {index === 2 && ( +
+
+ +
+
+ )} + + {/* Active Glow */} + {isInView && ( + + )} + +
+
+
+ +
+ ); +} + export function HowItWorksPage({ onHomeClick, onMelbourneClick, @@ -103,67 +281,38 @@ export function HowItWorksPage({ onContactUsClick, onEsimsClick, onHotelDiscountsClick, + onSuperSavingsClick, currentPage, user }: HowItWorksPageProps) { - const [activeStep, setActiveStep] = useState('pick-pass'); const [scrollProgress, setScrollProgress] = useState(0); - const mainSteps = steps; // All steps are now main steps - const stepRefs = useRef<(HTMLDivElement | null)[]>([]); - const containerRef = useRef(null); + const [activeStep, setActiveStep] = useState(0); - // Scroll-based step activation useEffect(() => { const handleScroll = () => { - if (!containerRef.current) return; - - const scrollY = window.scrollY; - const windowHeight = window.innerHeight; - const containerTop = containerRef.current.offsetTop; - const containerHeight = containerRef.current.offsetHeight; - - // Calculate trigger points for each step (when step indicator is in center of viewport) - const stepElements = stepRefs.current.filter(Boolean); - let currentStepIndex = 0; - let minDistance = Infinity; - - stepElements.forEach((stepEl, index) => { - if (stepEl) { - const stepTop = stepEl.offsetTop + containerTop; - const stepCenter = stepTop + stepEl.offsetHeight / 2; - const viewportCenter = scrollY + windowHeight / 2; - const distance = Math.abs(stepCenter - viewportCenter); - - if (distance < minDistance) { - minDistance = distance; - currentStepIndex = index; - } - } - }); - - // Update active step - const newActiveStep = mainSteps[currentStepIndex]?.id || 'pick-pass'; - if (newActiveStep !== activeStep) { - setActiveStep(newActiveStep); - } - - // Calculate smooth progress through entire stepper - const sectionStart = containerTop - windowHeight / 2; - const sectionEnd = containerTop + containerHeight - windowHeight / 2; - const rawProgress = (scrollY - sectionStart) / (sectionEnd - sectionStart); - const progress = Math.max(0, Math.min(1, rawProgress)); - + const totalHeight = document.documentElement.scrollHeight - window.innerHeight; + const progress = (window.scrollY / totalHeight) * 100; setScrollProgress(progress); }; - window.addEventListener('scroll', handleScroll, { passive: true }); - handleScroll(); // Initial call - + window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); - }, [activeStep, mainSteps]); + }, []); + + // Callback to update active step + const handleStepInView = (index: number) => { + setActiveStep(index); + }; + + // Calculate path length based on active step + const getPathLength = () => { + if (activeStep === 0) return 0.33; + if (activeStep === 1) return 0.66; + return 1.0; + }; return ( -
+
{/* Navigation */} - - {/* {}} - onHomeClick={onHomeClick} - onMelbourneClick={onMelbourneClick} - onAttractionsClick={onAttractionsClick} - onPassesClick={onPassesClick} - onBlogsClick={onBlogsClick} - onHowItWorksClick={onHowItWorksClick} - /> */} -
- {/* Page Header */} - -

- How{' '} - - It Works - -

-

- Discover the simple process that transforms your city exploration from ordinary to extraordinary -

-
- - {/* Vertical Stepper with Alternating Layout */} -
- {/* Central Progress Line */} -
- {/* Progress Fill */} - + {/* Curved Flight Path SVG - Desktop Only */} +
+ + {/* Main curved flight path - Animated with scroll */} + - {/* Ellipse at the end when progress reaches 100% */} - {scrollProgress >= 1 && ( - + + {/* Shadow path for depth - follows main path */} + + + {/* 3D Pin marker at the end - only shows when all steps complete */} + {activeStep === 2 && ( + + {/* Pin shadow */} + - )} - -
- - {/* Steps Container */} -
- {mainSteps.map((step, index) => { - const isActive = activeStep === step.id; - const isLeft = index % 2 === 0; // Step 1 & 3 on left, Step 2 on right - const stepNumber = `0${index + 1}`; - - return ( - stepRefs.current[index] = el} - className="relative" - initial={{ opacity: 0, y: 50 }} - animate={{ opacity: 1, y: 0 }} - transition={{ duration: 0.6, delay: index * 0.2 }} - > - {/* Step Indicator Circle - Always in center */} - -
- {step.icon} -
-
- - {/* Content Container - Zigzag Layout */} -
- {/* Content Card */} - - - - {/* Step Number */} - - {stepNumber} - - - {/* Step Title */} -

- {step.title} -

- - {/* Step Description */} -

- {step.description} -

- - {/* Features List */} - {step.features && ( -
- {step.features.map((feature, featureIndex) => ( - -
- -
- {feature} -
- ))} -
- )} - - {/* Action Button */} - {isActive && ( - - - - )} -
-
-
- - {/* Spacer for the opposite side */} -
-
- - ); - })} -
+ + {/* Pin body/stick - gradient effect */} + + + {/* Pin head - larger and more prominent */} + + + {/* Pin head border */} + + + {/* Shine effect on pin head */} + + + {/* Inner glow */} + + + {/* Checkmark on pin */} + + + {/* Define gradients */} + + + + + + + + + + + + + )} +
+
+ {/* Header */} + +

+ How it{' '} + works +

+
+ + {/* Steps Container */} +
+ {steps.map((step, index) => ( + + ))} +
+
+ + + + + {/* Additional Sections */} +
{/* Make the most of every attraction section */} @@ -403,4 +540,4 @@ export function HowItWorksPage({ ); } -export default HowItWorksPage; \ No newline at end of file +export default HowItWorksPage; diff --git a/src/components/MelbournePage.tsx b/src/components/MelbournePage.tsx index 07f56cb..bbbc09b 100644 --- a/src/components/MelbournePage.tsx +++ b/src/components/MelbournePage.tsx @@ -14,6 +14,7 @@ import { MobileAppPromotion } from './MobileAppPromotion'; import { MelbourneFAQ } from './MelbourneFAQ'; import { Footer } from './Footer'; import { Layout } from '../Layout'; +import { PersonalizedTourHero } from './PersonalizedTourHero'; interface User { email: string; @@ -21,7 +22,7 @@ interface User { } // Hero Banner Carousel Component -function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHotelDiscountsClick }: { +function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHotelDiscountsClick }: { onCheckoutClick?: () => void; onPassesClick?: () => void; onEsimsClick?: () => void; @@ -89,7 +90,7 @@ function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHo }; return ( -
setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} @@ -110,7 +111,7 @@ function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHo {/* Left Side - Content Panel (60% width) */} - +

{slide.description}

- + +
+
+
+
+ ); +}