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 { 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
@@ -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's<br className="hidden md:block" /> Harvest
|
This Week'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'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'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>
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
Reference in New Issue
Block a user