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:
@@ -2,8 +2,6 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import HeroSection from "@/components/landing/HeroSection";
|
||||
import FeaturesAndStats from "@/components/landing/FeaturesAndStats";
|
||||
import TestimonialsAndCTA from "@/components/landing/TestimonialsAndCTA";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
@@ -20,21 +18,6 @@ export default function LandingPageClient() {
|
||||
if (typeof window === "undefined" || !mainRef.current) return;
|
||||
|
||||
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
|
||||
const revealElements = gsap.utils.toArray<Element>(".scroll-reveal");
|
||||
revealElements.forEach((el) => {
|
||||
@@ -62,8 +45,6 @@ export default function LandingPageClient() {
|
||||
return (
|
||||
<div ref={mainRef}>
|
||||
<HeroSection />
|
||||
<FeaturesAndStats />
|
||||
<TestimonialsAndCTA />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+105
-31
@@ -656,22 +656,50 @@ export default function TuxedoPage() {
|
||||
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 />
|
||||
|
||||
<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>
|
||||
<div className="mb-14 flex items-end justify-between">
|
||||
<div>
|
||||
<p className="reveal-slide text-[11px] font-semibold uppercase tracking-widest text-emerald-600 mb-4">Delivery Stops</p>
|
||||
<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]">
|
||||
<FadeOnScroll from="left">
|
||||
<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
|
||||
</h2>
|
||||
<div className="reveal-scale 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">
|
||||
</FadeOnScroll>
|
||||
<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.
|
||||
</p>
|
||||
</FadeOnScroll>
|
||||
</div>
|
||||
{showSchedulePdf && (
|
||||
<FadeOnScroll from="right" delay={0.3}>
|
||||
<a
|
||||
href="/api/tuxedo/schedule-pdf"
|
||||
download
|
||||
@@ -682,8 +710,10 @@ export default function TuxedoPage() {
|
||||
</svg>
|
||||
Download Schedule
|
||||
</a>
|
||||
</FadeOnScroll>
|
||||
)}
|
||||
</div>
|
||||
<FadeOnScroll from="up" delay={0.3}>
|
||||
{stops.length === 0 ? (
|
||||
<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>
|
||||
@@ -691,7 +721,9 @@ export default function TuxedoPage() {
|
||||
) : (
|
||||
<PaginatedStops stops={stops} brandSlug="tuxedo" brandAccent="green" />
|
||||
)}
|
||||
</FadeOnScroll>
|
||||
</LayoutContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section divider */}
|
||||
@@ -707,25 +739,41 @@ export default function TuxedoPage() {
|
||||
</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>
|
||||
<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>
|
||||
<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's<br className="hidden md:block" /> Harvest
|
||||
</h2>
|
||||
<div className="reveal-scale 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">
|
||||
<div className="mt-6 h-px w-12 bg-emerald-600" />
|
||||
<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.
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
</LayoutContainer>
|
||||
</div>
|
||||
{featuredProducts.length === 0 ? (
|
||||
<div className="rounded-3xl bg-stone-50 p-14 text-center ring-1 ring-stone-200/60">
|
||||
<p className="text-stone-500">No products available at this time.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
{featuredProducts.map((product) => (
|
||||
|
||||
{/* Cinematic showcase replaces static grid */}
|
||||
<CinematicShowcase
|
||||
products={products.slice(0, 3).map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
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
|
||||
key={product.id}
|
||||
id={product.id}
|
||||
@@ -744,42 +792,65 @@ export default function TuxedoPage() {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</LayoutContainer>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
</div>
|
||||
)}
|
||||
</LayoutContainer>
|
||||
</section>
|
||||
|
||||
<section id="story" className="py-32 bg-stone-950 relative overflow-hidden">
|
||||
{/* Parallax decorative elements */}
|
||||
<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%)",
|
||||
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%)",
|
||||
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>
|
||||
|
||||
<LayoutContainer>
|
||||
<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>
|
||||
<h2 className="reveal-text text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-white leading-[1.05] mb-8">
|
||||
<FadeOnScroll from="up" className="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
|
||||
</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" />
|
||||
<p className="reveal-text text-stone-400 leading-relaxed text-lg md:text-xl max-w-2xl mx-auto">
|
||||
</FadeOnScroll>
|
||||
|
||||
<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's high-altitude mountain climate and grown by the same family for over 40 years.
|
||||
</p>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* 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: "3", suffix: "", label: "Generations" },
|
||||
@@ -796,8 +867,10 @@ export default function TuxedoPage() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FadeOnScroll>
|
||||
|
||||
<div className="reveal-scale mt-16">
|
||||
<FadeOnScroll from="up" delay={0.5}>
|
||||
<div className="mt-16">
|
||||
<Link
|
||||
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"
|
||||
@@ -820,6 +893,7 @@ export default function TuxedoPage() {
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
</FadeOnScroll>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</section>
|
||||
|
||||
@@ -11,122 +11,90 @@ if (typeof window !== "undefined") {
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// TYPES
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
interface ScrollProgress {
|
||||
progress: number;
|
||||
direction: number;
|
||||
velocity: number;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CINEMATIC HERO SECTION - SCROLL-DRIVEN ANIMATIONS
|
||||
// CINEMATIC HERO SECTION - APPLE-STYLE SCROLL-DRIVEN ANIMATIONS
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
export default function HeroSection() {
|
||||
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 [scrollProgress, setScrollProgress] = useState<ScrollProgress>({
|
||||
progress: 0,
|
||||
direction: 1,
|
||||
velocity: 0,
|
||||
});
|
||||
const [scrollProgress, setScrollProgress] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
// Mark as mounted on client-side
|
||||
const timer = setTimeout(() => {
|
||||
setMounted(true);
|
||||
}, 100);
|
||||
const timer = setTimeout(() => setMounted(true), 100);
|
||||
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 ────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
if (!mounted || !containerRef.current) return;
|
||||
if (!mounted || !heroRef.current || !contentRef.current) return;
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
// Hero section timeline
|
||||
const heroTl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
// Scroll progress tracking
|
||||
ScrollTrigger.create({
|
||||
trigger: heroRef.current,
|
||||
start: "top top",
|
||||
end: "bottom top",
|
||||
scrub: 1.5,
|
||||
pin: true,
|
||||
anticipatePin: 1,
|
||||
scrub: true,
|
||||
onUpdate: (self) => {
|
||||
setScrollProgress(self.progress);
|
||||
},
|
||||
});
|
||||
|
||||
// Hero content fade out on scroll
|
||||
heroTl
|
||||
.to(".hero-title", {
|
||||
// Hero content pin - fade out on scroll
|
||||
gsap.to(contentRef.current, {
|
||||
y: -120,
|
||||
opacity: 0,
|
||||
scale: 0.95,
|
||||
ease: "power2.inOut",
|
||||
})
|
||||
.to(
|
||||
".hero-subtitle",
|
||||
{
|
||||
y: -80,
|
||||
opacity: 0,
|
||||
ease: "power2.inOut",
|
||||
scrollTrigger: {
|
||||
trigger: heroRef.current,
|
||||
start: "top top",
|
||||
end: "40% top",
|
||||
scrub: 1.2,
|
||||
},
|
||||
"<"
|
||||
)
|
||||
.to(
|
||||
".hero-cta",
|
||||
{
|
||||
y: -60,
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
// Hero visual scale + parallax
|
||||
gsap.to(".hero-visual", {
|
||||
scale: 1.25,
|
||||
y: 80,
|
||||
ease: "power2.inOut",
|
||||
scrollTrigger: {
|
||||
trigger: heroRef.current,
|
||||
start: "top top",
|
||||
end: "50% top",
|
||||
scrub: 1.5,
|
||||
},
|
||||
"<+0.1"
|
||||
)
|
||||
.to(
|
||||
".hero-visual",
|
||||
});
|
||||
|
||||
// Staggered entrance for content elements
|
||||
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,
|
||||
y: 100,
|
||||
ease: "power2.inOut",
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
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
|
||||
const floatElements = gsap.utils.toArray<Element>(".parallax-float");
|
||||
floatElements.forEach((el, i) => {
|
||||
gsap.to(el, {
|
||||
y: -150 - i * 30,
|
||||
y: -100 - i * 20,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start: "top bottom",
|
||||
@@ -136,9 +104,20 @@ export default function HeroSection() {
|
||||
});
|
||||
});
|
||||
|
||||
// Staggered text reveal on scroll
|
||||
const revealTextElements = gsap.utils.toArray<Element>(".reveal-text");
|
||||
revealTextElements.forEach((el) => {
|
||||
// Scroll indicator fade on scroll
|
||||
gsap.to(".scroll-indicator", {
|
||||
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(
|
||||
el,
|
||||
{ opacity: 0, y: 60, skewY: 3 },
|
||||
@@ -159,8 +138,7 @@ export default function HeroSection() {
|
||||
});
|
||||
|
||||
// Scale reveal for cards
|
||||
const revealScaleElements = gsap.utils.toArray<Element>(".reveal-scale");
|
||||
revealScaleElements.forEach((el, i) => {
|
||||
gsap.utils.toArray<Element>(".reveal-scale").forEach((el, i) => {
|
||||
gsap.fromTo(
|
||||
el,
|
||||
{ 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
|
||||
const counterElements = gsap.utils.toArray<Element>(".counter-animate");
|
||||
counterElements.forEach((el) => {
|
||||
gsap.utils.toArray<Element>(".counter-animate").forEach((el) => {
|
||||
const target = parseInt(el.getAttribute("data-target") || "0", 10);
|
||||
const obj = { val: 0 };
|
||||
gsap.to(obj, {
|
||||
@@ -235,15 +192,19 @@ export default function HeroSection() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* ─── SCROLL PROGRESS INDICATOR ────────────────────────────────────────── */}
|
||||
<div className="scroll-progress-container">
|
||||
{/* ─── SCROLL PROGRESS BAR ──────────────────────────────────────────── */}
|
||||
<div className="fixed top-0 left-0 right-0 h-1 z-[1000] bg-white/10">
|
||||
<div
|
||||
className="scroll-progress-bar"
|
||||
style={{ transform: `scaleX(${scrollProgress.progress})` }}
|
||||
className="h-full bg-gradient-to-r from-amber-500 via-amber-400 to-sage-500"
|
||||
style={{
|
||||
transform: `scaleX(${scrollProgress})`,
|
||||
transformOrigin: "left",
|
||||
transition: "transform 0.1s ease-out",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ─── HERO SECTION - PINNED SCROLL SEQUENCE ───────────────────────────── */}
|
||||
{/* ─── HERO SECTION - SCROLL-DRIVEN PIN ───────────────────────────── */}
|
||||
<section
|
||||
ref={heroRef}
|
||||
className="hero-section relative min-h-screen flex items-center overflow-hidden"
|
||||
@@ -279,30 +240,27 @@ export default function HeroSection() {
|
||||
</div>
|
||||
|
||||
{/* ─── 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]">
|
||||
|
||||
{/* Left Column - Animated Content */}
|
||||
<div className="space-y-10 hero-content">
|
||||
<div className="space-y-10">
|
||||
{/* Animated Badge */}
|
||||
<div
|
||||
className="hero-badge inline-flex items-center gap-3 px-6 py-3 rounded-full border"
|
||||
style={{
|
||||
<div className="hero-reveal hero-badge inline-flex items-center gap-3 px-6 py-3 rounded-full border" style={{
|
||||
borderColor: "#c97a3e",
|
||||
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" }}>
|
||||
Farm-Fresh Delivery Platform
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Main Headline - Apple-style reveal */}
|
||||
<div className="hero-title space-y-2">
|
||||
<div className="hero-reveal hero-title space-y-2">
|
||||
<h1
|
||||
className="text-6xl sm:text-7xl lg:text-8xl xl:text-9xl font-bold leading-[0.9] tracking-tighter"
|
||||
style={{
|
||||
@@ -317,7 +275,7 @@ export default function HeroSection() {
|
||||
|
||||
{/* Subtext */}
|
||||
<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={{
|
||||
fontFamily: "'DM Sans', sans-serif",
|
||||
color: "#4a6d56",
|
||||
@@ -329,7 +287,7 @@ export default function HeroSection() {
|
||||
</p>
|
||||
|
||||
{/* CTAs */}
|
||||
<div className="hero-cta flex flex-wrap gap-5">
|
||||
<div className="hero-reveal hero-cta flex flex-wrap gap-5">
|
||||
<Link
|
||||
href="/login"
|
||||
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>
|
||||
|
||||
{/* 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: "98%", label: "On-Time" },
|
||||
@@ -411,7 +369,6 @@ export default function HeroSection() {
|
||||
className="w-full h-full"
|
||||
style={{ maxHeight: "700px", filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.08))" }}
|
||||
>
|
||||
{/* Background with grain */}
|
||||
<defs>
|
||||
<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" />
|
||||
@@ -463,7 +420,7 @@ export default function HeroSection() {
|
||||
/>
|
||||
|
||||
{/* 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="28" fill="rgba(26, 77, 46, 0.15)" />
|
||||
<circle cx="80" cy="400" r="20" fill="#1a4d2e" />
|
||||
@@ -471,7 +428,7 @@ export default function HeroSection() {
|
||||
</g>
|
||||
|
||||
{/* 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="32" fill="rgba(201, 122, 62, 0.15)" />
|
||||
<circle cx="260" cy="180" r="22" fill="#c97a3e" />
|
||||
@@ -480,11 +437,11 @@ export default function HeroSection() {
|
||||
|
||||
{/* Stop Points */}
|
||||
{[
|
||||
{ x: 180, y: 280, label: "Stop 1", delay: "1.2s" },
|
||||
{ x: 360, y: 120, label: "Stop 2", delay: "1.4s" },
|
||||
{ x: 420, y: 90, label: "Market", delay: "1.6s" },
|
||||
{ x: 180, y: 280 },
|
||||
{ x: 360, y: 120 },
|
||||
{ x: 420, y: 90 },
|
||||
].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="10" fill="#6b8f71" />
|
||||
</g>
|
||||
@@ -499,7 +456,6 @@ export default function HeroSection() {
|
||||
r="10"
|
||||
fill="rgba(107, 143, 113, 0.3)"
|
||||
className="parallax-float"
|
||||
style={{ animationDelay: `${i * 0.3}s` }}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -513,10 +469,7 @@ export default function HeroSection() {
|
||||
</svg>
|
||||
|
||||
{/* Floating Cards */}
|
||||
<div
|
||||
className="absolute top-12 -left-8 parallax-float"
|
||||
style={{ animation: "float 8s ease-in-out infinite" }}
|
||||
>
|
||||
<div className="absolute top-12 -left-8 parallax-float" style={{ animation: "float 8s ease-in-out infinite" }}>
|
||||
<div
|
||||
className="rounded-2xl p-5 shadow-2xl border"
|
||||
style={{
|
||||
@@ -530,10 +483,7 @@ export default function HeroSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-24 -right-4 parallax-float"
|
||||
style={{ animation: "float-delayed 10s ease-in-out infinite" }}
|
||||
>
|
||||
<div className="absolute bottom-24 -right-4 parallax-float" style={{ animation: "float-delayed 10s ease-in-out infinite" }}>
|
||||
<div
|
||||
className="rounded-2xl p-5 shadow-2xl border"
|
||||
style={{
|
||||
@@ -547,10 +497,7 @@ export default function HeroSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute top-1/2 -right-12 parallax-float"
|
||||
style={{ animation: "float 9s ease-in-out infinite 1s" }}
|
||||
>
|
||||
<div className="absolute top-1/2 -right-12 parallax-float" style={{ animation: "float 9s ease-in-out infinite 1s" }}>
|
||||
<div
|
||||
className="rounded-2xl p-4 shadow-xl border flex items-center gap-3"
|
||||
style={{
|
||||
@@ -569,13 +516,10 @@ export default function HeroSection() {
|
||||
|
||||
{/* ─── SCROLL INDICATOR ────────────────────────────────────────────────── */}
|
||||
<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}
|
||||
>
|
||||
<span
|
||||
className="text-xs font-semibold tracking-[0.2em] uppercase"
|
||||
style={{ color: "#6b8f71" }}
|
||||
>
|
||||
<span className="text-xs font-semibold tracking-[0.2em] uppercase" style={{ color: "#6b8f71" }}>
|
||||
Scroll to explore
|
||||
</span>
|
||||
<div className="relative w-8 h-14 rounded-full border-2" style={{ borderColor: "#6b8f71" }}>
|
||||
@@ -590,48 +534,45 @@ export default function HeroSection() {
|
||||
</div>
|
||||
|
||||
{/* 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 L30 55" stroke="#1a4d2e" strokeWidth="2" />
|
||||
</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 L20 37" stroke="#1a4d2e" strokeWidth="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── STORY SECTION - STICKY SCROLL NARRATIVE ──────────────────────────── */}
|
||||
{/* ─── STORY SECTION - SCROLL-DRIVEN REVEAL ──────────────────────────── */}
|
||||
<section id="story" className="relative" style={{ background: "#1a1a1a" }}>
|
||||
<div className="sticky-wrapper" style={{ minHeight: "400vh" }}>
|
||||
<div className="relative" style={{ minHeight: "300vh" }}>
|
||||
{/* Sticky Container */}
|
||||
<div
|
||||
className="sticky-content relative flex items-center justify-center overflow-hidden"
|
||||
style={{
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
height: "100vh",
|
||||
}}
|
||||
className="sticky top-0 h-screen flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
{/* Background gradient that shifts */}
|
||||
<div className="absolute inset-0 transition-opacity duration-700"
|
||||
style={{
|
||||
{/* Background gradient */}
|
||||
<div className="absolute inset-0" style={{
|
||||
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">
|
||||
{/* Story Step 1 */}
|
||||
<div className="story-step step-1">
|
||||
<div className="container mx-auto px-8 text-center relative z-10 max-w-4xl">
|
||||
<reveal-scale className="mb-8">
|
||||
<span
|
||||
className="reveal-text inline-block text-xs font-bold tracking-[0.3em] uppercase mb-8"
|
||||
style={{
|
||||
color: "#c97a3e",
|
||||
fontFamily: "'DM Sans', sans-serif",
|
||||
}}
|
||||
className="inline-block text-xs font-bold tracking-[0.3em] uppercase"
|
||||
style={{ color: "#c97a3e", fontFamily: "'DM Sans', sans-serif" }}
|
||||
>
|
||||
The Challenge
|
||||
</span>
|
||||
</FadeOnScroll>
|
||||
|
||||
<reveal-scale delay={0.1}>
|
||||
<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={{
|
||||
fontFamily: "'Playfair Display', serif",
|
||||
color: "#faf8f5",
|
||||
@@ -641,41 +582,9 @@ export default function HeroSection() {
|
||||
<br />
|
||||
<span style={{ color: "#c97a3e" }}>sit in transit for days</span>
|
||||
</h2>
|
||||
<p
|
||||
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>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* Story Step 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>
|
||||
<reveal-scale delay={0.2}>
|
||||
<p
|
||||
className="text-xl max-w-2xl mx-auto"
|
||||
style={{
|
||||
@@ -684,45 +593,9 @@ export default function HeroSection() {
|
||||
lineHeight: 1.7,
|
||||
}}
|
||||
>
|
||||
Our platform analyzes patterns, traffic, and delivery windows
|
||||
to get your produce from field to market in hours, not days.
|
||||
Traditional distribution means your harvest loses freshness, quality, and value before reaching customers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
</FadeOnScroll>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -733,20 +606,27 @@ export default function HeroSection() {
|
||||
className="relative py-32 overflow-hidden"
|
||||
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 */}
|
||||
<div className="text-center mb-20">
|
||||
<div className="reveal-scale text-center mb-20">
|
||||
<span
|
||||
className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase mb-6"
|
||||
style={{
|
||||
color: "#6b8f71",
|
||||
fontFamily: "'DM Sans', sans-serif",
|
||||
}}
|
||||
className="inline-block text-xs font-bold tracking-[0.2em] uppercase mb-6"
|
||||
style={{ color: "#6b8f71", fontFamily: "'DM Sans', sans-serif" }}
|
||||
>
|
||||
Platform Features
|
||||
</span>
|
||||
<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={{
|
||||
fontFamily: "'Playfair Display', serif",
|
||||
color: "#1a4d2e",
|
||||
@@ -901,12 +781,10 @@ export default function HeroSection() {
|
||||
{/* ─── STATS SECTION - SCROLL-TRIGGERED COUNTERS ──────────────────────── */}
|
||||
<section
|
||||
className="relative py-32 overflow-hidden"
|
||||
style={{
|
||||
background: "#1a4d2e",
|
||||
}}
|
||||
style={{ background: "#1a4d2e" }}
|
||||
>
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute inset-0 opacity-[0.03]" style={{
|
||||
{/* Parallax decorative elements */}
|
||||
<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)",
|
||||
backgroundSize: "48px 48px",
|
||||
}} />
|
||||
@@ -915,17 +793,14 @@ export default function HeroSection() {
|
||||
}} />
|
||||
|
||||
<div className="container mx-auto px-8 relative z-10">
|
||||
<div className="text-center mb-16">
|
||||
<reveal-scale className="text-center mb-16">
|
||||
<span
|
||||
className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase"
|
||||
style={{
|
||||
color: "#c97a3e",
|
||||
fontFamily: "'DM Sans', sans-serif",
|
||||
}}
|
||||
className="inline-block text-xs font-bold tracking-[0.2em] uppercase"
|
||||
style={{ color: "#c97a3e", fontFamily: "'DM Sans', sans-serif" }}
|
||||
>
|
||||
Our Impact
|
||||
</span>
|
||||
</div>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<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: 2000000, prefix: "$", suffix: "+", label: "Weekly Sales" },
|
||||
].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
|
||||
className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-4"
|
||||
style={{
|
||||
@@ -945,20 +820,12 @@ export default function HeroSection() {
|
||||
}}
|
||||
>
|
||||
{stat.prefix && <span style={{ color: "#c97a3e" }}>{stat.prefix}</span>}
|
||||
<span
|
||||
className="counter-animate"
|
||||
data-target={stat.value}
|
||||
>
|
||||
0
|
||||
</span>
|
||||
<span className="counter-animate" data-target={stat.value}>0</span>
|
||||
<span style={{ color: "#c97a3e" }}>{stat.suffix}</span>
|
||||
</div>
|
||||
<p
|
||||
className="text-base font-medium"
|
||||
style={{
|
||||
color: "#6b8f71",
|
||||
fontFamily: "'DM Sans', sans-serif",
|
||||
}}
|
||||
style={{ color: "#6b8f71", fontFamily: "'DM Sans', sans-serif" }}
|
||||
>
|
||||
{stat.label}
|
||||
</p>
|
||||
@@ -971,23 +838,26 @@ export default function HeroSection() {
|
||||
{/* ─── CTA SECTION - FINAL REVEAL ─────────────────────────────────────── */}
|
||||
<section
|
||||
className="relative py-40 overflow-hidden"
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #faf8f5 0%, #f5f2ed 100%)",
|
||||
}}
|
||||
style={{ background: "linear-gradient(180deg, #faf8f5 0%, #f5f2ed 100%)" }}
|
||||
>
|
||||
{/* Decorative orbs */}
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-10" style={{
|
||||
{/* Parallax decorative orbs */}
|
||||
<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%)",
|
||||
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%)",
|
||||
filter: "blur(60px)",
|
||||
animationDelay: "0.4s",
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-8 relative z-10">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
{/* Label */}
|
||||
<reveal-scale>
|
||||
<span
|
||||
className="reveal-text inline-block text-xs font-bold tracking-[0.2em] uppercase px-5 py-2 rounded-full mb-8"
|
||||
style={{
|
||||
@@ -998,238 +868,114 @@ export default function HeroSection() {
|
||||
>
|
||||
Get Started Today
|
||||
</span>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* Headline */}
|
||||
<reveal-scale delay={0.1}>
|
||||
<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={{
|
||||
fontFamily: "'Playfair Display', serif",
|
||||
color: "#1a1a1a",
|
||||
color: "#1a4d2e",
|
||||
}}
|
||||
>
|
||||
Ready to Grow
|
||||
<br />
|
||||
<span style={{ color: "#1a4d2e" }}>Your Routes?</span>
|
||||
Ready to Transform<br />
|
||||
Your Distribution?
|
||||
</h2>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* Subtitle */}
|
||||
{/* Subtext */}
|
||||
<reveal-scale delay={0.2}>
|
||||
<p
|
||||
className="reveal-text text-xl mb-12"
|
||||
className="text-xl mb-12 max-w-xl mx-auto"
|
||||
style={{
|
||||
color: "#555",
|
||||
fontFamily: "'DM Sans', sans-serif",
|
||||
lineHeight: 1.7,
|
||||
}}
|
||||
>
|
||||
Join hundreds of produce brands who trust Route Commerce
|
||||
to power their farm-fresh operations.
|
||||
Join hundreds of farms already using Route Commerce to deliver fresher produce faster.
|
||||
</p>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="flex flex-wrap justify-center gap-6">
|
||||
{/* CTAs */}
|
||||
<reveal-scale delay={0.3}>
|
||||
<div className="flex flex-wrap items-center justify-center gap-5">
|
||||
<Link
|
||||
href="/admin"
|
||||
className="group inline-flex items-center gap-3 px-10 py-5 rounded-2xl text-lg font-bold transition-all duration-300"
|
||||
href="/login"
|
||||
className="group relative inline-flex items-center gap-3 px-10 py-6 rounded-2xl font-semibold text-xl overflow-hidden"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #1a4d2e 0%, #166534 100%)",
|
||||
color: "white",
|
||||
boxShadow: "0 8px 32px rgba(26, 77, 46, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.15)",
|
||||
}}
|
||||
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)";
|
||||
boxShadow: "0 8px 32px rgba(26, 77, 46, 0.35)",
|
||||
}}
|
||||
>
|
||||
<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" />
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/brands"
|
||||
className="group inline-flex items-center gap-3 px-10 py-5 rounded-2xl text-lg font-bold border-2 transition-all duration-300"
|
||||
href="/contact"
|
||||
className="group inline-flex items-center gap-3 px-10 py-6 rounded-2xl font-semibold text-xl border-2"
|
||||
style={{
|
||||
color: "#1a4d2e",
|
||||
borderColor: "#1a4d2e",
|
||||
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>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
</FadeOnScroll>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── GLOBAL STYLES ────────────────────────────────────────────────────── */}
|
||||
<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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
z-index: 1000;
|
||||
background: rgba(26, 77, 46, 0.1);
|
||||
}
|
||||
|
||||
.scroll-progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #1a4d2e, #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;
|
||||
height: 3px;
|
||||
background: linear-gradient(to right, #c97a3e, #6b8f71);
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
|
||||
@@ -69,6 +69,7 @@ interface ScrollRevealProps {
|
||||
className?: string;
|
||||
stagger?: boolean;
|
||||
direction?: "up" | "down" | "left" | "right" | "scale";
|
||||
from?: "up" | "down" | "left" | "right" | "scale";
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
@@ -77,9 +78,11 @@ export function ScrollReveal({
|
||||
className = "",
|
||||
stagger = false,
|
||||
direction = "up",
|
||||
from,
|
||||
delay = 0,
|
||||
}: ScrollRevealProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const effectiveDirection = from ?? direction;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !containerRef.current) return;
|
||||
@@ -111,7 +114,7 @@ export function ScrollReveal({
|
||||
// Staggered reveal
|
||||
const animProps: gsap.TweenVars = { opacity: 0 };
|
||||
|
||||
switch (direction) {
|
||||
switch (effectiveDirection) {
|
||||
case "up":
|
||||
animProps.y = 60;
|
||||
break;
|
||||
@@ -150,7 +153,7 @@ export function ScrollReveal({
|
||||
}, containerRef);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, [direction, stagger, delay]);
|
||||
}, [effectiveDirection, stagger, delay]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={className}>
|
||||
@@ -166,12 +169,14 @@ interface ParallaxLayerProps {
|
||||
children: React.ReactNode;
|
||||
speed?: number; // 0-1, higher = more movement
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export function ParallaxLayer({
|
||||
children,
|
||||
speed = 0.5,
|
||||
className = "",
|
||||
style,
|
||||
}: ParallaxLayerProps) {
|
||||
const layerRef = useRef<HTMLDivElement>(null);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
@@ -202,11 +207,11 @@ export function ParallaxLayer({
|
||||
}, [isClient, speed]);
|
||||
|
||||
if (!isClient) {
|
||||
return <div className={className}>{children}</div>;
|
||||
return <div className={className} style={style}>{children}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={layerRef} className={className}>
|
||||
<div ref={layerRef} className={className} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -271,17 +276,21 @@ export function ProgressIndicator({
|
||||
interface FadeOnScrollProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
from?: "top" | "bottom" | "left" | "right";
|
||||
from?: "top" | "bottom" | "left" | "right" | "up" | "down";
|
||||
to?: "top" | "bottom" | "left" | "right";
|
||||
distance?: number;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export function FadeOnScroll({
|
||||
children,
|
||||
className = "",
|
||||
from = "bottom",
|
||||
to,
|
||||
distance = 100,
|
||||
duration = 1,
|
||||
delay = 0,
|
||||
}: FadeOnScrollProps) {
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -293,6 +302,7 @@ export function FadeOnScroll({
|
||||
let fromY = 0;
|
||||
let fromX = 0;
|
||||
|
||||
// Use 'from' direction for the starting position
|
||||
switch (from) {
|
||||
case "top":
|
||||
fromY = distance;
|
||||
@@ -320,6 +330,7 @@ export function FadeOnScroll({
|
||||
y: 0,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
scrollTrigger: {
|
||||
trigger: elementRef.current,
|
||||
start: "top 85%",
|
||||
@@ -330,7 +341,7 @@ export function FadeOnScroll({
|
||||
}, elementRef);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, [from, distance, duration]);
|
||||
}, [from, distance, duration, delay]);
|
||||
|
||||
return (
|
||||
<div ref={elementRef} className={className}>
|
||||
|
||||
Reference in New Issue
Block a user