2025-05-13 18:50:30 +02:00
|
|
|
import React, { useState, useEffect } from 'react';
|
2025-06-03 18:29:34 +02:00
|
|
|
import { Menu, X } from 'lucide-react';
|
2025-06-03 14:07:33 +02:00
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
2025-05-13 18:50:30 +02:00
|
|
|
import { Home } from './components/Home';
|
|
|
|
|
import { YachtSearch } from './components/YachtSearch';
|
|
|
|
|
import { TripCalculator } from './components/TripCalculator';
|
2025-06-05 01:08:00 +02:00
|
|
|
import { MobileCalculator } from './components/MobileCalculator';
|
2025-05-13 18:50:30 +02:00
|
|
|
import { HowItWorks } from './components/HowItWorks';
|
|
|
|
|
import { About } from './components/About';
|
|
|
|
|
import { Contact } from './components/Contact';
|
|
|
|
|
import { OffsetOrder } from './components/OffsetOrder';
|
2025-10-29 21:45:14 +01:00
|
|
|
import CheckoutSuccess from './pages/CheckoutSuccess';
|
|
|
|
|
import CheckoutCancel from './pages/CheckoutCancel';
|
2025-05-13 18:50:30 +02:00
|
|
|
import { getVesselData } from './api/aisClient';
|
2025-06-03 14:07:33 +02:00
|
|
|
import { calculateTripCarbon } from './utils/carbonCalculator';
|
2025-05-13 18:50:30 +02:00
|
|
|
import { analytics } from './utils/analytics';
|
2025-10-30 16:31:30 +01:00
|
|
|
import { useCalculatorState } from './hooks/useCalculatorState';
|
2025-05-13 18:50:30 +02:00
|
|
|
import type { VesselData, CarbonCalculation, CalculatorType } from './types';
|
|
|
|
|
|
|
|
|
|
const sampleVessel: VesselData = {
|
|
|
|
|
imo: "1234567",
|
|
|
|
|
vesselName: "Sample Yacht",
|
|
|
|
|
type: "Yacht",
|
|
|
|
|
length: 50,
|
|
|
|
|
width: 9,
|
|
|
|
|
estimatedEnginePower: 2250
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function App() {
|
2025-10-30 16:31:30 +01:00
|
|
|
const { state: savedState, saveState } = useCalculatorState();
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
const [vesselData, setVesselData] = useState<VesselData | null>(null);
|
|
|
|
|
const [currentPage, setCurrentPage] = useState<'home' | 'calculator' | 'how-it-works' | 'about' | 'contact'>('home');
|
2025-10-30 16:31:30 +01:00
|
|
|
const [showOffsetOrder, setShowOffsetOrder] = useState(savedState?.showOffsetOrder || false);
|
|
|
|
|
const [offsetTons, setOffsetTons] = useState(savedState?.offsetTons || 0);
|
|
|
|
|
const [monetaryAmount, setMonetaryAmount] = useState<number | undefined>(savedState?.monetaryAmount);
|
|
|
|
|
const [calculatorType, setCalculatorType] = useState<CalculatorType>(savedState?.calculatorTypeUsed || 'trip');
|
2025-05-13 18:50:30 +02:00
|
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
2025-06-05 01:08:00 +02:00
|
|
|
const [isMobileApp, setIsMobileApp] = useState(false);
|
2025-10-29 21:45:14 +01:00
|
|
|
const [isCheckoutSuccess, setIsCheckoutSuccess] = useState(false);
|
|
|
|
|
const [isCheckoutCancel, setIsCheckoutCancel] = useState(false);
|
|
|
|
|
const [showHeader, setShowHeader] = useState(true);
|
|
|
|
|
const [lastScrollY, setLastScrollY] = useState(0);
|
2025-05-13 18:50:30 +02:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-10-29 21:45:14 +01:00
|
|
|
// Check if we're on special routes
|
2025-06-05 01:08:00 +02:00
|
|
|
const path = window.location.pathname;
|
|
|
|
|
setIsMobileApp(path === '/mobile-app');
|
2025-10-29 21:45:14 +01:00
|
|
|
setIsCheckoutSuccess(path === '/checkout/success');
|
|
|
|
|
setIsCheckoutCancel(path === '/checkout/cancel');
|
|
|
|
|
|
2025-06-05 01:08:00 +02:00
|
|
|
analytics.pageView(path);
|
2025-05-13 18:50:30 +02:00
|
|
|
}, [currentPage]);
|
|
|
|
|
|
2025-06-05 01:08:00 +02:00
|
|
|
useEffect(() => {
|
|
|
|
|
// Handle URL changes (for back/forward navigation)
|
|
|
|
|
const handlePopState = () => {
|
|
|
|
|
const path = window.location.pathname;
|
|
|
|
|
setIsMobileApp(path === '/mobile-app');
|
2025-10-29 21:45:14 +01:00
|
|
|
setIsCheckoutSuccess(path === '/checkout/success');
|
|
|
|
|
setIsCheckoutCancel(path === '/checkout/cancel');
|
2025-06-05 01:08:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('popstate', handlePopState);
|
|
|
|
|
return () => window.removeEventListener('popstate', handlePopState);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-10-29 21:45:14 +01:00
|
|
|
useEffect(() => {
|
|
|
|
|
// Hide header on scroll down, show on scroll up
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
const currentScrollY = window.scrollY;
|
|
|
|
|
|
|
|
|
|
// Always show header at the top of the page
|
|
|
|
|
if (currentScrollY < 10) {
|
|
|
|
|
setShowHeader(true);
|
|
|
|
|
} else if (currentScrollY > lastScrollY) {
|
|
|
|
|
// Scrolling down
|
|
|
|
|
setShowHeader(false);
|
|
|
|
|
} else {
|
|
|
|
|
// Scrolling up
|
|
|
|
|
setShowHeader(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setLastScrollY(currentScrollY);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
|
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
|
|
|
}, [lastScrollY]);
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
const handleSearch = async (imo: string) => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
setVesselData(null);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const vessel = await getVesselData(imo);
|
|
|
|
|
setVesselData(vessel);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setError('Unable to fetch vessel data. Please verify the IMO number and try again.');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleOffsetClick = (tons: number, monetaryAmount?: number) => {
|
|
|
|
|
setOffsetTons(tons);
|
|
|
|
|
setMonetaryAmount(monetaryAmount);
|
|
|
|
|
setShowOffsetOrder(true);
|
2025-10-30 16:31:30 +01:00
|
|
|
|
|
|
|
|
// Save offset state to localStorage for browser back navigation
|
|
|
|
|
saveState({
|
|
|
|
|
showOffsetOrder: true,
|
|
|
|
|
offsetTons: tons,
|
|
|
|
|
monetaryAmount,
|
|
|
|
|
calculatorTypeUsed: calculatorType,
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleNavigate = (page: 'home' | 'calculator' | 'how-it-works' | 'about' | 'contact') => {
|
|
|
|
|
setCurrentPage(page);
|
|
|
|
|
setMobileMenuOpen(false);
|
2025-06-05 01:08:00 +02:00
|
|
|
setIsMobileApp(false);
|
2025-10-30 13:55:51 +01:00
|
|
|
setIsCheckoutSuccess(false); // Clear checkout flags when navigating
|
|
|
|
|
setIsCheckoutCancel(false); // Clear checkout flags when navigating
|
2025-06-05 01:08:00 +02:00
|
|
|
window.history.pushState({}, '', `/${page === 'home' ? '' : page}`);
|
2025-05-13 18:50:30 +02:00
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-05 01:08:00 +02:00
|
|
|
const handleBackFromMobile = () => {
|
|
|
|
|
setIsMobileApp(false);
|
|
|
|
|
window.history.pushState({}, '', '/');
|
|
|
|
|
setCurrentPage('home');
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
const renderPage = () => {
|
|
|
|
|
if (currentPage === 'calculator' && showOffsetOrder) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex justify-center px-4 sm:px-0">
|
|
|
|
|
<OffsetOrder
|
|
|
|
|
tons={offsetTons}
|
|
|
|
|
monetaryAmount={monetaryAmount}
|
|
|
|
|
onBack={() => {
|
|
|
|
|
setShowOffsetOrder(false);
|
2025-10-30 16:31:30 +01:00
|
|
|
// Clear offset state from localStorage when going back
|
|
|
|
|
saveState({
|
|
|
|
|
showOffsetOrder: false,
|
|
|
|
|
offsetTons: 0,
|
|
|
|
|
monetaryAmount: undefined,
|
|
|
|
|
});
|
2025-05-13 18:50:30 +02:00
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
|
|
}}
|
|
|
|
|
calculatorType={calculatorType}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (currentPage) {
|
|
|
|
|
case 'calculator':
|
|
|
|
|
return (
|
2025-06-03 18:18:42 +02:00
|
|
|
<div className="flex flex-col items-center">
|
|
|
|
|
<div className="text-center mb-12 max-w-4xl">
|
2025-05-13 18:50:30 +02:00
|
|
|
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">
|
|
|
|
|
Calculate & Offset Your Yacht's Carbon Footprint
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-base sm:text-lg text-gray-600">
|
|
|
|
|
Use the calculator below to estimate your carbon footprint and explore offsetting options through our verified projects.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-03 18:18:42 +02:00
|
|
|
<div className="flex flex-col items-center w-full max-w-4xl space-y-8">
|
2025-05-13 18:50:30 +02:00
|
|
|
<TripCalculator
|
|
|
|
|
vesselData={sampleVessel}
|
|
|
|
|
onOffsetClick={handleOffsetClick}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
case 'how-it-works':
|
|
|
|
|
return <HowItWorks onNavigate={handleNavigate} />;
|
|
|
|
|
case 'about':
|
|
|
|
|
return <About onNavigate={handleNavigate} />;
|
|
|
|
|
case 'contact':
|
|
|
|
|
return <Contact />;
|
|
|
|
|
default:
|
|
|
|
|
return <Home onNavigate={handleNavigate} />;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-05 01:08:00 +02:00
|
|
|
// If we're on the mobile app route, render only the mobile calculator
|
|
|
|
|
if (isMobileApp) {
|
|
|
|
|
return (
|
|
|
|
|
<MobileCalculator
|
|
|
|
|
vesselData={sampleVessel}
|
|
|
|
|
onOffsetClick={handleOffsetClick}
|
|
|
|
|
onBack={handleBackFromMobile}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 21:45:14 +01:00
|
|
|
// If we're on the checkout success route, render only the success page
|
|
|
|
|
if (isCheckoutSuccess) {
|
2025-10-30 13:55:51 +01:00
|
|
|
return (
|
|
|
|
|
<CheckoutSuccess
|
|
|
|
|
onNavigateHome={() => handleNavigate('home')}
|
|
|
|
|
onNavigateCalculator={() => handleNavigate('calculator')}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2025-10-29 21:45:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we're on the checkout cancel route, render only the cancel page
|
|
|
|
|
if (isCheckoutCancel) {
|
2025-10-30 13:55:51 +01:00
|
|
|
return (
|
|
|
|
|
<CheckoutCancel
|
|
|
|
|
onNavigateHome={() => handleNavigate('home')}
|
|
|
|
|
onNavigateCalculator={() => handleNavigate('calculator')}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2025-10-29 21:45:14 +01:00
|
|
|
}
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
return (
|
2025-06-03 18:45:33 +02:00
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-cyan-50 wave-pattern">
|
2025-10-29 21:45:14 +01:00
|
|
|
<header
|
|
|
|
|
className="glass-nav shadow-luxury z-50 fixed top-0 left-0 right-0 transition-transform duration-300 ease-in-out"
|
|
|
|
|
style={{
|
|
|
|
|
transform: showHeader ? 'translate3d(0,0,0)' : 'translate3d(0,-100%,0)',
|
|
|
|
|
WebkitTransform: showHeader ? 'translate3d(0,0,0)' : 'translate3d(0,-100%,0)',
|
|
|
|
|
WebkitBackfaceVisibility: 'hidden',
|
|
|
|
|
backfaceVisibility: 'hidden'
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-06-03 18:45:33 +02:00
|
|
|
<div className="max-w-7xl mx-auto px-4 py-3 sm:px-6 lg:px-8">
|
2025-05-13 18:50:30 +02:00
|
|
|
<div className="flex items-center justify-between">
|
2025-06-03 18:45:33 +02:00
|
|
|
<motion.div
|
|
|
|
|
className="flex items-center space-x-3 cursor-pointer group"
|
2025-05-13 18:50:30 +02:00
|
|
|
onClick={() => handleNavigate('home')}
|
2025-06-03 18:45:33 +02:00
|
|
|
whileHover={{ scale: 1.02 }}
|
|
|
|
|
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
2025-05-13 18:50:30 +02:00
|
|
|
>
|
2025-06-03 18:45:33 +02:00
|
|
|
<motion.img
|
2025-06-03 18:29:34 +02:00
|
|
|
src="/puffinOffset.webp"
|
|
|
|
|
alt="Puffin Offset Logo"
|
2025-06-03 18:45:33 +02:00
|
|
|
className="h-10 w-auto transition-transform duration-300 group-hover:scale-110"
|
|
|
|
|
initial={{ opacity: 0, rotate: -10 }}
|
|
|
|
|
animate={{ opacity: 1, rotate: 0 }}
|
|
|
|
|
transition={{ duration: 0.6, delay: 0.2 }}
|
2025-06-03 18:29:34 +02:00
|
|
|
/>
|
2025-06-03 18:45:33 +02:00
|
|
|
<motion.h1
|
|
|
|
|
className="text-xl font-bold heading-luxury"
|
|
|
|
|
initial={{ opacity: 0, x: -20 }}
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
transition={{ duration: 0.6, delay: 0.4 }}
|
|
|
|
|
>
|
|
|
|
|
Puffin Offset
|
|
|
|
|
</motion.h1>
|
|
|
|
|
</motion.div>
|
2025-05-13 18:50:30 +02:00
|
|
|
|
|
|
|
|
{/* Mobile menu button */}
|
|
|
|
|
<button
|
|
|
|
|
className="sm:hidden p-2 rounded-md text-gray-600 hover:text-gray-900"
|
|
|
|
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
|
|
|
>
|
|
|
|
|
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{/* Desktop navigation */}
|
2025-06-03 18:45:33 +02:00
|
|
|
<nav className="hidden sm:flex space-x-2">
|
|
|
|
|
{['calculator', 'how-it-works', 'about', 'contact'].map((page, index) => (
|
|
|
|
|
<motion.button
|
|
|
|
|
key={page}
|
|
|
|
|
onClick={() => handleNavigate(page as any)}
|
|
|
|
|
className={`px-4 py-2 rounded-xl font-medium transition-all duration-300 ${
|
|
|
|
|
currentPage === page
|
|
|
|
|
? 'bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg'
|
|
|
|
|
: 'text-slate-600 hover:text-slate-900 hover:bg-white/60'
|
|
|
|
|
}`}
|
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
|
initial={{ opacity: 0, y: -10 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ duration: 0.4, delay: 0.6 + index * 0.1 }}
|
|
|
|
|
>
|
|
|
|
|
{page === 'how-it-works' ? 'How it Works' :
|
|
|
|
|
page.charAt(0).toUpperCase() + page.slice(1)}
|
|
|
|
|
</motion.button>
|
|
|
|
|
))}
|
2025-05-13 18:50:30 +02:00
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Mobile navigation */}
|
|
|
|
|
{mobileMenuOpen && (
|
|
|
|
|
<nav className="sm:hidden mt-4 pb-2 space-y-2">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleNavigate('calculator')}
|
|
|
|
|
className={`block w-full text-left px-4 py-2 rounded-lg ${
|
|
|
|
|
currentPage === 'calculator'
|
|
|
|
|
? 'bg-blue-50 text-blue-600 font-semibold'
|
|
|
|
|
: 'text-gray-600 hover:bg-gray-50'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
Calculator
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleNavigate('how-it-works')}
|
|
|
|
|
className={`block w-full text-left px-4 py-2 rounded-lg ${
|
|
|
|
|
currentPage === 'how-it-works'
|
|
|
|
|
? 'bg-blue-50 text-blue-600 font-semibold'
|
|
|
|
|
: 'text-gray-600 hover:bg-gray-50'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
How it Works
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleNavigate('about')}
|
|
|
|
|
className={`block w-full text-left px-4 py-2 rounded-lg ${
|
|
|
|
|
currentPage === 'about'
|
|
|
|
|
? 'bg-blue-50 text-blue-600 font-semibold'
|
|
|
|
|
: 'text-gray-600 hover:bg-gray-50'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
About
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleNavigate('contact')}
|
|
|
|
|
className={`block w-full text-left px-4 py-2 rounded-lg ${
|
|
|
|
|
currentPage === 'contact'
|
|
|
|
|
? 'bg-blue-50 text-blue-600 font-semibold'
|
|
|
|
|
: 'text-gray-600 hover:bg-gray-50'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
Contact
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
2025-10-29 21:45:14 +01:00
|
|
|
<main className="max-w-[1600px] mx-auto pt-24 pb-8 sm:pb-12 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
2025-06-03 14:07:33 +02:00
|
|
|
<AnimatePresence mode="wait">
|
|
|
|
|
<motion.div
|
|
|
|
|
key={currentPage + (showOffsetOrder ? '-offset' : '')}
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
exit={{ opacity: 0, y: -20 }}
|
|
|
|
|
transition={{
|
|
|
|
|
duration: 0.4,
|
|
|
|
|
ease: [0.25, 0.1, 0.25, 1.0]
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{renderPage()}
|
|
|
|
|
</motion.div>
|
|
|
|
|
</AnimatePresence>
|
2025-05-13 18:50:30 +02:00
|
|
|
</main>
|
|
|
|
|
|
2025-06-03 18:45:33 +02:00
|
|
|
<footer className="bg-gradient-to-r from-slate-900 via-blue-900 to-slate-900 mt-16 relative overflow-hidden">
|
|
|
|
|
<div className="absolute inset-0 bg-[url('data:image/svg+xml,%3csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='none' fill-rule='evenodd'%3e%3cg fill='%23ffffff' fill-opacity='0.03'%3e%3cpath d='m36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3e%3c/g%3e%3c/g%3e%3c/svg%3e')] opacity-20"></div>
|
|
|
|
|
<div className="relative max-w-7xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ duration: 0.6 }}
|
|
|
|
|
viewport={{ once: true }}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center space-x-3 mb-4">
|
|
|
|
|
<img
|
|
|
|
|
src="/puffinOffset.webp"
|
|
|
|
|
alt="Puffin Offset Logo"
|
|
|
|
|
className="h-8 w-auto"
|
|
|
|
|
/>
|
|
|
|
|
<h3 className="text-xl font-bold text-white">Puffin Offset</h3>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-slate-300 leading-relaxed">
|
|
|
|
|
The world's most exclusive carbon offsetting platform for superyacht owners and operators.
|
|
|
|
|
</p>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ duration: 0.6, delay: 0.2 }}
|
|
|
|
|
viewport={{ once: true }}
|
|
|
|
|
className="text-center md:text-left"
|
|
|
|
|
>
|
|
|
|
|
<h4 className="text-lg font-semibold text-white mb-4">Services</h4>
|
|
|
|
|
<ul className="space-y-2 text-slate-300">
|
|
|
|
|
<li className="hover:text-yellow-400 transition-colors cursor-pointer">Carbon Calculator</li>
|
|
|
|
|
<li className="hover:text-yellow-400 transition-colors cursor-pointer">Offset Portfolio</li>
|
|
|
|
|
<li className="hover:text-yellow-400 transition-colors cursor-pointer">Fleet Management</li>
|
|
|
|
|
<li className="hover:text-yellow-400 transition-colors cursor-pointer">Custom Solutions</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ duration: 0.6, delay: 0.4 }}
|
|
|
|
|
viewport={{ once: true }}
|
|
|
|
|
className="text-center md:text-left"
|
|
|
|
|
>
|
|
|
|
|
<h4 className="text-lg font-semibold text-white mb-4">Sustainability Partners</h4>
|
|
|
|
|
<p className="text-slate-300 mb-4">
|
|
|
|
|
Powered by verified carbon offset projects through Wren Climate
|
|
|
|
|
</p>
|
|
|
|
|
<div className="text-xs text-slate-400">
|
|
|
|
|
All projects are verified to international standards
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
className="border-t border-slate-700 pt-8 text-center"
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
whileInView={{ opacity: 1 }}
|
|
|
|
|
transition={{ duration: 0.6, delay: 0.6 }}
|
|
|
|
|
viewport={{ once: true }}
|
|
|
|
|
>
|
|
|
|
|
<p className="text-slate-400">
|
|
|
|
|
© 2024 Puffin Offset. Luxury meets sustainability.
|
|
|
|
|
</p>
|
|
|
|
|
</motion.div>
|
2025-05-13 18:50:30 +02:00
|
|
|
</div>
|
|
|
|
|
</footer>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 14:07:33 +02:00
|
|
|
export default App;
|