puffin-app/app/page.tsx
Matt 82f72941ca
Some checks failed
Build and Push Docker Images / docker (push) Failing after 1m58s
Migrate from Vite to Next.js 16 with Turbopack
This is a major migration from Vite to Next.js 16.0.1 for improved
performance, better SEO, and modern React features.

## Next.js Migration Changes
- Upgraded to Next.js 16.0.1 with Turbopack (from Vite 6)
- Migrated from client-side routing to App Router architecture
- Created app/ directory with Next.js page structure
- Added server components and client components pattern
- Configured standalone Docker builds for production

## Bug Fixes - React Hooks
- Fixed infinite loop in Header.tsx scroll behavior (removed lastScrollY state dependency)
- Fixed infinite loop in useCalculatorState.ts (wrapped saveState/clearState in useCallback)
- Fixed infinite loop in OffsetOrder.tsx (removed savedState from useEffect dependencies)
- Removed unused React imports from all client components

## Environment Variable Migration
- Migrated all VITE_ variables to NEXT_PUBLIC_ prefix
- Updated src/utils/config.ts to use direct static references (required for Next.js)
- Updated src/api/checkoutClient.ts, emailClient.ts, aisClient.ts for Next.js env vars
- Updated src/vite-env.d.ts types for Next.js environment
- Maintained backward compatibility with Docker window.env

## Layout & UX Improvements
- Fixed footer to always stay at bottom of viewport using flexbox
- Updated app/layout.tsx with flex-1 main content area
- Preserved glass morphism effects and luxury styling

## TypeScript & Build
- Fixed TypeScript strict mode compilation errors
- Removed unused imports and variables
- Fixed Axios interceptor types in project/src/api/wrenClient.ts
- Production build verified and passing

## Testing & Verification
- Tested calculator end-to-end in Playwright
- Verified Wren API integration working (11 portfolios fetched)
- Confirmed calculation: 5000L → 13.47 tons CO₂ → $3,206 total
- All navigation routes working correctly
- Footer positioning verified across all pages

## Files Added
- app/ directory with Next.js routes
- components/ directory with client components
- next.config.mjs, next-env.d.ts
- ENV_MIGRATION.md, NEXTJS_MIGRATION_COMPLETE.md documentation

## Files Modified
- Docker configuration for Next.js standalone builds
- package.json dependencies (Next.js, React 19)
- ts config.json for Next.js
- All API clients for new env var pattern

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 22:23:45 +01:00

338 lines
12 KiB
TypeScript

'use client';
import { useEffect, useRef, useState } from 'react';
import { Anchor, Waves, Shield, Award, ArrowRight } from 'lucide-react';
import { motion, useScroll, useTransform } from 'framer-motion';
import { useRouter } from 'next/navigation';
export default function Home() {
const router = useRouter();
const [, setMousePosition] = useState({ x: 0, y: 0 });
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: heroRef,
offset: ["start start", "end start"]
});
const y = useTransform(scrollYProgress, [0, 1], ["0%", "30%"]);
const opacity = useTransform(scrollYProgress, [0, 0.8], [1, 0]);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
const handleCalculateClick = () => {
router.push('/calculator');
};
const handleLearnMoreClick = () => {
router.push('/about');
};
// Animation variants
const fadeInUp = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: [0.22, 1, 0.36, 1]
}
}
};
const staggerContainer = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.3
}
}
};
const scaleOnHover = {
rest: { scale: 1 },
hover: {
scale: 1.05,
transition: {
type: "spring",
stiffness: 400,
damping: 17
}
}
};
return (
<div className="relative">
{/* Hero Section */}
<motion.div
ref={heroRef}
className="relative min-h-screen w-full flex items-center justify-center py-20"
style={{ y, opacity }}
>
{/* Hero Content */}
<div className="relative">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, delay: 0.5 }}
className="mb-8"
>
{/* Puffin Logo */}
<motion.img
src="/puffinOffset.webp"
alt="Puffin Offset Logo"
className="h-24 w-auto mx-auto mb-8"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, delay: 0.3 }}
/>
<motion.h1
className="text-6xl sm:text-7xl lg:text-8xl font-bold mb-6"
>
<span className="heading-luxury">Luxury Meets</span>
<br />
<span className="text-gradient-gold">Sustainability</span>
</motion.h1>
<motion.p
className="text-xl sm:text-2xl text-slate-600 max-w-4xl mx-auto leading-relaxed mb-12"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 1 }}
>
The world&apos;s most exclusive carbon offsetting platform for superyacht owners and operators
</motion.p>
</motion.div>
<motion.div
className="flex flex-col sm:flex-row gap-6 justify-center items-center"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.3 }}
>
<motion.button
onClick={handleCalculateClick}
className="btn-premium text-lg px-8 py-4"
whileHover={{ scale: 1.05, boxShadow: "0 10px 30px rgba(30, 64, 175, 0.4)" }}
whileTap={{ scale: 0.95 }}
>
Calculate Your Impact
<ArrowRight className="ml-2 inline" size={20} />
</motion.button>
<motion.button
onClick={handleLearnMoreClick}
className="btn-secondary text-lg px-8 py-4"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Discover More
</motion.button>
</motion.div>
</div>
</div>
</motion.div>
{/* Features Section with Luxury Styling */}
<div className="py-16 relative">
<motion.div
className="text-center mb-16"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
<h2 className="text-5xl font-bold heading-luxury mb-6">
Exclusive Maritime Solutions
</h2>
<p className="text-xl text-slate-600 max-w-3xl mx-auto">
Experience the pinnacle of sustainable luxury with our premium carbon offsetting services
</p>
</motion.div>
<motion.div
className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-16"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
>
<motion.div
className="luxury-card p-8"
variants={fadeInUp}
whileHover="hover"
>
<motion.div variants={scaleOnHover} className="h-full text-center">
<motion.div
className="w-16 h-16 bg-gradient-to-br from-blue-500 to-blue-600 rounded-full flex items-center justify-center mx-auto mb-6"
initial={{ rotate: -10, opacity: 0 }}
whileInView={{ rotate: 0, opacity: 1 }}
transition={{ duration: 0.6, delay: 0.2 }}
viewport={{ once: true }}
>
<Waves className="text-white" size={28} />
</motion.div>
<h3 className="text-2xl font-bold heading-luxury mb-4">Flexible Solutions</h3>
<p className="text-slate-600 leading-relaxed">
Tailor your offsetting to match your yachting lifestyle - from single trips to full annual emissions coverage.
</p>
</motion.div>
</motion.div>
<motion.div
className="luxury-card p-8"
variants={fadeInUp}
whileHover="hover"
>
<motion.div variants={scaleOnHover} className="h-full text-center">
<motion.div
className="w-16 h-16 bg-gradient-to-br from-yellow-400 to-yellow-500 rounded-full flex items-center justify-center mx-auto mb-6"
initial={{ rotate: 10, opacity: 0 }}
whileInView={{ rotate: 0, opacity: 1 }}
transition={{ duration: 0.6, delay: 0.4 }}
viewport={{ once: true }}
>
<Shield className="text-slate-800" size={28} />
</motion.div>
<h3 className="text-2xl font-bold heading-luxury mb-4">Verified Impact</h3>
<p className="text-slate-600 leading-relaxed">
Science-based projects with transparent reporting ensure your investment creates real environmental change.
</p>
</motion.div>
</motion.div>
<motion.div
className="luxury-card p-8"
variants={fadeInUp}
whileHover="hover"
>
<motion.div variants={scaleOnHover} className="h-full text-center">
<motion.div
className="w-16 h-16 bg-gradient-to-br from-green-500 to-green-600 rounded-full flex items-center justify-center mx-auto mb-6"
initial={{ rotate: -5, opacity: 0 }}
whileInView={{ rotate: 0, opacity: 1 }}
transition={{ duration: 0.6, delay: 0.6 }}
viewport={{ once: true }}
>
<Award className="text-white" size={28} />
</motion.div>
<h3 className="text-2xl font-bold heading-luxury mb-4">Premium Service</h3>
<p className="text-slate-600 leading-relaxed">
White-glove service designed for discerning yacht owners who demand excellence in sustainability.
</p>
</motion.div>
</motion.div>
</motion.div>
</div>
<motion.div
className="bg-white rounded-xl shadow-lg p-12 mb-16 text-center"
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, ease: "easeOut" }}
viewport={{ once: true, amount: 0.3 }}
>
<div className="max-w-4xl mx-auto">
<div className="flex items-center justify-center space-x-4 mb-6">
<motion.div
initial={{ y: -20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
transition={{
type: "spring",
stiffness: 300,
damping: 15,
delay: 0.2
}}
viewport={{ once: true }}
>
<Anchor className="text-blue-600" size={32} />
</motion.div>
<motion.h2
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
viewport={{ once: true }}
className="text-3xl font-bold text-gray-900"
>
Empower Your Yacht Business with In-House Offsetting
</motion.h2>
</div>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.4 }}
viewport={{ once: true }}
className="text-lg text-gray-600 leading-relaxed text-justify"
>
Our offsetting tool is not only perfect for charter guests and yacht owners, it can also be used by yacht management companies and brokerage firms seeking to integrate sustainability into the entirety of their operations. Use Puffin to offer clients carbon-neutral charter options or manage the environmental footprint of your fleet. Showcase your commitment to eco-conscious luxury while adding value to your services and elevating your brand.
</motion.p>
</div>
</motion.div>
<motion.div
className="text-center bg-white rounded-xl shadow-lg p-12 mb-16"
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, ease: "easeOut" }}
viewport={{ once: true, amount: 0.3 }}
>
<motion.h2
initial={{ opacity: 0, y: -10 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
viewport={{ once: true }}
className="text-3xl font-bold text-gray-900 mb-6"
>
Ready to Make a Difference?
</motion.h2>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.3 }}
viewport={{ once: true }}
className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto"
>
Join the growing community of environmentally conscious yacht owners and operators who are leading the way in maritime sustainability.
</motion.p>
<motion.div
className="flex justify-center space-x-4"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
viewport={{ once: true }}
>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
onClick={handleCalculateClick}
className="bg-blue-600 text-white px-8 py-3 rounded-lg"
>
Calculate Your Impact
</motion.button>
<motion.button
whileHover={{ scale: 1.05, backgroundColor: "rgba(219, 234, 254, 1)" }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
onClick={handleLearnMoreClick}
className="border-2 border-blue-600 text-blue-600 px-8 py-3 rounded-lg"
>
Learn More
</motion.button>
</motion.div>
</motion.div>
</div>
);
}