Загрузка...

Лендинг для компенсации задержки рейса. Минималистичный дизайн, расчет компенсации, информация о задержках, отзывы. Для защиты путешествий.
# SkyVoloLanding
You are given a task to integrate an existing React component in the codebase
~~~/src/App.tsx
/**
* SkyVolo - Flight Compensation Landing Page
* Main App entry point that composes all sections.
* AirHelp-style flight compensation service landing page.
*/
import React from 'react';
import './styles.css';
import { Nav } from './Nav';
import { Hero } from './Hero';
import { TrustBanner } from './TrustBanner';
import { Stats } from './Stats';
import { HowItWorks } from './HowItWorks';
import { Compensation } from './Compensation';
import { Disruptions } from './Disruptions';
import { Offers } from './Offers';
import { Reviews } from './Reviews';
import { Pricing } from './Pricing';
import { FAQ } from './FAQ';
import { CTA } from './CTA';
import { Footer } from './Footer';
export default function App() {
return (
<div className="min-h-screen" style={{ background: 'white' }}>
<Nav />
<Hero />
<TrustBanner />
<Stats />
<HowItWorks />
<Compensation />
<Disruptions />
<Offers />
<Reviews />
<Pricing />
<FAQ />
<CTA />
<Footer />
</div>
);
}
~~~
~~~/src/CTA.tsx
/**
* SkyVolo Call-to-Action Banner
* Full-width gradient banner with compelling headline and CTA button.
* Includes decorative floating elements and glass-morphism accents.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { ArrowRight, Plane, Shield } from 'lucide-react';
export function CTA() {
return (
<section className="py-24 px-6 relative overflow-hidden gradient-hero">
{/* Decorative elements */}
<div className="absolute inset-0 pointer-events-none">
<motion.div
animate={{ y: [0, -20, 0], rotate: [0, 5, 0] }}
transition={{ duration: 15, repeat: Infinity, ease: 'easeInOut' }}
className="absolute top-[20%] right-[15%] w-[200px] h-[200px] rounded-full opacity-15"
style={{ background: 'radial-gradient(circle, rgba(255,255,255,0.3) 0%, transparent 70%)' }}
/>
<motion.div
animate={{ y: [0, 15, 0] }}
transition={{ duration: 12, repeat: Infinity, ease: 'easeInOut' }}
className="absolute bottom-[20%] left-[10%] w-[150px] h-[150px] rounded-full opacity-10"
style={{ background: 'radial-gradient(circle, rgba(255,255,255,0.4) 0%, transparent 70%)' }}
/>
</div>
<div className="max-w-[700px] mx-auto text-center relative z-10">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-6"
style={{ background: 'rgba(255,255,255,0.15)', border: '1px solid rgba(255,255,255,0.2)' }}
>
<Shield size={14} style={{ color: 'var(--gold-400)' }} />
<span className="text-white/90 text-[13px] font-medium">No win, no fee guarantee</span>
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[48px] leading-[1.1] font-bold text-white tracking-[-0.02em] mb-5"
style={{ fontFamily: 'var(--font-display)' }}
>
Don't leave money
<br />
on the runway
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[17px] leading-relaxed mb-10 max-w-[480px] mx-auto"
style={{ color: 'rgba(255,255,255,0.75)' }}
>
Check if your disrupted flight qualifies for up to €600 in compensation. It only takes 3 minutes.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
className="flex flex-col sm:flex-row items-center justify-center gap-4"
>
<a
href="#hero"
className="inline-flex items-center gap-2 px-8 py-4 rounded-full font-semibold text-[15px] transition-all duration-300 hover:scale-[1.02] active:scale-[0.98]"
style={{
background: 'white',
color: 'var(--sky-700)',
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
}}
>
Check My Flight
<ArrowRight size={16} />
</a>
<span className="text-[13px] font-medium" style={{ color: 'rgba(255,255,255,0.5)' }}>
Free eligibility check · No obligations
</span>
</motion.div>
{/* Trust marks */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.5 }}
className="flex items-center justify-center gap-6 mt-12"
style={{ color: 'rgba(255,255,255,0.4)' }}
>
<div className="flex items-center gap-2 text-[12px] font-medium">
<Plane size={14} />
<span>2M+ claims processed</span>
</div>
<div className="w-1 h-1 rounded-full" style={{ background: 'rgba(255,255,255,0.3)' }} />
<div className="flex items-center gap-2 text-[12px] font-medium">
<Shield size={14} />
<span>98% success rate</span>
</div>
</motion.div>
</div>
</section>
);
}
export default CTA;
~~~
~~~/src/FAQ.tsx
/**
* SkyVolo FAQ Section
* Accordion-style frequently asked questions with smooth expand/collapse.
* Clean minimal layout with animated reveal and plus/minus toggle icons.
*/
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Plus, Minus, HelpCircle } from 'lucide-react';
const faqs = [
{
q: 'How much does it cost to use SkyVolo?',
a: 'Our eligibility check is completely free. If you choose to proceed with a claim, we charge a 25% success fee — meaning you only pay if we win your case. There are no upfront costs or hidden fees.',
},
{
q: 'Which flights are eligible for compensation?',
a: 'Flights departing from any EU airport, or arriving in the EU on an EU-based airline, are covered under EU Regulation 261/2004. This includes delays of 3+ hours, cancellations with less than 14 days notice, and denied boarding due to overbooking.',
},
{
q: 'How long does the claim process take?',
a: 'Most claims are resolved within 2-12 weeks, depending on the airline. Some airlines respond quickly, while others may require escalation to legal proceedings. We keep you updated at every step.',
},
{
q: 'What if the airline refuses to pay?',
a: 'Our legal team handles all negotiations and, if necessary, will take the case to court at no additional cost to you. We have a 98% success rate, including cases that go to court.',
},
{
q: 'Can I claim for a flight from last year?',
a: 'Yes! Depending on the country, you can claim for flights up to 3-6 years in the past. Enter your flight details and we\'ll instantly tell you if your flight is still eligible.',
},
{
q: 'What documents do I need?',
a: 'Usually just your booking confirmation or e-ticket. We handle all the paperwork and correspondence with the airline. If any additional documents are needed, we\'ll guide you through the process.',
},
{
q: 'What counts as an "extraordinary circumstance"?',
a: 'Airlines can deny compensation for events truly beyond their control, such as severe weather, political instability, or air traffic control restrictions. However, technical problems and staff shortages do NOT count — and airlines often incorrectly cite these as excuses.',
},
];
function FAQItem({ faq, isOpen, toggle, index }: { faq: typeof faqs[0]; isOpen: boolean; toggle: () => void; index: number }) {
return (
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.05 }}
className="rounded-xl overflow-hidden transition-all"
style={{
background: isOpen ? 'var(--sky-50)' : 'transparent',
border: isOpen ? '1px solid var(--sky-100)' : '1px solid transparent',
}}
>
<button
onClick={toggle}
className="w-full flex items-start justify-between gap-4 p-5 text-left transition-colors"
>
<span
className="text-[16px] font-semibold leading-snug pr-4"
style={{ color: isOpen ? 'var(--sky-700)' : 'var(--sky-900)' }}
>
{faq.q}
</span>
<div
className="w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 transition-colors mt-0.5"
style={{
background: isOpen ? 'var(--sky-600)' : 'var(--sky-100)',
color: isOpen ? 'white' : 'var(--sky-500)',
}}
>
{isOpen ? <Minus size={14} /> : <Plus size={14} />}
</div>
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
className="overflow-hidden"
>
<div className="px-5 pb-5">
<p
className="text-[14px] leading-relaxed"
style={{ color: 'var(--sky-800)', opacity: 0.65 }}
>
{faq.a}
</p>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
}
export function FAQ() {
const [openIndex, setOpenIndex] = useState(0);
return (
<section id="faq" className="py-24 px-6" style={{ background: 'white' }}>
<div className="max-w-[760px] mx-auto">
{/* Header */}
<div className="text-center mb-14">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4 mx-auto"
style={{ background: 'var(--sky-50)', color: 'var(--sky-600)' }}
>
<HelpCircle size={12} />
Common Questions
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em] mb-4"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
Frequently asked
<br />
questions
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[16px] leading-relaxed"
style={{ color: 'var(--sky-800)', opacity: 0.7 }}
>
Everything you need to know about claiming flight compensation.
</motion.p>
</div>
{/* Accordion */}
<div className="space-y-2">
{faqs.map((faq, i) => (
<FAQItem
key={i}
faq={faq}
index={i}
isOpen={openIndex === i}
toggle={() => setOpenIndex(openIndex === i ? -1 : i)}
/>
))}
</div>
</div>
</section>
);
}
export default FAQ;
~~~
~~~/src/Nav.tsx
/**
* SkyVolo Navigation Bar
* Sticky top navigation with logo, nav links, and CTA button.
* Transparent on scroll-top, white with shadow on scroll.
* Includes mobile hamburger menu.
*/
import React, { useState, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Plane, Menu, X, ArrowRight } from 'lucide-react';
const navLinks = [
{ label: 'How It Works', href: '#how-it-works' },
{ label: 'Compensation', href: '#compensation' },
{ label: 'Disruptions', href: '#disruptions' },
{ label: 'Reviews', href: '#reviews' },
{ label: 'FAQ', href: '#faq' },
];
export function Nav() {
const [isScrolled, setIsScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const handleScroll = useCallback(() => {
setIsScrolled(window.scrollY > 50);
}, []);
React.useEffect(() => {
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
return (
<>
<motion.nav
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5 }}
className="fixed top-0 left-0 right-0 z-50 transition-all duration-300 px-4 sm:px-6"
style={{
background: isScrolled ? 'rgba(255,255,255,0.95)' : 'transparent',
backdropFilter: isScrolled ? 'blur(12px)' : 'none',
boxShadow: isScrolled ? '0 1px 3px rgba(0,0,0,0.05)' : 'none',
}}
>
<div className="max-w-[1200px] mx-auto flex items-center justify-between h-16">
{/* Logo */}
<a href="#" className="flex items-center gap-2">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center"
style={{
background: isScrolled ? 'var(--sky-50)' : 'rgba(255,255,255,0.15)',
}}
>
<Plane size={16} style={{ color: isScrolled ? 'var(--sky-600)' : 'white' }} />
</div>
<span
className="text-[18px] font-bold tracking-tight"
style={{
fontFamily: 'var(--font-display)',
color: isScrolled ? 'var(--sky-950)' : 'white',
}}
>
SkyVolo
</span>
</a>
{/* Desktop links */}
<div className="hidden md:flex items-center gap-7">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-[14px] font-medium transition-colors"
style={{ color: isScrolled ? 'var(--sky-700)' : 'rgba(255,255,255,0.8)' }}
>
{link.label}
</a>
))}
</div>
{/* CTA + Mobile toggle */}
<div className="flex items-center gap-3">
<a
href="#hero"
className="hidden sm:inline-flex items-center gap-2 px-5 py-2 rounded-full text-[13px] font-semibold transition-all"
style={{
background: isScrolled ? 'var(--sky-600)' : 'rgba(255,255,255,0.15)',
color: 'white',
border: isScrolled ? 'none' : '1px solid rgba(255,255,255,0.2)',
}}
>
Check My Flight
<ArrowRight size={13} />
</a>
<button
onClick={() => setMobileOpen(!mobileOpen)}
className="md:hidden w-9 h-9 rounded-lg flex items-center justify-center"
style={{
background: isScrolled ? 'var(--sky-50)' : 'rgba(255,255,255,0.1)',
color: isScrolled ? 'var(--sky-700)' : 'white',
}}
>
{mobileOpen ? <X size={18} /> : <Menu size={18} />}
</button>
</div>
</div>
</motion.nav>
{/* Mobile menu */}
<AnimatePresence>
{mobileOpen && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="fixed top-16 left-0 right-0 z-40 p-4 md:hidden"
style={{ background: 'rgba(255,255,255,0.98)', backdropFilter: 'blur(12px)' }}
>
<div className="flex flex-col gap-1">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
onClick={() => setMobileOpen(false)}
className="px-4 py-3 rounded-xl text-[15px] font-medium transition-colors"
style={{ color: 'var(--sky-800)' }}
>
{link.label}
</a>
))}
<a
href="#hero"
onClick={() => setMobileOpen(false)}
className="mt-2 flex items-center justify-center gap-2 px-5 py-3 rounded-xl text-[15px] font-semibold text-white"
style={{ background: 'var(--sky-600)' }}
>
Check My Flight
<ArrowRight size={14} />
</a>
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}
export default Nav;
~~~
~~~/package.json
{
"name": "skyvolo-landing",
"description": "SkyVolo - Flight compensation service landing page inspired by AirHelp",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"framer-motion": "^11.0.0",
"lucide-react": "^0.344.0"
}
}
~~~
~~~/src/Hero.tsx
/**
* SkyVolo Hero Section
* Full-viewport hero with gradient sky background, animated floating elements,
* compelling headline, flight search form, and trust signals.
* The main entry point for user claim submission.
*/
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { Plane, Search, ArrowRight, Shield, Clock, Star, ChevronDown } from 'lucide-react';
const airlines = [
'Select Airline', 'Ryanair', 'Lufthansa', 'Air France', 'EasyJet', 'KLM',
'British Airways', 'Iberia', 'Wizz Air', 'TAP Portugal', 'SAS',
];
const trustBadges = [
{ icon: Shield, text: 'No win, no fee' },
{ icon: Clock, text: '3 min to claim' },
{ icon: Star, text: '4.8 rating' },
];
export function Hero() {
const [flightNumber, setFlightNumber] = useState('');
const [airline, setAirline] = useState('Select Airline');
return (
<section id="hero" className="relative min-h-screen overflow-hidden gradient-hero">
{/* Background decorations */}
<div className="absolute inset-0 pointer-events-none">
{/* Radial glow */}
<div
className="absolute top-[20%] left-[50%] w-[800px] h-[800px] rounded-full -translate-x-1/2"
style={{ background: 'radial-gradient(circle, rgba(255,255,255,0.06) 0%, transparent 60%)' }}
/>
{/* Floating planes */}
<motion.div
animate={{ x: [0, 30, 0], y: [0, -15, 0], rotate: [0, 3, 0] }}
transition={{ duration: 20, repeat: Infinity, ease: 'easeInOut' }}
className="absolute top-[15%] right-[12%] opacity-[0.08]"
>
<Plane size={80} style={{ color: 'white' }} />
</motion.div>
<motion.div
animate={{ x: [0, -20, 0], y: [0, 10, 0] }}
transition={{ duration: 15, repeat: Infinity, ease: 'easeInOut' }}
className="absolute top-[60%] left-[8%] opacity-[0.05] rotate-[-30deg]"
>
<Plane size={50} style={{ color: 'white' }} />
</motion.div>
{/* Grid dots */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage: `radial-gradient(circle, rgba(255,255,255,0.8) 1px, transparent 1px)`,
backgroundSize: '40px 40px',
}}
/>
</div>
<div className="relative z-10 max-w-[1200px] mx-auto px-6 flex flex-col justify-center min-h-screen pt-24 pb-16">
<div className="max-w-[680px]">
{/* Badge */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-6 glass"
>
<div className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
<span className="text-white/80 text-[13px] font-medium">
Over €300M recovered for passengers
</span>
</motion.div>
{/* Headline */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.6 }}
className="text-[42px] sm:text-[56px] md:text-[64px] leading-[1.05] font-bold text-white tracking-[-0.03em] mb-5"
style={{ fontFamily: 'var(--font-display)' }}
>
Flight disrupted?
<br />
<span style={{ color: 'var(--gold-400)' }}>Get up to €600.</span>
</motion.h1>
{/* Subhead */}
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.45 }}
className="text-[17px] sm:text-[19px] leading-relaxed mb-10 max-w-[520px]"
style={{ color: 'rgba(255,255,255,0.7)' }}
>
Delayed, cancelled, or overbooked? Check your eligibility in seconds and let our experts handle your claim. No win, no fee.
</motion.p>
{/* Search form */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
className="rounded-2xl p-2 mb-8"
style={{
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.15)',
backdropFilter: 'blur(12px)',
}}
>
<div className="flex flex-col sm:flex-row gap-2">
{/* Airline select */}
<div className="relative flex-1">
<select
value={airline}
onChange={(e) => setAirline(e.target.value)}
className="w-full px-4 py-3.5 rounded-xl text-[15px] appearance-none cursor-pointer outline-none"
style={{
background: 'rgba(255,255,255,0.08)',
color: airline === 'Select Airline' ? 'rgba(255,255,255,0.4)' : 'white',
border: '1px solid rgba(255,255,255,0.1)',
}}
>
{airlines.map((a) => (
<option key={a} value={a} style={{ color: '#333', background: 'white' }}>
{a}
</option>
))}
</select>
<ChevronDown
size={16}
className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none"
style={{ color: 'rgba(255,255,255,0.4)' }}
/>
</div>
{/* Flight number input */}
<div className="relative flex-1">
<Search
size={16}
className="absolute left-4 top-1/2 -translate-y-1/2"
style={{ color: 'rgba(255,255,255,0.3)' }}
/>
<input
type="text"
placeholder="Flight number (e.g. FR1234)"
value={flightNumber}
onChange={(e) => setFlightNumber(e.target.value)}
className="w-full pl-10 pr-4 py-3.5 rounded-xl text-[15px] text-white placeholder:text-white/30 outline-none"
style={{
background: 'rgba(255,255,255,0.08)',
border: '1px solid rgba(255,255,255,0.1)',
}}
/>
</div>
{/* Submit */}
<button
className="flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl text-[15px] font-semibold transition-all duration-300 hover:scale-[1.02] active:scale-[0.98]"
style={{
background: 'white',
color: 'var(--sky-700)',
boxShadow: '0 4px 15px rgba(0,0,0,0.1)',
}}
>
Check Flight
<ArrowRight size={15} />
</button>
</div>
</motion.div>
{/* Trust badges */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
className="flex flex-wrap gap-5"
>
{trustBadges.map((badge) => (
<div key={badge.text} className="flex items-center gap-2">
<badge.icon size={14} style={{ color: 'rgba(255,255,255,0.4)' }} />
<span className="text-[13px] font-medium" style={{ color: 'rgba(255,255,255,0.5)' }}>
{badge.text}
</span>
</div>
))}
</motion.div>
</div>
{/* Decorative card (right side, desktop only) */}
<motion.div
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.9, duration: 0.8 }}
className="hidden lg:block absolute right-[8%] top-[50%] -translate-y-1/2"
>
<div
className="w-[280px] rounded-2xl p-6 glass"
style={{ transform: 'rotate(2deg)' }}
>
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'rgba(52,211,153,0.15)' }}>
<Shield size={18} style={{ color: 'var(--emerald-400)' }} />
</div>
<div>
<div className="text-white text-[14px] font-semibold">Claim Approved</div>
<div className="text-white/40 text-[12px]">Just now · FR1234</div>
</div>
</div>
<div className="flex items-baseline gap-2 mb-3">
<span className="text-[36px] font-bold text-white tracking-[-0.02em]" style={{ fontFamily: 'var(--font-display)' }}>€400</span>
<span className="text-white/40 text-[13px]">compensation</span>
</div>
<div className="flex gap-2">
<div className="flex-1 h-2 rounded-full" style={{ background: 'var(--emerald-400)' }} />
<div className="w-4 h-2 rounded-full" style={{ background: 'rgba(255,255,255,0.1)' }} />
</div>
<div className="text-white/30 text-[11px] mt-2">London → Barcelona · 3h delay</div>
</div>
<motion.div
animate={{ y: [0, 8, 0] }}
transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }}
className="w-[220px] rounded-xl p-4 glass mt-3 ml-8"
style={{ transform: 'rotate(-1deg)' }}
>
<div className="flex items-center gap-2">
<div className="flex -space-x-2">
{['SM', 'TK', 'ML'].map((initials, i) => (
<div
key={initials}
className="w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-bold text-white border-2 border-white/10"
style={{ background: ['var(--sky-400)', 'var(--coral-400)', 'var(--gold-400)'][i] }}
>
{initials}
</div>
))}
</div>
<span className="text-white/60 text-[12px] font-medium">+2.4M passengers</span>
</div>
</motion.div>
</motion.div>
</div>
</section>
);
}
export default Hero;
~~~
~~~/src/Stats.tsx
/**
* SkyVolo Stats Section
* Key metrics displayed in a clean grid with animated counters.
* Shows compensation recovered, claims processed, success rate, countries.
*/
import React, { useState, useRef } from 'react';
import { motion, useInView } from 'framer-motion';
import { TrendingUp, Users, Award, Globe } from 'lucide-react';
const metrics = [
{ icon: TrendingUp, value: '€300M+', label: 'Compensation recovered', color: 'var(--emerald-500)' },
{ icon: Users, value: '2M+', label: 'Passengers helped', color: 'var(--sky-500)' },
{ icon: Award, value: '98%', label: 'Success rate', color: 'var(--gold-500)' },
{ icon: Globe, value: '30+', label: 'Countries covered', color: 'var(--coral-500)' },
];
export function Stats() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true });
return (
<section ref={ref} className="py-16 px-6" style={{ background: 'white' }}>
<div className="max-w-[1000px] mx-auto">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{metrics.map((metric, i) => (
<motion.div
key={metric.label}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ delay: i * 0.1, duration: 0.6 }}
className="text-center"
>
<div
className="w-11 h-11 rounded-xl flex items-center justify-center mx-auto mb-3"
style={{ background: `${metric.color}12` }}
>
<metric.icon size={20} style={{ color: metric.color }} />
</div>
<div
className="text-[32px] font-bold tracking-[-0.03em] mb-1"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
{metric.value}
</div>
<div className="text-[13px] font-medium" style={{ color: 'var(--sky-700)', opacity: 0.5 }}>
{metric.label}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}
export default Stats;
~~~
~~~/src/Footer.tsx
/**
* SkyVolo Footer Section
* Multi-column footer with navigation links, legal info, trust badges,
* social media links, and newsletter signup.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { Plane, Heart, ExternalLink, Mail, Twitter, Linkedin, Instagram } from 'lucide-react';
const footerLinks = {
'For Passengers': [
'Check My Flight',
'How It Works',
'Compensation Guide',
'Flight Disruptions',
'Airline Directory',
],
Company: [
'About SkyVolo',
'Careers',
'Press',
'Blog',
'Partners',
],
Legal: [
'Terms of Service',
'Privacy Policy',
'Cookie Policy',
'EU Regulation 261',
'Imprint',
],
Support: [
'Help Center',
'Contact Us',
'Claim Status',
'Complaint',
'Accessibility',
],
};
export function Footer() {
return (
<footer
className="pt-16 pb-8 px-6"
style={{ background: 'var(--sky-950)', color: 'rgba(255,255,255,0.6)' }}
>
<div className="max-w-[1200px] mx-auto">
{/* Top section */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-10 mb-16">
{/* Brand column */}
<div className="col-span-2 md:col-span-1">
<div className="flex items-center gap-2 mb-4">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center"
style={{ background: 'rgba(255,255,255,0.1)' }}
>
<Plane size={16} style={{ color: 'var(--sky-400)' }} />
</div>
<span
className="text-[18px] font-bold text-white tracking-tight"
style={{ fontFamily: 'var(--font-display)' }}
>
SkyVolo
</span>
</div>
<p className="text-[13px] leading-relaxed mb-5 max-w-[200px]">
Europe's most trusted flight compensation service. Your rights, our expertise.
</p>
<div className="flex gap-3">
{[Twitter, Linkedin, Instagram].map((Icon, i) => (
<a
key={i}
href="#"
className="w-8 h-8 rounded-lg flex items-center justify-center transition-colors hover:bg-white/10"
style={{ background: 'rgba(255,255,255,0.05)' }}
>
<Icon size={14} style={{ color: 'rgba(255,255,255,0.5)' }} />
</a>
))}
</div>
</div>
{/* Link columns */}
{Object.entries(footerLinks).map(([title, links]) => (
<div key={title}>
<h4 className="text-white text-[13px] font-semibold mb-4 uppercase tracking-[0.04em]">
{title}
</h4>
<ul className="space-y-2.5">
{links.map((link) => (
<li key={link}>
<a
href="#"
className="text-[13px] transition-colors hover:text-white"
>
{link}
</a>
</li>
))}
</ul>
</div>
))}
</div>
{/* Newsletter */}
<div
className="rounded-2xl p-6 sm:p-8 mb-12 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"
style={{ background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.08)' }}
>
<div>
<h4 className="text-white text-[16px] font-semibold mb-1">Stay updated</h4>
<p className="text-[13px]">Get the latest travel rights news and compensation tips.</p>
</div>
<div className="flex gap-2">
<input
type="email"
placeholder="Your email"
className="px-4 py-2.5 rounded-xl text-[14px] bg-white/10 outline-none text-white placeholder:text-white/30 min-w-[200px]"
style={{ border: '1px solid rgba(255,255,255,0.1)' }}
/>
<button
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-[14px] font-semibold transition-all hover:scale-[1.02]"
style={{ background: 'var(--sky-500)', color: 'white' }}
>
<Mail size={14} />
Subscribe
</button>
</div>
</div>
{/* Bottom bar */}
<div
className="flex flex-col sm:flex-row items-center justify-between gap-4 pt-8"
style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}
>
<div className="text-[12px] flex items-center gap-1">
© 2024 SkyVolo. Made with <Heart size={10} fill="var(--coral-400)" style={{ color: 'var(--coral-400)' }} /> in Europe.
</div>
<div className="flex items-center gap-4 text-[12px]">
<span>🇪🇺 EU Regulation 261/2004 Compliant</span>
<span className="hidden sm:inline">·</span>
<span className="flex items-center gap-1">
Member of APRA <ExternalLink size={10} />
</span>
</div>
</div>
</div>
</footer>
);
}
export default Footer;
~~~
~~~/src/Navbar.tsx
/**
* SkyVolo Top Navigation Bar
* Fixed header with logo, navigation links, and CTA button.
* Transparent on scroll top, turns white with shadow when scrolled.
*/
import React, { useState, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Plane, Menu, X, ChevronDown } from 'lucide-react';
const navLinks = [
{ label: 'How It Works', href: '#how-it-works' },
{ label: 'Compensation', href: '#compensation' },
{ label: 'Flight Issues', href: '#disruptions' },
{ label: 'Reviews', href: '#reviews' },
{ label: 'FAQ', href: '#faq' },
];
export function Navbar() {
const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const handleScroll = useCallback(() => {
setScrolled(window.scrollY > 20);
}, []);
React.useEffect(() => {
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
return (
<>
<motion.nav
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
scrolled
? 'bg-white/90 backdrop-blur-xl shadow-[0_1px_0_rgba(0,45,92,0.06)]'
: 'bg-transparent'
}`}
>
<div className="max-w-[1200px] mx-auto px-6 h-[72px] flex items-center justify-between">
{/* Logo */}
<a href="#" className="flex items-center gap-2 group">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center transition-transform duration-300 group-hover:scale-105"
style={{ background: scrolled ? 'var(--sky-600)' : 'rgba(255,255,255,0.2)' }}
>
<Plane
size={18}
className="transition-colors duration-300"
style={{ color: scrolled ? 'white' : 'white' }}
/>
</div>
<span
className="text-[20px] font-bold tracking-tight transition-colors duration-300"
style={{
fontFamily: 'var(--font-display)',
color: scrolled ? 'var(--sky-950)' : 'white',
}}
>
SkyVolo
</span>
</a>
{/* Desktop Nav */}
<div className="hidden lg:flex items-center gap-8">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-[14px] font-medium transition-colors duration-200 hover:opacity-100"
style={{
color: scrolled ? 'var(--sky-800)' : 'rgba(255,255,255,0.85)',
opacity: 0.85,
}}
>
{link.label}
</a>
))}
</div>
{/* CTA */}
<div className="hidden lg:flex items-center gap-3">
<a
href="#"
className="text-[14px] font-medium transition-colors duration-200"
style={{ color: scrolled ? 'var(--sky-700)' : 'rgba(255,255,255,0.85)' }}
>
Log In
</a>
<a
href="#hero"
className="px-5 py-2.5 rounded-full text-[14px] font-semibold transition-all duration-300 hover:scale-[1.02]"
style={{
background: scrolled ? 'var(--sky-600)' : 'white',
color: scrolled ? 'white' : 'var(--sky-700)',
}}
>
Check My Flight
</a>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setMobileOpen(!mobileOpen)}
className="lg:hidden p-2 rounded-lg"
style={{ color: scrolled ? 'var(--sky-800)' : 'white' }}
>
{mobileOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</motion.nav>
{/* Mobile Menu */}
<AnimatePresence>
{mobileOpen && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="fixed inset-0 z-40 bg-white pt-[80px] px-6 pb-6 lg:hidden"
>
<div className="flex flex-col gap-1">
{navLinks.map((link, i) => (
<motion.a
key={link.label}
href={link.href}
onClick={() => setMobileOpen(false)}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05 }}
className="py-3 px-4 text-[16px] font-medium rounded-xl transition-colors hover:bg-[var(--sky-50)]"
style={{ color: 'var(--sky-800)' }}
>
{link.label}
</motion.a>
))}
<div className="mt-4 pt-4" style={{ borderTop: '1px solid var(--sky-100)' }}>
<a
href="#hero"
onClick={() => setMobileOpen(false)}
className="block w-full py-3.5 rounded-2xl text-center text-white font-semibold text-[15px]"
style={{ background: 'var(--sky-600)' }}
>
Check My Flight
</a>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}
export default Navbar;
~~~
~~~/src/Offers.tsx
/**
* SkyVolo Offers / SkyVolo Plus Section
* Premium upsell section showcasing additional travel protection features.
* Features: trip monitoring, automatic claims, priority support, travel insurance.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { Bell, Zap, Headphones, Shield, ArrowRight, Sparkles, Check } from 'lucide-react';
const features = [
{
icon: Bell,
title: 'Flight Monitoring',
description: 'We track your flights in real-time and automatically detect disruptions before you even notice.',
},
{
icon: Zap,
title: 'Automatic Claims',
description: 'No manual submission needed. When a disruption qualifies, we initiate your claim instantly.',
},
{
icon: Headphones,
title: 'Priority Support',
description: 'Skip the queue with dedicated support agents who handle your case within 24 hours.',
},
{
icon: Shield,
title: 'Travel Protection',
description: 'Extended coverage for missed connections, luggage delays, and travel disruptions beyond EU regulation.',
},
];
const benefits = [
'Covers all flights automatically',
'Reduced 20% success fee',
'Priority legal representation',
'Real-time disruption alerts',
'Cancel anytime',
];
export function Offers() {
return (
<section className="py-24 px-6 gradient-subtle relative noise-overlay">
<div className="max-w-[1200px] mx-auto relative z-10">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Left — Info */}
<div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4"
style={{ background: 'linear-gradient(135deg, var(--sky-500) 0%, var(--sky-600) 100%)', color: 'white' }}
>
<Sparkles size={12} />
SkyVolo Plus
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em] mb-4"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
Travel with total
<br />
peace of mind
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[16px] leading-relaxed mb-8"
style={{ color: 'var(--sky-800)', opacity: 0.7 }}
>
SkyVolo Plus monitors all your flights, automatically files claims, and provides extended travel protection — so you never miss a compensation opportunity.
</motion.p>
{/* Benefits */}
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
className="space-y-3 mb-8"
>
{benefits.map((b) => (
<div key={b} className="flex items-center gap-3">
<div
className="w-5 h-5 rounded-full flex items-center justify-center"
style={{ background: '#f0fdf4' }}
>
<Check size={12} style={{ color: 'var(--emerald-500)' }} />
</div>
<span className="text-[14px] font-medium" style={{ color: 'var(--sky-800)' }}>
{b}
</span>
</div>
))}
</motion.div>
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.4 }}
className="flex items-baseline gap-2 mb-6"
>
<span
className="text-[40px] font-bold tracking-[-0.02em]"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
€4.99
</span>
<span className="text-[14px]" style={{ color: 'var(--sky-600)', opacity: 0.6 }}>
/ month
</span>
</motion.div>
<motion.a
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.5 }}
href="#"
className="inline-flex items-center gap-2 px-7 py-3.5 rounded-full text-white font-semibold text-[15px] transition-all duration-300 hover:scale-[1.02]"
style={{
background: 'var(--sky-600)',
boxShadow: '0 4px 20px rgba(0,98,208,0.25)',
}}
>
Get SkyVolo Plus
<ArrowRight size={16} />
</motion.a>
</div>
{/* Right — Feature grid */}
<div className="grid grid-cols-2 gap-4">
{features.map((feature, i) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 + i * 0.1 }}
className="bg-white rounded-2xl p-6 group cursor-default transition-all duration-300 hover:shadow-lg hover:-translate-y-1"
style={{ border: '1px solid var(--sky-100)' }}
>
<div
className="w-10 h-10 rounded-xl flex items-center justify-center mb-4 transition-colors"
style={{ background: 'var(--sky-50)' }}
>
<feature.icon size={20} style={{ color: 'var(--sky-500)' }} />
</div>
<h3
className="text-[16px] font-bold mb-2"
style={{ color: 'var(--sky-950)', fontFamily: 'var(--font-display)' }}
>
{feature.title}
</h3>
<p className="text-[13px] leading-relaxed" style={{ color: 'var(--sky-700)', opacity: 0.6 }}>
{feature.description}
</p>
</motion.div>
))}
</div>
</div>
</div>
</section>
);
}
export default Offers;
~~~
~~~/src/styles.css
/**
* SkyVolo Global Styles
* Custom CSS variables, utility classes, and global styling
* for the flight compensation landing page.
* Design system: Sky blue primary, coral accents, warm sand neutrals.
*/
@import url('https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@100,200,300,400,500,600,700,800,900&f[]=satoshi@300,400,500,600,700,800,900&display=swap');
:root {
/* Sky Blues */
--sky-50: #f0f7ff;
--sky-100: #e0efff;
--sky-200: #b8dbff;
--sky-300: #7abfff;
--sky-400: #3a9eff;
--sky-500: #0078f0;
--sky-600: #0062d0;
--sky-700: #004da8;
--sky-800: #003d85;
--sky-900: #002e63;
--sky-950: #001a3d;
/* Coral Accents */
--coral-400: #ff6b6b;
--coral-500: #f44336;
/* Gold Accents */
--gold-400: #ffc107;
--gold-500: #f59e0b;
/* Emerald */
--emerald-400: #34d399;
--emerald-500: #10b981;
/* Sand Neutrals */
--sand-50: #faf8f5;
--sand-100: #f2ede6;
--sand-200: #e8dfd3;
/* Typography */
--font-display: 'Cabinet Grotesk', sans-serif;
--font-body: 'Satoshi', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-body);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--sky-900);
background: white;
}
html {
scroll-behavior: smooth;
}
/* Gradient backgrounds */
.gradient-hero {
background: linear-gradient(135deg, var(--sky-600) 0%, var(--sky-700) 40%, var(--sky-900) 100%);
}
.gradient-subtle {
background: linear-gradient(180deg, var(--sand-50) 0%, white 100%);
}
/* Noise texture overlay */
.noise-overlay::before {
content: '';
position: absolute;
inset: 0;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
pointer-events: none;
}
/* Pill tags */
.pill-tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 100px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
}
/* Glass morphism */
.glass {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.12);
}
/* Scrollbar hide */
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* Selection */
::selection {
background: var(--sky-200);
color: var(--sky-900);
}
/* Focus styles */
:focus-visible {
outline: 2px solid var(--sky-400);
outline-offset: 2px;
}
/* Smooth transitions for interactive elements */
a, button {
cursor: pointer;
text-decoration: none;
color: inherit;
}
/* Input reset */
input {
font-family: var(--font-body);
}
~~~
~~~/src/Pricing.tsx
/**
* SkyVolo Pricing / Fees Section
* No-win-no-fee model with comparison between free claim check and full service.
* Clean pricing cards with feature lists and prominent CTAs.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { Check, X, Zap, Crown, ArrowRight, ShieldCheck } from 'lucide-react';
const plans = [
{
name: 'Self Service',
price: 'Free',
subtitle: 'Check eligibility',
description: 'Verify if your flight qualifies for compensation at no cost.',
features: [
{ label: 'Instant eligibility check', included: true },
{ label: 'Compensation estimate', included: true },
{ label: 'Flight disruption info', included: true },
{ label: 'Claim submission for you', included: false },
{ label: 'Legal representation', included: false },
{ label: 'Court proceedings', included: false },
],
cta: 'Check Eligibility',
highlight: false,
icon: Zap,
},
{
name: 'SkyVolo Pro',
price: '25%',
subtitle: 'of your compensation',
description: 'We handle everything. You only pay when we win.',
features: [
{ label: 'Instant eligibility check', included: true },
{ label: 'Compensation estimate', included: true },
{ label: 'Flight disruption info', included: true },
{ label: 'Full claim management', included: true },
{ label: 'Legal representation', included: true },
{ label: 'Court proceedings if needed', included: true },
],
cta: 'Start Your Claim',
highlight: true,
icon: Crown,
},
];
export function Pricing() {
return (
<section id="pricing" className="py-24 px-6" style={{ background: 'white' }}>
<div className="max-w-[900px] mx-auto">
{/* Header */}
<div className="text-center max-w-[560px] mx-auto mb-16">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4 mx-auto"
style={{ background: '#f0fdf4', color: 'var(--emerald-500)' }}
>
<ShieldCheck size={12} />
No Win, No Fee
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em] mb-4"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
Simple, transparent
<br />
pricing
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[16px] leading-relaxed"
style={{ color: 'var(--sky-800)', opacity: 0.7 }}
>
No hidden fees, no upfront costs. We only get paid when you do.
</motion.p>
</div>
{/* Cards */}
<div className="grid md:grid-cols-2 gap-6">
{plans.map((plan, i) => (
<motion.div
key={plan.name}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.12, duration: 0.6 }}
className="rounded-2xl p-8 relative overflow-hidden"
style={{
background: plan.highlight
? 'linear-gradient(135deg, var(--sky-600) 0%, var(--sky-700) 100%)'
: 'var(--sand-50)',
border: plan.highlight ? 'none' : '1px solid var(--sky-100)',
}}
>
{plan.highlight && (
<div className="absolute top-4 right-4 px-3 py-1 rounded-full text-[11px] font-bold uppercase tracking-wider" style={{ background: 'rgba(255,255,255,0.2)', color: 'white' }}>
Most Popular
</div>
)}
{/* Icon */}
<div
className="w-10 h-10 rounded-xl flex items-center justify-center mb-5"
style={{
background: plan.highlight ? 'rgba(255,255,255,0.15)' : 'var(--sky-50)',
}}
>
<plan.icon size={20} style={{ color: plan.highlight ? 'white' : 'var(--sky-500)' }} />
</div>
{/* Name & Price */}
<div className="mb-1">
<span
className="text-[14px] font-semibold"
style={{ color: plan.highlight ? 'rgba(255,255,255,0.7)' : 'var(--sky-600)' }}
>
{plan.name}
</span>
</div>
<div className="flex items-baseline gap-2 mb-1">
<span
className="text-[48px] font-bold tracking-[-0.03em]"
style={{
fontFamily: 'var(--font-display)',
color: plan.highlight ? 'white' : 'var(--sky-950)',
}}
>
{plan.price}
</span>
</div>
<p
className="text-[13px] mb-2"
style={{ color: plan.highlight ? 'rgba(255,255,255,0.6)' : 'var(--sky-700)', opacity: plan.highlight ? 1 : 0.6 }}
>
{plan.subtitle}
</p>
<p
className="text-[14px] leading-relaxed mb-6"
style={{ color: plan.highlight ? 'rgba(255,255,255,0.75)' : 'var(--sky-800)', opacity: plan.highlight ? 1 : 0.6 }}
>
{plan.description}
</p>
{/* Features */}
<div className="space-y-3 mb-8">
{plan.features.map((feature) => (
<div key={feature.label} className="flex items-center gap-3">
{feature.included ? (
<Check size={16} style={{ color: plan.highlight ? 'var(--emerald-400)' : 'var(--emerald-500)' }} />
) : (
<X size={16} style={{ color: plan.highlight ? 'rgba(255,255,255,0.25)' : 'var(--sky-300)' }} />
)}
<span
className="text-[14px]"
style={{
color: feature.included
? plan.highlight
? 'rgba(255,255,255,0.9)'
: 'var(--sky-800)'
: plan.highlight
? 'rgba(255,255,255,0.3)'
: 'var(--sky-400)',
}}
>
{feature.label}
</span>
</div>
))}
</div>
{/* CTA */}
<a
href="#hero"
className="flex items-center justify-center gap-2 w-full py-3.5 rounded-xl text-[15px] font-semibold transition-all duration-300 hover:scale-[1.01]"
style={{
background: plan.highlight ? 'white' : 'var(--sky-600)',
color: plan.highlight ? 'var(--sky-700)' : 'white',
}}
>
{plan.cta}
<ArrowRight size={14} />
</a>
</motion.div>
))}
</div>
</div>
</section>
);
}
export default Pricing;
~~~
~~~/src/Reviews.tsx
/**
* SkyVolo Reviews / Testimonials Section
* Horizontal scrolling testimonial cards with star ratings.
* Social proof with real-sounding passenger stories and satisfaction metrics.
*/
import React, { useRef, useState } from 'react';
import { motion } from 'framer-motion';
import { Star, ChevronLeft, ChevronRight, Quote, MessageSquare } from 'lucide-react';
const reviews = [
{
name: 'Sarah M.',
location: 'London, UK',
rating: 5,
text: 'I had given up on getting compensation after my Ryanair flight was cancelled. SkyVolo took over and I received €400 within 6 weeks. Absolutely brilliant service.',
amount: '€400',
airline: 'Ryanair',
avatar: 'SM',
},
{
name: 'Thomas K.',
location: 'Berlin, Germany',
rating: 5,
text: 'My flight to New York was delayed by 5 hours. I submitted my details through SkyVolo and they handled the entire process. Got €600 in my account.',
amount: '€600',
airline: 'Lufthansa',
avatar: 'TK',
},
{
name: 'Marie L.',
location: 'Paris, France',
rating: 5,
text: 'The process was incredibly smooth. I just entered my flight number and SkyVolo did the rest. I didn\'t have to deal with the airline at all.',
amount: '€250',
airline: 'Air France',
avatar: 'ML',
},
{
name: 'Jan V.',
location: 'Amsterdam, NL',
rating: 4,
text: 'Very professional team. It took a bit longer than expected due to the airline pushing back, but SkyVolo persisted and won my case.',
amount: '€400',
airline: 'KLM',
avatar: 'JV',
},
{
name: 'Elena R.',
location: 'Madrid, Spain',
rating: 5,
text: 'I was denied boarding on an overbooked flight. SkyVolo helped me claim what I was owed. The legal team was fantastic — highly recommend!',
amount: '€600',
airline: 'Iberia',
avatar: 'ER',
},
];
const stats = [
{ value: '4.8', label: 'Average rating' },
{ value: '98%', label: 'Success rate' },
{ value: '2M+', label: 'Happy passengers' },
];
export function Reviews() {
const scrollRef = useRef<HTMLDivElement>(null);
const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(true);
const checkScroll = () => {
if (!scrollRef.current) return;
const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current;
setCanScrollLeft(scrollLeft > 10);
setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 10);
};
const scroll = (dir: 'left' | 'right') => {
if (!scrollRef.current) return;
const amount = 360;
scrollRef.current.scrollBy({ left: dir === 'left' ? -amount : amount, behavior: 'smooth' });
setTimeout(checkScroll, 400);
};
return (
<section id="reviews" className="py-24 gradient-subtle relative noise-overlay">
<div className="max-w-[1200px] mx-auto px-6 relative z-10">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-end md:justify-between mb-12">
<div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4"
style={{ background: '#fffbeb', color: 'var(--gold-500)' }}
>
<MessageSquare size={12} />
Passenger Stories
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em]"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
Real results from
<br />
real passengers
</motion.h2>
</div>
{/* Stats */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
className="flex gap-8 mt-6 md:mt-0"
>
{stats.map((stat) => (
<div key={stat.label} className="text-right">
<div
className="text-[28px] font-bold tracking-[-0.02em]"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-600)' }}
>
{stat.value}
</div>
<div className="text-[12px] font-medium" style={{ color: 'var(--sky-700)', opacity: 0.5 }}>
{stat.label}
</div>
</div>
))}
</motion.div>
</div>
{/* Scroll controls */}
<div className="flex justify-end gap-2 mb-4">
<button
onClick={() => scroll('left')}
disabled={!canScrollLeft}
className="w-10 h-10 rounded-full flex items-center justify-center transition-all"
style={{
background: canScrollLeft ? 'var(--sky-600)' : 'var(--sky-100)',
color: canScrollLeft ? 'white' : 'var(--sky-400)',
}}
>
<ChevronLeft size={18} />
</button>
<button
onClick={() => scroll('right')}
disabled={!canScrollRight}
className="w-10 h-10 rounded-full flex items-center justify-center transition-all"
style={{
background: canScrollRight ? 'var(--sky-600)' : 'var(--sky-100)',
color: canScrollRight ? 'white' : 'var(--sky-400)',
}}
>
<ChevronRight size={18} />
</button>
</div>
{/* Reviews scroll */}
<div
ref={scrollRef}
onScroll={checkScroll}
className="flex gap-5 overflow-x-auto pb-4 scrollbar-hide"
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
>
{reviews.map((review, i) => (
<motion.div
key={review.name}
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.08 }}
className="flex-shrink-0 w-[320px] bg-white rounded-2xl p-6"
style={{ border: '1px solid var(--sky-100)' }}
>
{/* Quote icon */}
<Quote size={24} style={{ color: 'var(--sky-200)' }} className="mb-3" />
{/* Stars */}
<div className="flex gap-0.5 mb-3">
{Array.from({ length: 5 }).map((_, j) => (
<Star
key={j}
size={14}
fill={j < review.rating ? 'var(--gold-400)' : 'var(--sky-100)'}
style={{ color: j < review.rating ? 'var(--gold-400)' : 'var(--sky-100)' }}
/>
))}
</div>
{/* Text */}
<p
className="text-[14px] leading-relaxed mb-4"
style={{ color: 'var(--sky-800)', opacity: 0.75 }}
>
"{review.text}"
</p>
{/* Amount badge */}
<div
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full mb-5 text-[12px] font-bold"
style={{ background: '#f0fdf4', color: 'var(--emerald-500)' }}
>
Received {review.amount}
</div>
{/* Author */}
<div className="flex items-center gap-3 pt-4" style={{ borderTop: '1px solid var(--sky-50)' }}>
<div
className="w-9 h-9 rounded-full flex items-center justify-center text-[12px] font-bold text-white"
style={{ background: 'var(--sky-400)' }}
>
{review.avatar}
</div>
<div>
<div className="text-[14px] font-semibold" style={{ color: 'var(--sky-950)' }}>
{review.name}
</div>
<div className="text-[12px]" style={{ color: 'var(--sky-600)', opacity: 0.5 }}>
{review.location} · {review.airline}
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}
export default Reviews;
~~~
~~~/src/HowItWorks.tsx
/**
* SkyVolo How It Works Section
* 3-step process visualization with animated connectors.
* Steps: Submit Claim → We Handle Everything → Get Paid.
* Minimal, clean layout with numbered steps and connecting lines.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { FileText, Scale, Banknote, Sparkles, ArrowRight } from 'lucide-react';
const steps = [
{
number: '01',
icon: FileText,
title: 'Submit your claim',
description: 'Enter your flight details. It takes less than 3 minutes. We\'ll instantly tell you if you\'re eligible.',
detail: 'No upfront costs',
},
{
number: '02',
icon: Scale,
title: 'We handle everything',
description: 'Our legal experts take care of all the paperwork, negotiations, and — if needed — court proceedings.',
detail: 'Average 45 days',
},
{
number: '03',
icon: Banknote,
title: 'You get paid',
description: 'Once the claim is successful, you receive your compensation. Simple, fast, and hassle-free.',
detail: 'Direct bank transfer',
},
];
export function HowItWorks() {
return (
<section id="how-it-works" className="py-24 px-6" style={{ background: 'white' }}>
<div className="max-w-[1200px] mx-auto">
{/* Header */}
<div className="text-center max-w-[560px] mx-auto mb-20">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4 mx-auto"
style={{ background: 'var(--sky-50)', color: 'var(--sky-600)' }}
>
<Sparkles size={12} />
Simple Process
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em] mb-4"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
Three steps to your
<br />
compensation
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[16px] leading-relaxed"
style={{ color: 'var(--sky-800)', opacity: 0.7 }}
>
We take the stress out of claiming flight compensation. No legal knowledge needed.
</motion.p>
</div>
{/* Steps */}
<div className="relative">
{/* Connector line (desktop) */}
<div
className="hidden md:block absolute top-[60px] left-[calc(16.67%+30px)] right-[calc(16.67%+30px)] h-[2px]"
style={{ background: 'linear-gradient(90deg, var(--sky-200) 0%, var(--sky-300) 50%, var(--sky-200) 100%)' }}
/>
<div className="grid md:grid-cols-3 gap-12 md:gap-8">
{steps.map((step, i) => (
<motion.div
key={step.number}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.15, duration: 0.6 }}
className="text-center relative"
>
{/* Number circle */}
<div className="relative inline-flex mb-8">
<div
className="w-[120px] h-[120px] rounded-full flex items-center justify-center relative z-10"
style={{
background: `linear-gradient(135deg, var(--sky-50) 0%, var(--sky-100) 100%)`,
border: '3px solid white',
boxShadow: '0 8px 30px rgba(0,98,208,0.08)',
}}
>
<step.icon size={36} style={{ color: 'var(--sky-500)' }} />
</div>
<div
className="absolute -top-2 -right-2 w-8 h-8 rounded-full flex items-center justify-center text-white text-[12px] font-bold z-20"
style={{ background: 'var(--sky-600)' }}
>
{step.number}
</div>
</div>
{/* Content */}
<h3
className="text-[22px] font-bold mb-2 tracking-[-0.01em]"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
{step.title}
</h3>
<p
className="text-[14px] leading-relaxed mb-3 max-w-[280px] mx-auto"
style={{ color: 'var(--sky-800)', opacity: 0.65 }}
>
{step.description}
</p>
<span
className="inline-flex items-center gap-1.5 text-[12px] font-semibold px-3 py-1 rounded-full"
style={{ background: 'var(--sky-50)', color: 'var(--sky-600)' }}
>
{step.detail}
</span>
</motion.div>
))}
</div>
</div>
{/* CTA */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.5 }}
className="text-center mt-16"
>
<a
href="#hero"
className="inline-flex items-center gap-2 px-8 py-4 rounded-full text-white font-semibold text-[15px] transition-all duration-300 hover:scale-[1.02] active:scale-[0.98]"
style={{
background: 'var(--sky-600)',
boxShadow: '0 4px 20px rgba(0,98,208,0.25)',
}}
>
Start Your Claim
<ArrowRight size={16} />
</a>
</motion.div>
</div>
</section>
);
}
export default HowItWorks;
~~~
~~~/src/Disruptions.tsx
/**
* SkyVolo Flight Disruptions Section
* Showcases the types of flight disruptions covered by SkyVolo.
* Three main categories: Delays, Cancellations, Overbooking.
* Each with icon, description, and animated reveal.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { Clock, XCircle, Users, AlertTriangle, ArrowRight, CheckCircle } from 'lucide-react';
const disruptions = [
{
icon: Clock,
title: 'Flight Delays',
subtitle: '3+ hours late',
description: 'If your flight arrived more than 3 hours late at the final destination, you may be entitled to compensation.',
eligibility: ['Delay of 3+ hours at arrival', 'Not caused by extraordinary circumstances', 'Flight from/to EU'],
accent: 'var(--sky-500)',
accentBg: 'var(--sky-50)',
},
{
icon: XCircle,
title: 'Cancellations',
subtitle: 'Less than 14 days notice',
description: 'Airlines must notify you at least 14 days before departure. If they didn\'t, you deserve compensation.',
eligibility: ['Cancelled with < 14 days notice', 'No suitable alternative offered', 'Not extraordinary circumstances'],
accent: 'var(--coral-500)',
accentBg: '#fff5f4',
},
{
icon: Users,
title: 'Overbooking',
subtitle: 'Denied boarding',
description: 'If you were denied boarding due to overbooking despite having a valid ticket and arriving on time.',
eligibility: ['Valid ticket and reservation', 'Checked in on time', 'Involuntarily denied boarding'],
accent: 'var(--gold-500)',
accentBg: '#fffbeb',
},
];
export function Disruptions() {
return (
<section id="disruptions" className="py-24 px-6 gradient-subtle relative noise-overlay">
<div className="max-w-[1200px] mx-auto relative z-10">
{/* Header */}
<div className="text-center max-w-[600px] mx-auto mb-16">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4 mx-auto"
style={{ background: '#fff5f4', color: 'var(--coral-500)' }}
>
<AlertTriangle size={12} />
We've Got You Covered
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em] mb-4"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
What happened to
<br />
your flight?
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[16px] leading-relaxed"
style={{ color: 'var(--sky-800)', opacity: 0.7 }}
>
Whether your flight was delayed, cancelled, or overbooked — you may be entitled to compensation under EU law.
</motion.p>
</div>
{/* Cards */}
<div className="grid md:grid-cols-3 gap-6">
{disruptions.map((item, i) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.12, duration: 0.6 }}
className="bg-white rounded-2xl p-7 group cursor-default transition-all duration-300 hover:shadow-xl hover:-translate-y-1"
style={{ border: '1px solid var(--sky-100)' }}
>
{/* Icon */}
<div
className="w-12 h-12 rounded-xl flex items-center justify-center mb-5"
style={{ background: item.accentBg }}
>
<item.icon size={22} style={{ color: item.accent }} />
</div>
{/* Title */}
<h3
className="text-[22px] font-bold mb-1 tracking-[-0.01em]"
style={{ color: 'var(--sky-950)', fontFamily: 'var(--font-display)' }}
>
{item.title}
</h3>
<div
className="text-[13px] font-semibold mb-3"
style={{ color: item.accent }}
>
{item.subtitle}
</div>
{/* Description */}
<p
className="text-[14px] leading-relaxed mb-5"
style={{ color: 'var(--sky-800)', opacity: 0.65 }}
>
{item.description}
</p>
{/* Eligibility list */}
<div className="space-y-2">
{item.eligibility.map((rule) => (
<div key={rule} className="flex items-start gap-2">
<CheckCircle
size={14}
className="mt-0.5 flex-shrink-0"
style={{ color: 'var(--emerald-500)' }}
/>
<span className="text-[13px]" style={{ color: 'var(--sky-800)', opacity: 0.6 }}>
{rule}
</span>
</div>
))}
</div>
{/* CTA */}
<div className="mt-6 pt-5" style={{ borderTop: '1px solid var(--sky-100)' }}>
<a
href="#hero"
className="flex items-center gap-2 text-[14px] font-semibold transition-all group-hover:gap-3"
style={{ color: item.accent }}
>
Check eligibility
<ArrowRight size={14} />
</a>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}
export default Disruptions;
~~~
~~~/src/TrustBanner.tsx
/**
* SkyVolo Trust Banner
* Airline logos and trust signals showing partnerships and coverage.
* Horizontal auto-scrolling marquee of airline brand logos.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { CheckCircle } from 'lucide-react';
const airlines = [
'Ryanair', 'Lufthansa', 'Air France', 'EasyJet', 'KLM',
'Iberia', 'British Airways', 'Wizz Air', 'Vueling', 'TAP',
'SAS', 'Finnair', 'Austrian', 'Aer Lingus', 'Eurowings',
];
export function TrustBanner() {
return (
<section className="py-12 px-6 overflow-hidden" style={{ background: 'var(--sand-50)' }}>
<div className="max-w-[1200px] mx-auto">
<div className="flex items-center justify-center gap-2 mb-6">
<CheckCircle size={14} style={{ color: 'var(--sky-500)' }} />
<span className="text-[13px] font-medium" style={{ color: 'var(--sky-700)', opacity: 0.5 }}>
Claims processed against 200+ airlines including
</span>
</div>
<div className="relative">
{/* Fade edges */}
<div
className="absolute left-0 top-0 bottom-0 w-20 z-10 pointer-events-none"
style={{ background: 'linear-gradient(90deg, var(--sand-50) 0%, transparent 100%)' }}
/>
<div
className="absolute right-0 top-0 bottom-0 w-20 z-10 pointer-events-none"
style={{ background: 'linear-gradient(270deg, var(--sand-50) 0%, transparent 100%)' }}
/>
{/* Marquee */}
<div className="flex overflow-hidden">
<motion.div
animate={{ x: [0, -50 * airlines.length] }}
transition={{ duration: 30, repeat: Infinity, ease: 'linear' }}
className="flex gap-8 items-center"
>
{[...airlines, ...airlines].map((airline, i) => (
<div
key={`${airline}-${i}`}
className="flex-shrink-0 px-5 py-2.5 rounded-lg whitespace-nowrap text-[14px] font-semibold"
style={{
color: 'var(--sky-700)',
opacity: 0.35,
background: 'rgba(255,255,255,0.6)',
border: '1px solid var(--sky-100)',
}}
>
{airline}
</div>
))}
</motion.div>
</div>
</div>
</div>
</section>
);
}
export default TrustBanner;
~~~
~~~/src/Compensation.tsx
/**
* SkyVolo Compensation Section
* Visual breakdown of EU compensation amounts by flight distance.
* Elegant card layout with animated entry, distance indicators, and pricing tiers.
*/
import React from 'react';
import { motion } from 'framer-motion';
import { Plane, MapPin, ArrowRight } from 'lucide-react';
const tiers = [
{
distance: 'Up to 1,500 km',
amount: '€250',
example: 'London → Paris',
color: 'var(--sky-400)',
bg: 'var(--sky-50)',
width: '40%',
},
{
distance: '1,500 – 3,500 km',
amount: '€400',
example: 'Berlin → Istanbul',
color: 'var(--sky-500)',
bg: 'var(--sky-100)',
width: '65%',
},
{
distance: 'Over 3,500 km',
amount: '€600',
example: 'Frankfurt → New York',
color: 'var(--sky-600)',
bg: 'var(--sky-200)',
width: '100%',
},
];
export function Compensation() {
return (
<section id="compensation" className="py-24 px-6" style={{ background: 'white' }}>
<div className="max-w-[1200px] mx-auto">
{/* Header */}
<div className="max-w-[600px] mb-16">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="pill-tag mb-4"
style={{ background: 'var(--sky-50)', color: 'var(--sky-600)' }}
>
<Plane size={12} />
EU Regulation 261
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-[36px] sm:text-[44px] leading-[1.1] font-bold tracking-[-0.02em] mb-4"
style={{ fontFamily: 'var(--font-display)', color: 'var(--sky-950)' }}
>
How much can
<br />
you claim?
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-[16px] leading-relaxed"
style={{ color: 'var(--sky-800)', opacity: 0.7 }}
>
Your compensation depends on the distance of your flight. EU regulations protect passengers on all flights departing from the EU.
</motion.p>
</div>
{/* Tiers */}
<div className="grid md:grid-cols-3 gap-5">
{tiers.map((tier, i) => (
<motion.div
key={tier.distance}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.6 }}
className="relative group rounded-2xl p-8 transition-all duration-300 hover:shadow-lg cursor-default"
style={{
background: tier.bg,
border: `1px solid ${tier.color}20`,
}}
>
{/* Amount */}
<div
className="text-[52px] font-bold leading-none mb-1 tracking-[-0.03em]"
style={{ fontFamily: 'var(--font-display)', color: tier.color }}
>
{tier.amount}
</div>
<div
className="text-[13px] font-semibold uppercase tracking-[0.04em] mb-6"
style={{ color: tier.color, opacity: 0.7 }}
>
per passenger
</div>
{/* Distance bar */}
<div className="mb-6">
<div className="text-[15px] font-semibold mb-2" style={{ color: 'var(--sky-900)' }}>
{tier.distance}
</div>
<div className="h-2 rounded-full overflow-hidden" style={{ background: `${tier.color}15` }}>
<motion.div
initial={{ width: 0 }}
whileInView={{ width: tier.width }}
viewport={{ once: true }}
transition={{ delay: 0.3 + i * 0.1, duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
className="h-full rounded-full"
style={{ background: tier.color }}
/>
</div>
</div>
{/* Example */}
<div className="flex items-center gap-2 text-[13px]" style={{ color: 'var(--sky-700)', opacity: 0.6 }}>
<MapPin size={12} />
<span>{tier.example}</span>
</div>
{/* Hover decoration */}
<div
className="absolute top-4 right-4 w-8 h-8 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
style={{ background: `${tier.color}15` }}
>
<ArrowRight size={14} style={{ color: tier.color }} />
</div>
</motion.div>
))}
</div>
{/* Bottom note */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.5 }}
className="mt-8 text-center"
>
<p className="text-[13px]" style={{ color: 'var(--sky-700)', opacity: 0.5 }}>
Applies to flights departing from the EU or operated by an EU-based airline arriving in the EU
</p>
</motion.div>
</div>
</section>
);
}
export default Compensation;
~~~
Implementation Guidelines
1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system
Help me integrate this into my design