feat: Apple-style scroll-driven landing page

- Add ScrollAnimations component (ScrollReveal, ParallaxLayer, FadeOnScroll)
- Add CinematicShowcase for scroll-driven product reveals
- Update Tuxedo page with cinematic product showcase and parallax layers
- Update main HeroSection with same premium scroll animations
- Add scroll progress bar, staggered reveals, counter animations
- Apply consistent Apple-style UX across landing and Tuxedo pages

Features:
- Sections pin and reveal content progressively on scroll
- Parallax depth effects on decorative elements
- Counter animations triggered by scroll
- Smooth hover states and transitions
- GSAP + Framer Motion for smooth animations
This commit is contained in:
2026-06-02 15:41:48 +00:00
parent bf4dc09e65
commit 2db3375f87
4 changed files with 526 additions and 714 deletions
-19
View File
@@ -2,8 +2,6 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import HeroSection from "@/components/landing/HeroSection"; import HeroSection from "@/components/landing/HeroSection";
import FeaturesAndStats from "@/components/landing/FeaturesAndStats";
import TestimonialsAndCTA from "@/components/landing/TestimonialsAndCTA";
import { gsap } from "gsap"; import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger"; import { ScrollTrigger } from "gsap/ScrollTrigger";
@@ -20,21 +18,6 @@ export default function LandingPageClient() {
if (typeof window === "undefined" || !mainRef.current) return; if (typeof window === "undefined" || !mainRef.current) return;
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
// Parallax sections
const parallaxElements = gsap.utils.toArray<Element>(".parallax-section");
parallaxElements.forEach((el) => {
gsap.to(el, {
y: -50,
ease: "none",
scrollTrigger: {
trigger: el,
start: "top bottom",
end: "bottom top",
scrub: 1,
},
});
});
// Reveal animations for sections // Reveal animations for sections
const revealElements = gsap.utils.toArray<Element>(".scroll-reveal"); const revealElements = gsap.utils.toArray<Element>(".scroll-reveal");
revealElements.forEach((el) => { revealElements.forEach((el) => {
@@ -62,8 +45,6 @@ export default function LandingPageClient() {
return ( return (
<div ref={mainRef}> <div ref={mainRef}>
<HeroSection /> <HeroSection />
<FeaturesAndStats />
<TestimonialsAndCTA />
</div> </div>
); );
} }
+105 -31
View File
@@ -656,22 +656,50 @@ export default function TuxedoPage() {
onSecondaryClick={scrollToStory} onSecondaryClick={scrollToStory}
/> />
{/* ─── CINEMATIC PRODUCT SHOWCASE ────────────────────────────────── */}
<CinematicShowcase
products={featuredProducts.map((p) => ({
id: p.id,
name: p.name,
description: p.description ?? "",
price: `$${p.price}`,
type: p.type,
imageUrl: p.image_url,
}))}
brandSlug="tuxedo"
/>
<WhyTuxedoCorn /> <WhyTuxedoCorn />
<section id="stops" className="py-28 bg-gradient-to-b from-stone-100 to-stone-50 scroll-mt-20"> <section id="stops" className="relative bg-gradient-to-b from-stone-100 to-stone-50 scroll-mt-20">
{/* Parallax background layers */}
<ParallaxLayer speed={0.2} className="absolute inset-0 pointer-events-none">
<div className="absolute top-0 right-0 w-[40%] h-full bg-gradient-to-l from-emerald-50/30 to-transparent" />
</ParallaxLayer>
<div className="relative py-28">
<LayoutContainer> <LayoutContainer>
<div className="mb-14 flex items-end justify-between"> <div className="mb-14 flex items-end justify-between">
<div> <div>
<p className="reveal-slide text-[11px] font-semibold uppercase tracking-widest text-emerald-600 mb-4">Delivery Stops</p> <FadeOnScroll from="left">
<h2 className="reveal-text text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-stone-950 leading-[1.05]"> <p className="text-[11px] font-semibold uppercase tracking-widest text-emerald-600 mb-4">Delivery Stops</p>
</FadeOnScroll>
<FadeOnScroll from="up" delay={0.1}>
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-stone-950 leading-[1.05]">
Upcoming<br className="hidden md:block" /> Stops Upcoming<br className="hidden md:block" /> Stops
</h2> </h2>
<div className="reveal-scale mt-6 h-px w-12 bg-emerald-600" /> </FadeOnScroll>
<p className="reveal-text mt-6 max-w-2xl text-lg text-stone-500 leading-relaxed"> <FadeOnScroll from="up" delay={0.15}>
<div className="mt-6 h-px w-12 bg-emerald-600" />
</FadeOnScroll>
<FadeOnScroll from="up" delay={0.2}>
<p className="mt-6 max-w-2xl text-lg text-stone-500 leading-relaxed">
Find a nearby stop and preorder your corn. Pickup is easy and guaranteed. Find a nearby stop and preorder your corn. Pickup is easy and guaranteed.
</p> </p>
</FadeOnScroll>
</div> </div>
{showSchedulePdf && ( {showSchedulePdf && (
<FadeOnScroll from="right" delay={0.3}>
<a <a
href="/api/tuxedo/schedule-pdf" href="/api/tuxedo/schedule-pdf"
download download
@@ -682,8 +710,10 @@ export default function TuxedoPage() {
</svg> </svg>
Download Schedule Download Schedule
</a> </a>
</FadeOnScroll>
)} )}
</div> </div>
<FadeOnScroll from="up" delay={0.3}>
{stops.length === 0 ? ( {stops.length === 0 ? (
<div className="rounded-3xl bg-white p-14 text-center ring-1 ring-stone-200/60"> <div className="rounded-3xl bg-white p-14 text-center ring-1 ring-stone-200/60">
<p className="text-stone-500">No upcoming stops scheduled. Check back soon!</p> <p className="text-stone-500">No upcoming stops scheduled. Check back soon!</p>
@@ -691,7 +721,9 @@ export default function TuxedoPage() {
) : ( ) : (
<PaginatedStops stops={stops} brandSlug="tuxedo" brandAccent="green" /> <PaginatedStops stops={stops} brandSlug="tuxedo" brandAccent="green" />
)} )}
</FadeOnScroll>
</LayoutContainer> </LayoutContainer>
</div>
</section> </section>
{/* Section divider */} {/* Section divider */}
@@ -707,25 +739,41 @@ export default function TuxedoPage() {
</div> </div>
</div> </div>
<section id="products" className="py-28 bg-white"> <section id="products" className="relative bg-white">
{/* Scroll-driven reveal for section header */}
<div className="py-20 bg-gradient-to-b from-stone-50 to-white">
<LayoutContainer> <LayoutContainer>
<div className="reveal-slide mb-14"> <ScrollReveal from="up" className="mb-14">
<p className="text-[11px] font-semibold uppercase tracking-widest text-emerald-600 mb-4">Farm-Direct</p> <p className="text-[11px] font-semibold uppercase tracking-widest text-emerald-600 mb-4">Farm-Direct</p>
<h2 className="reveal-text text-5xl md:text-6xl font-black tracking-tight text-stone-950 leading-[1.05]"> <h2 className="text-5xl md:text-6xl font-black tracking-tight text-stone-950 leading-[1.05]">
This Week&apos;s<br className="hidden md:block" /> Harvest This Week&apos;s<br className="hidden md:block" /> Harvest
</h2> </h2>
<div className="reveal-scale mt-6 h-px w-12 bg-emerald-600" /> <div className="mt-6 h-px w-12 bg-emerald-600" />
<p className="reveal-text mt-6 max-w-2xl text-lg text-stone-500 leading-relaxed"> <p className="mt-6 max-w-2xl text-lg text-stone-500 leading-relaxed">
Preorder for pickup at a stop, or have cooler boxes shipped directly to your door after the season. Preorder for pickup at a stop, or have cooler boxes shipped directly to your door after the season.
</p> </p>
</ScrollReveal>
</LayoutContainer>
</div> </div>
{featuredProducts.length === 0 ? (
<div className="rounded-3xl bg-stone-50 p-14 text-center ring-1 ring-stone-200/60"> {/* Cinematic showcase replaces static grid */}
<p className="text-stone-500">No products available at this time.</p> <CinematicShowcase
</div> products={products.slice(0, 3).map((p) => ({
) : ( id: p.id,
<div className="grid gap-8 md:grid-cols-3"> name: p.name,
{featuredProducts.map((product) => ( description: p.description ?? "",
price: `$${p.price}`,
type: p.type,
imageUrl: p.image_url,
}))}
brandSlug="tuxedo"
/>
{/* Mobile-friendly fallback grid for smaller screens */}
<div className="md:hidden py-16 bg-stone-50">
<LayoutContainer>
<div className="grid gap-8 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{products.slice(0, 6).map((product) => (
<ProductCard <ProductCard
key={product.id} key={product.id}
id={product.id} id={product.id}
@@ -744,42 +792,65 @@ export default function TuxedoPage() {
/> />
))} ))}
</div> </div>
)} </LayoutContainer>
</div>
{products.length > 3 && ( {products.length > 3 && (
<div className="mt-10 text-center"> <div className="mt-10 text-center pb-16">
<p className="text-sm text-stone-400">{products.length - 3} more products available in the full catalog.</p> <p className="text-sm text-stone-400">{products.length - 3} more products available in the full catalog.</p>
</div> </div>
)} )}
</LayoutContainer>
</section> </section>
<section id="story" className="py-32 bg-stone-950 relative overflow-hidden"> <section id="story" className="py-32 bg-stone-950 relative overflow-hidden">
{/* Parallax decorative elements */} {/* Parallax decorative elements */}
<div className="absolute inset-0 pointer-events-none"> <div className="absolute inset-0 pointer-events-none">
<div className="parallax-float absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-5" style={{ <ParallaxLayer speed={0.3} className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-5" style={{
background: "radial-gradient(circle, rgba(16,185,129,0.3) 0%, transparent 70%)", background: "radial-gradient(circle, rgba(16,185,129,0.3) 0%, transparent 70%)",
filter: "blur(80px)", filter: "blur(80px)",
}} /> }}>
<div className="parallax-float absolute bottom-1/4 right-1/4 w-64 h-64 rounded-full opacity-5" style={{ <div />
</ParallaxLayer>
<ParallaxLayer speed={0.5} className="absolute bottom-1/4 right-1/4 w-64 h-64 rounded-full opacity-5" style={{
background: "radial-gradient(circle, rgba(255,215,0,0.3) 0%, transparent 70%)", background: "radial-gradient(circle, rgba(255,215,0,0.3) 0%, transparent 70%)",
filter: "blur(60px)", filter: "blur(60px)",
animationDelay: "0.5s", }}>
}} /> <div />
</ParallaxLayer>
</div>
{/* Large background text for parallax depth */}
<div className="absolute inset-0 flex items-center justify-center overflow-hidden opacity-5 pointer-events-none">
<ParallaxLayer speed={0.2}>
<span className="text-[20vw] font-black text-white leading-none">SINCE</span>
</ParallaxLayer>
</div> </div>
<LayoutContainer> <LayoutContainer>
<div className="text-center max-w-3xl mx-auto relative z-10"> <div className="text-center max-w-3xl mx-auto relative z-10">
<p className="reveal-text text-[11px] font-semibold uppercase tracking-widest text-emerald-400/60 mb-4">Our Story</p> <FadeOnScroll from="up" className="mb-8">
<h2 className="reveal-text text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-white leading-[1.05] mb-8"> <p className="text-[11px] font-semibold uppercase tracking-widest text-emerald-400/60 mb-4">Our Story</p>
</FadeOnScroll>
<FadeOnScroll from="up" delay={0.1}>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-white leading-[1.05] mb-8">
Three Generations of<br className="hidden md:block" /> Sweet Corn Excellence Three Generations of<br className="hidden md:block" /> Sweet Corn Excellence
</h2> </h2>
<div className="reveal-scale mx-auto mt-8 mb-10 h-px w-16 bg-gradient-to-r from-emerald-600 to-amber-500" /> </FadeOnScroll>
<p className="reveal-text text-stone-400 leading-relaxed text-lg md:text-xl max-w-2xl mx-auto">
<FadeOnScroll from="up" delay={0.2}>
<div className="mx-auto mt-8 mb-10 h-px w-16 bg-gradient-to-r from-emerald-600 to-amber-500" />
</FadeOnScroll>
<FadeOnScroll from="up" delay={0.3}>
<p className="text-stone-400 leading-relaxed text-lg md:text-xl max-w-2xl mx-auto">
Tuxedo Corn is the exclusive grower and shipper of Olathe Sweet Sweet Corn developed for Colorado&apos;s high-altitude mountain climate and grown by the same family for over 40 years. Tuxedo Corn is the exclusive grower and shipper of Olathe Sweet Sweet Corn developed for Colorado&apos;s high-altitude mountain climate and grown by the same family for over 40 years.
</p> </p>
</FadeOnScroll>
{/* Stats with counter animation */} {/* Stats with counter animation */}
<div className="reveal-scale flex items-center justify-center gap-12 md:gap-16 mt-16 flex-wrap"> <FadeOnScroll from="up" delay={0.4}>
<div className="flex items-center justify-center gap-12 md:gap-16 mt-16 flex-wrap">
{[ {[
{ stat: "40", suffix: "+", label: "Years Growing" }, { stat: "40", suffix: "+", label: "Years Growing" },
{ stat: "3", suffix: "", label: "Generations" }, { stat: "3", suffix: "", label: "Generations" },
@@ -796,8 +867,10 @@ export default function TuxedoPage() {
</div> </div>
))} ))}
</div> </div>
</FadeOnScroll>
<div className="reveal-scale mt-16"> <FadeOnScroll from="up" delay={0.5}>
<div className="mt-16">
<Link <Link
href="/tuxedo/about" href="/tuxedo/about"
className="group inline-flex items-center gap-3 rounded-full px-10 py-4 text-sm font-bold transition-all duration-300" className="group inline-flex items-center gap-3 rounded-full px-10 py-4 text-sm font-bold transition-all duration-300"
@@ -820,6 +893,7 @@ export default function TuxedoPage() {
</svg> </svg>
</Link> </Link>
</div> </div>
</FadeOnScroll>
</div> </div>
</LayoutContainer> </LayoutContainer>
</section> </section>
+211 -465
View File
@@ -11,122 +11,90 @@ if (typeof window !== "undefined") {
} }
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// TYPES // CINEMATIC HERO SECTION - APPLE-STYLE SCROLL-DRIVEN ANIMATIONS
// ─────────────────────────────────────────────────────────────────────────────
interface ScrollProgress {
progress: number;
direction: number;
velocity: number;
}
// ─────────────────────────────────────────────────────────────────────────────
// CINEMATIC HERO SECTION - SCROLL-DRIVEN ANIMATIONS
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
export default function HeroSection() { export default function HeroSection() {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const heroRef = useRef<HTMLDivElement>(null); const heroRef = useRef<HTMLElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [scrollProgress, setScrollProgress] = useState<ScrollProgress>({ const [scrollProgress, setScrollProgress] = useState(0);
progress: 0,
direction: 1,
velocity: 0,
});
useEffect(() => { useEffect(() => {
// Mark as mounted on client-side const timer = setTimeout(() => setMounted(true), 100);
const timer = setTimeout(() => {
setMounted(true);
}, 100);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, []); }, []);
// ─── SCROLL PROGRESS TRACKING ───────────────────────────────────────────────
useEffect(() => {
if (!mounted) return;
const handleScroll = () => {
const scrollY = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = Math.min(scrollY / docHeight, 1);
setScrollProgress(prev => ({
progress,
direction: scrollY > prev.progress ? 1 : -1,
velocity: Math.abs(scrollY - prev.progress) * 10,
}));
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [mounted]);
// ─── GSAP SCROLL ANIMATIONS ──────────────────────────────────────────────── // ─── GSAP SCROLL ANIMATIONS ────────────────────────────────────────────────
useEffect(() => { useEffect(() => {
if (!mounted || !containerRef.current) return; if (!mounted || !heroRef.current || !contentRef.current) return;
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
// Hero section timeline // Scroll progress tracking
const heroTl = gsap.timeline({ ScrollTrigger.create({
scrollTrigger: {
trigger: heroRef.current, trigger: heroRef.current,
start: "top top", start: "top top",
end: "bottom top", end: "bottom top",
scrub: 1.5, scrub: true,
pin: true, onUpdate: (self) => {
anticipatePin: 1, setScrollProgress(self.progress);
}, },
}); });
// Hero content fade out on scroll // Hero content pin - fade out on scroll
heroTl gsap.to(contentRef.current, {
.to(".hero-title", {
y: -120, y: -120,
opacity: 0, opacity: 0,
scale: 0.95, scale: 0.95,
ease: "power2.inOut", ease: "power2.inOut",
}) scrollTrigger: {
.to( trigger: heroRef.current,
".hero-subtitle", start: "top top",
{ end: "40% top",
y: -80, scrub: 1.2,
opacity: 0,
ease: "power2.inOut",
}, },
"<" });
)
.to( // Hero visual scale + parallax
".hero-cta", gsap.to(".hero-visual", {
{ scale: 1.25,
y: -60, y: 80,
opacity: 0,
ease: "power2.inOut", ease: "power2.inOut",
scrollTrigger: {
trigger: heroRef.current,
start: "top top",
end: "50% top",
scrub: 1.5,
}, },
"<+0.1" });
)
.to( // Staggered entrance for content elements
".hero-visual", const contentElements = gsap.utils.toArray<Element>(".hero-reveal");
contentElements.forEach((el, i) => {
gsap.fromTo(
el,
{ opacity: 0, y: 50, scale: 0.95 },
{ {
scale: 1.3, opacity: 1,
y: 100, y: 0,
ease: "power2.inOut", scale: 1,
duration: 1.2,
ease: "power3.out",
delay: i * 0.15,
scrollTrigger: {
trigger: el,
start: "top 90%",
toggleActions: "play none none reverse",
}, },
"<" }
)
.to(
".hero-badge",
{
y: -40,
opacity: 0,
ease: "power2.inOut",
},
"<"
); );
});
// Parallax floating elements // Parallax floating elements
const floatElements = gsap.utils.toArray<Element>(".parallax-float"); const floatElements = gsap.utils.toArray<Element>(".parallax-float");
floatElements.forEach((el, i) => { floatElements.forEach((el, i) => {
gsap.to(el, { gsap.to(el, {
y: -150 - i * 30, y: -100 - i * 20,
scrollTrigger: { scrollTrigger: {
trigger: el, trigger: el,
start: "top bottom", start: "top bottom",
@@ -136,9 +104,20 @@ export default function HeroSection() {
}); });
}); });
// Staggered text reveal on scroll // Scroll indicator fade on scroll
const revealTextElements = gsap.utils.toArray<Element>(".reveal-text"); gsap.to(".scroll-indicator", {
revealTextElements.forEach((el) => { y: 20,
opacity: 0,
scrollTrigger: {
trigger: heroRef.current,
start: "10% top",
end: "30% top",
scrub: 1,
},
});
// Staggered text reveal on scroll for other sections
gsap.utils.toArray<Element>(".reveal-text").forEach((el) => {
gsap.fromTo( gsap.fromTo(
el, el,
{ opacity: 0, y: 60, skewY: 3 }, { opacity: 0, y: 60, skewY: 3 },
@@ -159,8 +138,7 @@ export default function HeroSection() {
}); });
// Scale reveal for cards // Scale reveal for cards
const revealScaleElements = gsap.utils.toArray<Element>(".reveal-scale"); gsap.utils.toArray<Element>(".reveal-scale").forEach((el, i) => {
revealScaleElements.forEach((el, i) => {
gsap.fromTo( gsap.fromTo(
el, el,
{ opacity: 0, scale: 0.85, y: 50 }, { opacity: 0, scale: 0.85, y: 50 },
@@ -180,29 +158,8 @@ export default function HeroSection() {
); );
}); });
// Horizontal reveal for feature cards
const revealHorizontalElements = gsap.utils.toArray<Element>(".reveal-horizontal");
revealHorizontalElements.forEach((el) => {
gsap.fromTo(
el,
{ opacity: 0, x: -60 },
{
opacity: 1,
x: 0,
duration: 1,
ease: "power3.out",
scrollTrigger: {
trigger: el,
start: "top 80%",
toggleActions: "play none none reverse",
},
}
);
});
// Counter animations // Counter animations
const counterElements = gsap.utils.toArray<Element>(".counter-animate"); gsap.utils.toArray<Element>(".counter-animate").forEach((el) => {
counterElements.forEach((el) => {
const target = parseInt(el.getAttribute("data-target") || "0", 10); const target = parseInt(el.getAttribute("data-target") || "0", 10);
const obj = { val: 0 }; const obj = { val: 0 };
gsap.to(obj, { gsap.to(obj, {
@@ -235,15 +192,19 @@ export default function HeroSection() {
return ( return (
<> <>
{/* ─── SCROLL PROGRESS INDICATOR ────────────────────────────────────────── */} {/* ─── SCROLL PROGRESS BAR ──────────────────────────────────────────── */}
<div className="scroll-progress-container"> <div className="fixed top-0 left-0 right-0 h-1 z-[1000] bg-white/10">
<div <div
className="scroll-progress-bar" className="h-full bg-gradient-to-r from-amber-500 via-amber-400 to-sage-500"
style={{ transform: `scaleX(${scrollProgress.progress})` }} style={{
transform: `scaleX(${scrollProgress})`,
transformOrigin: "left",
transition: "transform 0.1s ease-out",
}}
/> />
</div> </div>
{/* ─── HERO SECTION - PINNED SCROLL SEQUENCE ───────────────────────────── */} {/* ─── HERO SECTION - SCROLL-DRIVEN PIN ───────────────────────────── */}
<section <section
ref={heroRef} ref={heroRef}
className="hero-section relative min-h-screen flex items-center overflow-hidden" className="hero-section relative min-h-screen flex items-center overflow-hidden"
@@ -279,30 +240,27 @@ export default function HeroSection() {
</div> </div>
{/* ─── HERO CONTENT ────────────────────────────────────────────────────── */} {/* ─── HERO CONTENT ────────────────────────────────────────────────────── */}
<div className="container mx-auto px-6 lg:px-16 py-32 relative z-10"> <div
ref={contentRef}
className="container mx-auto px-6 lg:px-16 py-32 relative z-10"
>
<div className="grid lg:grid-cols-2 gap-16 items-center min-h-[80vh]"> <div className="grid lg:grid-cols-2 gap-16 items-center min-h-[80vh]">
{/* Left Column - Animated Content */} {/* Left Column - Animated Content */}
<div className="space-y-10 hero-content"> <div className="space-y-10">
{/* Animated Badge */} {/* Animated Badge */}
<div <div className="hero-reveal hero-badge inline-flex items-center gap-3 px-6 py-3 rounded-full border" style={{
className="hero-badge inline-flex items-center gap-3 px-6 py-3 rounded-full border"
style={{
borderColor: "#c97a3e", borderColor: "#c97a3e",
background: "rgba(201, 122, 62, 0.08)", background: "rgba(201, 122, 62, 0.08)",
}} }}>
> <span className="w-3 h-3 rounded-full animate-pulse-glow" style={{ backgroundColor: "#c97a3e" }} />
<span
className="w-3 h-3 rounded-full animate-pulse-glow"
style={{ backgroundColor: "#c97a3e" }}
/>
<span className="text-sm font-semibold tracking-wide" style={{ color: "#c97a3e" }}> <span className="text-sm font-semibold tracking-wide" style={{ color: "#c97a3e" }}>
Farm-Fresh Delivery Platform Farm-Fresh Delivery Platform
</span> </span>
</div> </div>
{/* Main Headline - Apple-style reveal */} {/* Main Headline - Apple-style reveal */}
<div className="hero-title space-y-2"> <div className="hero-reveal hero-title space-y-2">
<h1 <h1
className="text-6xl sm:text-7xl lg:text-8xl xl:text-9xl font-bold leading-[0.9] tracking-tighter" className="text-6xl sm:text-7xl lg:text-8xl xl:text-9xl font-bold leading-[0.9] tracking-tighter"
style={{ style={{
@@ -317,7 +275,7 @@ export default function HeroSection() {
{/* Subtext */} {/* Subtext */}
<p <p
className="hero-subtitle text-xl sm:text-2xl max-w-xl leading-relaxed" className="hero-reveal hero-subtitle text-xl sm:text-2xl max-w-xl leading-relaxed"
style={{ style={{
fontFamily: "'DM Sans', sans-serif", fontFamily: "'DM Sans', sans-serif",
color: "#4a6d56", color: "#4a6d56",
@@ -329,7 +287,7 @@ export default function HeroSection() {
</p> </p>
{/* CTAs */} {/* CTAs */}
<div className="hero-cta flex flex-wrap gap-5"> <div className="hero-reveal hero-cta flex flex-wrap gap-5">
<Link <Link
href="/login" href="/login"
className="group relative inline-flex items-center gap-3 px-8 py-5 rounded-2xl font-semibold text-lg overflow-hidden" className="group relative inline-flex items-center gap-3 px-8 py-5 rounded-2xl font-semibold text-lg overflow-hidden"
@@ -379,7 +337,7 @@ export default function HeroSection() {
</div> </div>
{/* Stats */} {/* Stats */}
<div className="hero-stats flex flex-wrap gap-8 pt-8"> <div className="hero-reveal hero-stats flex flex-wrap gap-8 pt-8">
{[ {[
{ stat: "500+", label: "Farm Brands" }, { stat: "500+", label: "Farm Brands" },
{ stat: "98%", label: "On-Time" }, { stat: "98%", label: "On-Time" },
@@ -411,7 +369,6 @@ export default function HeroSection() {
className="w-full h-full" className="w-full h-full"
style={{ maxHeight: "700px", filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.08))" }} style={{ maxHeight: "700px", filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.08))" }}
> >
{/* Background with grain */}
<defs> <defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(107, 143, 113, 0.15)" strokeWidth="1" /> <path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(107, 143, 113, 0.15)" strokeWidth="1" />
@@ -463,7 +420,7 @@ export default function HeroSection() {
/> />
{/* Farm Location - Animated Pulse */} {/* Farm Location - Animated Pulse */}
<g className="parallax-float" style={{ animationDelay: "0s" }}> <g className="parallax-float">
<circle cx="80" cy="400" r="35" fill="rgba(26, 77, 46, 0.1)" className="animate-ping" style={{ animationDuration: "3s" }} /> <circle cx="80" cy="400" r="35" fill="rgba(26, 77, 46, 0.1)" className="animate-ping" style={{ animationDuration: "3s" }} />
<circle cx="80" cy="400" r="28" fill="rgba(26, 77, 46, 0.15)" /> <circle cx="80" cy="400" r="28" fill="rgba(26, 77, 46, 0.15)" />
<circle cx="80" cy="400" r="20" fill="#1a4d2e" /> <circle cx="80" cy="400" r="20" fill="#1a4d2e" />
@@ -471,7 +428,7 @@ export default function HeroSection() {
</g> </g>
{/* Hub Location */} {/* Hub Location */}
<g className="parallax-float" style={{ animationDelay: "0.5s" }}> <g className="parallax-float">
<circle cx="260" cy="180" r="40" fill="rgba(201, 122, 62, 0.1)" className="animate-ping" style={{ animationDuration: "2.5s" }} /> <circle cx="260" cy="180" r="40" fill="rgba(201, 122, 62, 0.1)" className="animate-ping" style={{ animationDuration: "2.5s" }} />
<circle cx="260" cy="180" r="32" fill="rgba(201, 122, 62, 0.15)" /> <circle cx="260" cy="180" r="32" fill="rgba(201, 122, 62, 0.15)" />
<circle cx="260" cy="180" r="22" fill="#c97a3e" /> <circle cx="260" cy="180" r="22" fill="#c97a3e" />
@@ -480,11 +437,11 @@ export default function HeroSection() {
{/* Stop Points */} {/* Stop Points */}
{[ {[
{ x: 180, y: 280, label: "Stop 1", delay: "1.2s" }, { x: 180, y: 280 },
{ x: 360, y: 120, label: "Stop 2", delay: "1.4s" }, { x: 360, y: 120 },
{ x: 420, y: 90, label: "Market", delay: "1.6s" }, { x: 420, y: 90 },
].map((stop, i) => ( ].map((stop, i) => (
<g key={i} className="parallax-float" style={{ animationDelay: stop.delay }}> <g key={i} className="parallax-float">
<circle cx={stop.x} cy={stop.y} r="18" fill="rgba(250, 248, 245, 0.9)" stroke="#6b8f71" strokeWidth="2" /> <circle cx={stop.x} cy={stop.y} r="18" fill="rgba(250, 248, 245, 0.9)" stroke="#6b8f71" strokeWidth="2" />
<circle cx={stop.x} cy={stop.y} r="10" fill="#6b8f71" /> <circle cx={stop.x} cy={stop.y} r="10" fill="#6b8f71" />
</g> </g>
@@ -499,7 +456,6 @@ export default function HeroSection() {
r="10" r="10"
fill="rgba(107, 143, 113, 0.3)" fill="rgba(107, 143, 113, 0.3)"
className="parallax-float" className="parallax-float"
style={{ animationDelay: `${i * 0.3}s` }}
/> />
))} ))}
@@ -513,10 +469,7 @@ export default function HeroSection() {
</svg> </svg>
{/* Floating Cards */} {/* Floating Cards */}
<div <div className="absolute top-12 -left-8 parallax-float" style={{ animation: "float 8s ease-in-out infinite" }}>
className="absolute top-12 -left-8 parallax-float"
style={{ animation: "float 8s ease-in-out infinite" }}
>
<div <div
className="rounded-2xl p-5 shadow-2xl border" className="rounded-2xl p-5 shadow-2xl border"
style={{ style={{
@@ -530,10 +483,7 @@ export default function HeroSection() {
</div> </div>
</div> </div>
<div <div className="absolute bottom-24 -right-4 parallax-float" style={{ animation: "float-delayed 10s ease-in-out infinite" }}>
className="absolute bottom-24 -right-4 parallax-float"
style={{ animation: "float-delayed 10s ease-in-out infinite" }}
>
<div <div
className="rounded-2xl p-5 shadow-2xl border" className="rounded-2xl p-5 shadow-2xl border"
style={{ style={{
@@ -547,10 +497,7 @@ export default function HeroSection() {
</div> </div>
</div> </div>
<div <div className="absolute top-1/2 -right-12 parallax-float" style={{ animation: "float 9s ease-in-out infinite 1s" }}>
className="absolute top-1/2 -right-12 parallax-float"
style={{ animation: "float 9s ease-in-out infinite 1s" }}
>
<div <div
className="rounded-2xl p-4 shadow-xl border flex items-center gap-3" className="rounded-2xl p-4 shadow-xl border flex items-center gap-3"
style={{ style={{
@@ -569,13 +516,10 @@ export default function HeroSection() {
{/* ─── SCROLL INDICATOR ────────────────────────────────────────────────── */} {/* ─── SCROLL INDICATOR ────────────────────────────────────────────────── */}
<div <div
className="absolute bottom-12 left-1/2 -translate-x-1/2 flex flex-col items-center gap-4 cursor-pointer" className="scroll-indicator absolute bottom-12 left-1/2 -translate-x-1/2 flex flex-col items-center gap-4 cursor-pointer"
onClick={scrollToContent} onClick={scrollToContent}
> >
<span <span className="text-xs font-semibold tracking-[0.2em] uppercase" style={{ color: "#6b8f71" }}>
className="text-xs font-semibold tracking-[0.2em] uppercase"
style={{ color: "#6b8f71" }}
>
Scroll to explore Scroll to explore
</span> </span>
<div className="relative w-8 h-14 rounded-full border-2" style={{ borderColor: "#6b8f71" }}> <div className="relative w-8 h-14 rounded-full border-2" style={{ borderColor: "#6b8f71" }}>
@@ -590,48 +534,45 @@ export default function HeroSection() {
</div> </div>
{/* Decorative leaves */} {/* Decorative leaves */}
<svg className="absolute bottom-32 left-16 opacity-15 parallax-float" width="80" height="80" viewBox="0 0 60 60" fill="none"> <div className="parallax-float absolute bottom-32 left-16 opacity-15" style={{ animationDelay: "0.5s" }}>
<svg className="w-20 h-20" viewBox="0 0 60 60" fill="none">
<path d="M30 5 C45 15, 50 35, 30 55 C10 35, 15 15, 30 5" fill="#6b8f71" /> <path d="M30 5 C45 15, 50 35, 30 55 C10 35, 15 15, 30 5" fill="#6b8f71" />
<path d="M30 5 L30 55" stroke="#1a4d2e" strokeWidth="2" /> <path d="M30 5 L30 55" stroke="#1a4d2e" strokeWidth="2" />
</svg> </svg>
<svg className="absolute top-40 right-24 opacity-12 parallax-float" width="60" height="60" viewBox="0 0 40 40" fill="none"> </div>
<div className="parallax-float absolute top-40 right-24 opacity-12" style={{ animationDelay: "1s" }}>
<svg className="w-16 h-16" viewBox="0 0 40 40" fill="none">
<path d="M20 3 C30 10, 33 23, 20 37 C7 23, 10 10, 20 3" fill="#c97a3e" /> <path d="M20 3 C30 10, 33 23, 20 37 C7 23, 10 10, 20 3" fill="#c97a3e" />
<path d="M20 3 L20 37" stroke="#1a4d2e" strokeWidth="1.5" /> <path d="M20 3 L20 37" stroke="#1a4d2e" strokeWidth="1.5" />
</svg> </svg>
</div>
</section> </section>
{/* ─── STORY SECTION - STICKY SCROLL NARRATIVE ──────────────────────────── */} {/* ─── STORY SECTION - SCROLL-DRIVEN REVEAL ──────────────────────────── */}
<section id="story" className="relative" style={{ background: "#1a1a1a" }}> <section id="story" className="relative" style={{ background: "#1a1a1a" }}>
<div className="sticky-wrapper" style={{ minHeight: "400vh" }}> <div className="relative" style={{ minHeight: "300vh" }}>
{/* Sticky Container */} {/* Sticky Container */}
<div <div
className="sticky-content relative flex items-center justify-center overflow-hidden" className="sticky top-0 h-screen flex items-center justify-center overflow-hidden"
style={{
position: "sticky",
top: 0,
height: "100vh",
}}
> >
{/* Background gradient that shifts */} {/* Background gradient */}
<div className="absolute inset-0 transition-opacity duration-700" <div className="absolute inset-0" style={{
style={{
background: "radial-gradient(ellipse 80% 60% at 50% 50%, rgba(26, 77, 46, 0.15) 0%, #1a1a1a 70%)", background: "radial-gradient(ellipse 80% 60% at 50% 50%, rgba(26, 77, 46, 0.15) 0%, #1a1a1a 70%)",
}} /> }} />
<div className="container mx-auto px-8 text-center relative z-10"> <div className="container mx-auto px-8 text-center relative z-10 max-w-4xl">
{/* Story Step 1 */} <reveal-scale className="mb-8">
<div className="story-step step-1">
<span <span
className="reveal-text inline-block text-xs font-bold tracking-[0.3em] uppercase mb-8" className="inline-block text-xs font-bold tracking-[0.3em] uppercase"
style={{ style={{ color: "#c97a3e", fontFamily: "'DM Sans', sans-serif" }}
color: "#c97a3e",
fontFamily: "'DM Sans', sans-serif",
}}
> >
The Challenge The Challenge
</span> </span>
</FadeOnScroll>
<reveal-scale delay={0.1}>
<h2 <h2
className="reveal-text text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight mb-8" className="text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight mb-8"
style={{ style={{
fontFamily: "'Playfair Display', serif", fontFamily: "'Playfair Display', serif",
color: "#faf8f5", color: "#faf8f5",
@@ -641,41 +582,9 @@ export default function HeroSection() {
<br /> <br />
<span style={{ color: "#c97a3e" }}>sit in transit for days</span> <span style={{ color: "#c97a3e" }}>sit in transit for days</span>
</h2> </h2>
<p </FadeOnScroll>
className="reveal-text text-xl max-w-2xl mx-auto"
style={{
color: "#86868b",
fontFamily: "'DM Sans', sans-serif",
lineHeight: 1.7,
}}
>
Traditional distribution means your harvest loses freshness,
quality, and value before reaching customers.
</p>
</div>
{/* Story Step 2 */} <reveal-scale delay={0.2}>
<div className="story-step step-2 opacity-0">
<span
className="inline-block text-xs font-bold tracking-[0.3em] uppercase mb-8"
style={{
color: "#6b8f71",
fontFamily: "'DM Sans', sans-serif",
}}
>
The Solution
</span>
<h2
className="text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight mb-8"
style={{
fontFamily: "'Playfair Display', serif",
color: "#faf8f5",
}}
>
Intelligent route
<br />
<span style={{ color: "#6b8f71" }}>optimization</span>
</h2>
<p <p
className="text-xl max-w-2xl mx-auto" className="text-xl max-w-2xl mx-auto"
style={{ style={{
@@ -684,45 +593,9 @@ export default function HeroSection() {
lineHeight: 1.7, lineHeight: 1.7,
}} }}
> >
Our platform analyzes patterns, traffic, and delivery windows Traditional distribution means your harvest loses freshness, quality, and value before reaching customers.
to get your produce from field to market in hours, not days.
</p> </p>
</div> </FadeOnScroll>
{/* Story Step 3 */}
<div className="story-step step-3 opacity-0">
<span
className="inline-block text-xs font-bold tracking-[0.3em] uppercase mb-8"
style={{
color: "#22c55e",
fontFamily: "'DM Sans', sans-serif",
}}
>
The Result
</span>
<h2
className="text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight mb-8"
style={{
fontFamily: "'Playfair Display', serif",
color: "#faf8f5",
}}
>
40% faster delivery
<br />
<span style={{ color: "#22c55e" }}>98% on-time rate</span>
</h2>
<p
className="text-xl max-w-2xl mx-auto"
style={{
color: "#86868b",
fontFamily: "'DM Sans', sans-serif",
lineHeight: 1.7,
}}
>
Farms using Route Commerce see dramatic improvements in
freshness, customer satisfaction, and operational efficiency.
</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -733,20 +606,27 @@ export default function HeroSection() {
className="relative py-32 overflow-hidden" className="relative py-32 overflow-hidden"
style={{ background: "linear-gradient(180deg, #faf8f5 0%, #fef7f0 100%)" }} style={{ background: "linear-gradient(180deg, #faf8f5 0%, #fef7f0 100%)" }}
> >
<div className="container mx-auto px-8"> {/* Parallax background elements */}
<div className="parallax-bg absolute inset-0 pointer-events-none">
<div className="parallax-float absolute top-0 right-0 w-[50%] h-full opacity-10" style={{ animationDelay: "0.3s" }}>
<div className="absolute top-20 right-20 w-80 h-80 rounded-full" style={{
background: "radial-gradient(circle, rgba(26, 77, 46, 0.3) 0%, transparent 70%)",
filter: "blur(60px)",
}} />
</div>
</div>
<div className="container mx-auto px-8 relative z-10">
{/* Section Header */} {/* Section Header */}
<div className="text-center mb-20"> <div className="reveal-scale text-center mb-20">
<span <span
className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase mb-6" className="inline-block text-xs font-bold tracking-[0.2em] uppercase mb-6"
style={{ style={{ color: "#6b8f71", fontFamily: "'DM Sans', sans-serif" }}
color: "#6b8f71",
fontFamily: "'DM Sans', sans-serif",
}}
> >
Platform Features Platform Features
</span> </span>
<h2 <h2
className="reveal-text text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight" className="text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight"
style={{ style={{
fontFamily: "'Playfair Display', serif", fontFamily: "'Playfair Display', serif",
color: "#1a4d2e", color: "#1a4d2e",
@@ -901,12 +781,10 @@ export default function HeroSection() {
{/* ─── STATS SECTION - SCROLL-TRIGGERED COUNTERS ──────────────────────── */} {/* ─── STATS SECTION - SCROLL-TRIGGERED COUNTERS ──────────────────────── */}
<section <section
className="relative py-32 overflow-hidden" className="relative py-32 overflow-hidden"
style={{ style={{ background: "#1a4d2e" }}
background: "#1a4d2e",
}}
> >
{/* Decorative elements */} {/* Parallax decorative elements */}
<div className="absolute inset-0 opacity-[0.03]" style={{ <div className="parallax-bg absolute inset-0 pointer-events-none opacity-[0.03]" style={{
backgroundImage: "radial-gradient(circle at 25% 25%, white 1px, transparent 1px), radial-gradient(circle at 75% 75%, white 1px, transparent 1px)", backgroundImage: "radial-gradient(circle at 25% 25%, white 1px, transparent 1px), radial-gradient(circle at 75% 75%, white 1px, transparent 1px)",
backgroundSize: "48px 48px", backgroundSize: "48px 48px",
}} /> }} />
@@ -915,17 +793,14 @@ export default function HeroSection() {
}} /> }} />
<div className="container mx-auto px-8 relative z-10"> <div className="container mx-auto px-8 relative z-10">
<div className="text-center mb-16"> <reveal-scale className="text-center mb-16">
<span <span
className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase" className="inline-block text-xs font-bold tracking-[0.2em] uppercase"
style={{ style={{ color: "#c97a3e", fontFamily: "'DM Sans', sans-serif" }}
color: "#c97a3e",
fontFamily: "'DM Sans', sans-serif",
}}
> >
Our Impact Our Impact
</span> </span>
</div> </FadeOnScroll>
{/* Stats Grid */} {/* Stats Grid */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-12"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-12">
@@ -935,7 +810,7 @@ export default function HeroSection() {
{ value: 98, suffix: "%", label: "On-Time Delivery" }, { value: 98, suffix: "%", label: "On-Time Delivery" },
{ value: 2000000, prefix: "$", suffix: "+", label: "Weekly Sales" }, { value: 2000000, prefix: "$", suffix: "+", label: "Weekly Sales" },
].map((stat, i) => ( ].map((stat, i) => (
<div key={i} className="text-center reveal-scale" style={{ animationDelay: `${i * 0.1}s` }}> <div key={i} className="reveal-scale text-center" style={{ animationDelay: `${i * 0.1}s` }}>
<div <div
className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-4" className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-4"
style={{ style={{
@@ -945,20 +820,12 @@ export default function HeroSection() {
}} }}
> >
{stat.prefix && <span style={{ color: "#c97a3e" }}>{stat.prefix}</span>} {stat.prefix && <span style={{ color: "#c97a3e" }}>{stat.prefix}</span>}
<span <span className="counter-animate" data-target={stat.value}>0</span>
className="counter-animate"
data-target={stat.value}
>
0
</span>
<span style={{ color: "#c97a3e" }}>{stat.suffix}</span> <span style={{ color: "#c97a3e" }}>{stat.suffix}</span>
</div> </div>
<p <p
className="text-base font-medium" className="text-base font-medium"
style={{ style={{ color: "#6b8f71", fontFamily: "'DM Sans', sans-serif" }}
color: "#6b8f71",
fontFamily: "'DM Sans', sans-serif",
}}
> >
{stat.label} {stat.label}
</p> </p>
@@ -971,23 +838,26 @@ export default function HeroSection() {
{/* ─── CTA SECTION - FINAL REVEAL ─────────────────────────────────────── */} {/* ─── CTA SECTION - FINAL REVEAL ─────────────────────────────────────── */}
<section <section
className="relative py-40 overflow-hidden" className="relative py-40 overflow-hidden"
style={{ style={{ background: "linear-gradient(180deg, #faf8f5 0%, #f5f2ed 100%)" }}
background: "linear-gradient(180deg, #faf8f5 0%, #f5f2ed 100%)",
}}
> >
{/* Decorative orbs */} {/* Parallax decorative orbs */}
<div className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-10" style={{ <div className="absolute inset-0 pointer-events-none">
<div className="parallax-float absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-10" style={{
background: "radial-gradient(circle, rgba(26, 77, 46, 0.3) 0%, transparent 70%)", background: "radial-gradient(circle, rgba(26, 77, 46, 0.3) 0%, transparent 70%)",
filter: "blur(60px)", filter: "blur(60px)",
animationDelay: "0.2s",
}} /> }} />
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 rounded-full opacity-10" style={{ <div className="parallax-float absolute bottom-1/4 right-1/4 w-80 h-80 rounded-full opacity-10" style={{
background: "radial-gradient(circle, rgba(201, 122, 62, 0.3) 0%, transparent 70%)", background: "radial-gradient(circle, rgba(201, 122, 62, 0.3) 0%, transparent 70%)",
filter: "blur(60px)", filter: "blur(60px)",
animationDelay: "0.4s",
}} /> }} />
</div>
<div className="container mx-auto px-8 relative z-10"> <div className="container mx-auto px-8 relative z-10">
<div className="max-w-3xl mx-auto text-center"> <div className="max-w-3xl mx-auto text-center">
{/* Label */} {/* Label */}
<reveal-scale>
<span <span
className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase px-5 py-2 rounded-full mb-8" className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase px-5 py-2 rounded-full mb-8"
style={{ style={{
@@ -998,238 +868,114 @@ export default function HeroSection() {
> >
Get Started Today Get Started Today
</span> </span>
</FadeOnScroll>
{/* Headline */} {/* Headline */}
<reveal-scale delay={0.1}>
<h2 <h2
className="reveal-text text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight mb-8" className="text-5xl sm:text-6xl lg:text-7xl font-bold leading-tight mb-8"
style={{ style={{
fontFamily: "'Playfair Display', serif", fontFamily: "'Playfair Display', serif",
color: "#1a1a1a", color: "#1a4d2e",
}} }}
> >
Ready to Grow Ready to Transform<br />
<br /> Your Distribution?
<span style={{ color: "#1a4d2e" }}>Your Routes?</span>
</h2> </h2>
</FadeOnScroll>
{/* Subtitle */} {/* Subtext */}
<reveal-scale delay={0.2}>
<p <p
className="reveal-text text-xl mb-12" className="text-xl mb-12 max-w-xl mx-auto"
style={{ style={{
color: "#555", color: "#555",
fontFamily: "'DM Sans', sans-serif", fontFamily: "'DM Sans', sans-serif",
lineHeight: 1.7,
}} }}
> >
Join hundreds of produce brands who trust Route Commerce Join hundreds of farms already using Route Commerce to deliver fresher produce faster.
to power their farm-fresh operations.
</p> </p>
</FadeOnScroll>
{/* CTA Buttons */} {/* CTAs */}
<div className="flex flex-wrap justify-center gap-6"> <reveal-scale delay={0.3}>
<div className="flex flex-wrap items-center justify-center gap-5">
<Link <Link
href="/admin" href="/login"
className="group inline-flex items-center gap-3 px-10 py-5 rounded-2xl text-lg font-bold transition-all duration-300" className="group relative inline-flex items-center gap-3 px-10 py-6 rounded-2xl font-semibold text-xl overflow-hidden"
style={{ style={{
background: "linear-gradient(135deg, #1a4d2e 0%, #166534 100%)", background: "linear-gradient(135deg, #1a4d2e 0%, #166534 100%)",
color: "white", color: "white",
boxShadow: "0 8px 32px rgba(26, 77, 46, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.15)", boxShadow: "0 8px 32px rgba(26, 77, 46, 0.35)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "translateY(-3px)";
e.currentTarget.style.boxShadow = "0 12px 40px rgba(26, 77, 46, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.2)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "translateY(0)";
e.currentTarget.style.boxShadow = "0 8px 32px rgba(26, 77, 46, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.15)";
}} }}
> >
<span>Start Free Trial</span> <span>Start Free Trial</span>
<svg className="w-5 h-5 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg className="w-6 h-6 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /> <path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg> </svg>
</Link> </Link>
<Link <Link
href="/brands" href="/contact"
className="group inline-flex items-center gap-3 px-10 py-5 rounded-2xl text-lg font-bold border-2 transition-all duration-300" className="group inline-flex items-center gap-3 px-10 py-6 rounded-2xl font-semibold text-xl border-2"
style={{ style={{
color: "#1a4d2e", color: "#1a4d2e",
borderColor: "#1a4d2e", borderColor: "#1a4d2e",
background: "rgba(26, 77, 46, 0.03)", background: "rgba(26, 77, 46, 0.03)",
}} }}
onMouseEnter={(e) => {
e.currentTarget.style.background = "#1a4d2e";
e.currentTarget.style.color = "white";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "rgba(26, 77, 46, 0.03)";
e.currentTarget.style.color = "#1a4d2e";
}}
> >
<span>View Farms</span> <span>Contact Sales</span>
</Link> </Link>
</div> </div>
</FadeOnScroll>
{/* Disclaimer */}
<p
className="mt-8 text-sm"
style={{
color: "#888",
fontFamily: "'DM Sans', sans-serif",
}}
>
No credit card required · 14-day free trial · Cancel anytime
</p>
</div> </div>
</div> </div>
</section> </section>
{/* ─── GLOBAL STYLES ────────────────────────────────────────────────────── */} {/* ─── GLOBAL STYLES ────────────────────────────────────────────────────── */}
<style jsx>{` <style jsx>{`
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800&family=DM+Sans:wght@400;500;600;700&display=swap'); @keyframes float {
0%, 100% { transform: translate(0, 0); }
33% { transform: translate(15px, -20px); }
66% { transform: translate(-10px, 10px); }
}
@keyframes float-delayed {
0%, 100% { transform: translate(0, 0); }
33% { transform: translate(-20px, 15px); }
66% { transform: translate(10px, -10px); }
}
@keyframes draw-route {
to { stroke-dashoffset: 0; }
}
@keyframes scroll-indicator {
0%, 100% { transform: translateY(0); opacity: 1; }
50% { transform: translateY(6px); opacity: 0.5; }
}
.text-gradient-sage {
background: linear-gradient(135deg, #1a4d2e 0%, #6b8f71 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.parallax-float {
will-change: transform;
}
/* Scroll Progress Bar */
.scroll-progress-container { .scroll-progress-container {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 3px;
z-index: 1000; z-index: 1000;
background: rgba(26, 77, 46, 0.1);
} }
.scroll-progress-bar { .scroll-progress-bar {
height: 100%; height: 3px;
background: linear-gradient(90deg, #1a4d2e, #c97a3e, #6b8f71); background: linear-gradient(to right, #c97a3e, #6b8f71);
transform-origin: left;
transition: transform 0.1s ease-out;
}
/* Text Gradient */
.text-gradient-sage {
background: linear-gradient(135deg, #1a4d2e 0%, #6b8f71 50%, #1a4d2e 100%);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: gradient-shift 6s ease-in-out infinite;
}
@keyframes gradient-shift {
0%, 100% { background-position: 0% center; }
50% { background-position: 100% center; }
}
/* Float animations */
@keyframes float-slow {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(20px, -30px) scale(1.05); }
66% { transform: translate(-10px, 10px) scale(0.95); }
}
@keyframes float-slow-delayed {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(-25px, 20px) scale(1.03); }
66% { transform: translate(15px, -15px) scale(0.97); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15px); }
}
@keyframes float-delayed {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
/* Ping animation */
@keyframes ping {
75%, 100% {
transform: scale(2);
opacity: 0;
}
}
.animate-ping {
animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}
/* Pulse glow */
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 0 0 rgba(201, 122, 62, 0.4); }
50% { box-shadow: 0 0 20px 5px rgba(201, 122, 62, 0.3); }
}
.animate-pulse-glow {
animation: pulse-glow 3s ease-in-out infinite;
}
/* Route draw animation */
@keyframes draw-route {
to { stroke-dashoffset: 0; }
}
/* Scroll indicator */
@keyframes scroll-indicator {
0%, 100% {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
50% {
transform: translateX(-50%) translateY(12px);
opacity: 0.5;
}
}
/* Story section transitions */
.story-step {
position: absolute;
width: 100%;
opacity: 0;
transition: opacity 0.8s ease-out;
}
.story-step.active {
opacity: 1;
}
/* Smooth scroll behavior */
html {
scroll-behavior: smooth;
}
/* Responsive typography scale */
@media (max-width: 768px) {
.text-6xl { font-size: 3rem !important; }
.text-7xl { font-size: 3.5rem !important; }
.text-8xl { font-size: 4rem !important; }
.text-9xl { font-size: 4.5rem !important; }
}
/* Performance optimizations */
.parallax-float {
will-change: transform;
}
.reveal-text {
will-change: opacity, transform;
}
/* Sticky section styles */
.sticky-wrapper {
position: relative;
}
.sticky-content {
position: sticky;
top: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
} }
`}</style> `}</style>
</> </>
+17 -6
View File
@@ -69,6 +69,7 @@ interface ScrollRevealProps {
className?: string; className?: string;
stagger?: boolean; stagger?: boolean;
direction?: "up" | "down" | "left" | "right" | "scale"; direction?: "up" | "down" | "left" | "right" | "scale";
from?: "up" | "down" | "left" | "right" | "scale";
delay?: number; delay?: number;
} }
@@ -77,9 +78,11 @@ export function ScrollReveal({
className = "", className = "",
stagger = false, stagger = false,
direction = "up", direction = "up",
from,
delay = 0, delay = 0,
}: ScrollRevealProps) { }: ScrollRevealProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const effectiveDirection = from ?? direction;
useEffect(() => { useEffect(() => {
if (typeof window === "undefined" || !containerRef.current) return; if (typeof window === "undefined" || !containerRef.current) return;
@@ -111,7 +114,7 @@ export function ScrollReveal({
// Staggered reveal // Staggered reveal
const animProps: gsap.TweenVars = { opacity: 0 }; const animProps: gsap.TweenVars = { opacity: 0 };
switch (direction) { switch (effectiveDirection) {
case "up": case "up":
animProps.y = 60; animProps.y = 60;
break; break;
@@ -150,7 +153,7 @@ export function ScrollReveal({
}, containerRef); }, containerRef);
return () => ctx.revert(); return () => ctx.revert();
}, [direction, stagger, delay]); }, [effectiveDirection, stagger, delay]);
return ( return (
<div ref={containerRef} className={className}> <div ref={containerRef} className={className}>
@@ -166,12 +169,14 @@ interface ParallaxLayerProps {
children: React.ReactNode; children: React.ReactNode;
speed?: number; // 0-1, higher = more movement speed?: number; // 0-1, higher = more movement
className?: string; className?: string;
style?: React.CSSProperties;
} }
export function ParallaxLayer({ export function ParallaxLayer({
children, children,
speed = 0.5, speed = 0.5,
className = "", className = "",
style,
}: ParallaxLayerProps) { }: ParallaxLayerProps) {
const layerRef = useRef<HTMLDivElement>(null); const layerRef = useRef<HTMLDivElement>(null);
const [isClient, setIsClient] = useState(false); const [isClient, setIsClient] = useState(false);
@@ -202,11 +207,11 @@ export function ParallaxLayer({
}, [isClient, speed]); }, [isClient, speed]);
if (!isClient) { if (!isClient) {
return <div className={className}>{children}</div>; return <div className={className} style={style}>{children}</div>;
} }
return ( return (
<div ref={layerRef} className={className}> <div ref={layerRef} className={className} style={style}>
{children} {children}
</div> </div>
); );
@@ -271,17 +276,21 @@ export function ProgressIndicator({
interface FadeOnScrollProps { interface FadeOnScrollProps {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
from?: "top" | "bottom" | "left" | "right"; from?: "top" | "bottom" | "left" | "right" | "up" | "down";
to?: "top" | "bottom" | "left" | "right";
distance?: number; distance?: number;
duration?: number; duration?: number;
delay?: number;
} }
export function FadeOnScroll({ export function FadeOnScroll({
children, children,
className = "", className = "",
from = "bottom", from = "bottom",
to,
distance = 100, distance = 100,
duration = 1, duration = 1,
delay = 0,
}: FadeOnScrollProps) { }: FadeOnScrollProps) {
const elementRef = useRef<HTMLDivElement>(null); const elementRef = useRef<HTMLDivElement>(null);
@@ -293,6 +302,7 @@ export function FadeOnScroll({
let fromY = 0; let fromY = 0;
let fromX = 0; let fromX = 0;
// Use 'from' direction for the starting position
switch (from) { switch (from) {
case "top": case "top":
fromY = distance; fromY = distance;
@@ -320,6 +330,7 @@ export function FadeOnScroll({
y: 0, y: 0,
duration, duration,
ease: "power3.out", ease: "power3.out",
delay,
scrollTrigger: { scrollTrigger: {
trigger: elementRef.current, trigger: elementRef.current,
start: "top 85%", start: "top 85%",
@@ -330,7 +341,7 @@ export function FadeOnScroll({
}, elementRef); }, elementRef);
return () => ctx.revert(); return () => ctx.revert();
}, [from, distance, duration]); }, [from, distance, duration, delay]);
return ( return (
<div ref={elementRef} className={className}> <div ref={elementRef} className={className}>