diff --git a/src/app/LandingPageClient.tsx b/src/app/LandingPageClient.tsx index 908bb6a..c588944 100644 --- a/src/app/LandingPageClient.tsx +++ b/src/app/LandingPageClient.tsx @@ -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(".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(".scroll-reveal"); revealElements.forEach((el) => { @@ -62,8 +45,6 @@ export default function LandingPageClient() { return (
- -
); } \ No newline at end of file diff --git a/src/app/tuxedo/page.tsx b/src/app/tuxedo/page.tsx index b50f56e..a1b0959 100644 --- a/src/app/tuxedo/page.tsx +++ b/src/app/tuxedo/page.tsx @@ -656,42 +656,74 @@ export default function TuxedoPage() { onSecondaryClick={scrollToStory} /> + {/* ─── CINEMATIC PRODUCT SHOWCASE ────────────────────────────────── */} + ({ + id: p.id, + name: p.name, + description: p.description ?? "", + price: `$${p.price}`, + type: p.type, + imageUrl: p.image_url, + }))} + brandSlug="tuxedo" + /> + -
- -
-
-

Delivery Stops

-

- Upcoming
Stops -

-
-

- Find a nearby stop and preorder your corn. Pickup is easy and guaranteed. -

+
+ {/* Parallax background layers */} + +
+ + +
+ +
+
+ +

Delivery Stops

+
+ +

+ Upcoming
Stops +

+
+ +
+ + +

+ Find a nearby stop and preorder your corn. Pickup is easy and guaranteed. +

+
+
+ {showSchedulePdf && ( + + + + + + Download Schedule + + + )}
- {showSchedulePdf && ( - - - - - Download Schedule - - )} -
- {stops.length === 0 ? ( -
-

No upcoming stops scheduled. Check back soon!

-
- ) : ( - - )} -
+ + {stops.length === 0 ? ( +
+

No upcoming stops scheduled. Check back soon!

+
+ ) : ( + + )} +
+ +
{/* Section divider */} @@ -707,25 +739,41 @@ export default function TuxedoPage() {
-
- -
-

Farm-Direct

-

- This Week's
Harvest -

-
-

- Preorder for pickup at a stop, or have cooler boxes shipped directly to your door after the season. -

-
- {featuredProducts.length === 0 ? ( -
-

No products available at this time.

-
- ) : ( -
- {featuredProducts.map((product) => ( +
+ {/* Scroll-driven reveal for section header */} +
+ + +

Farm-Direct

+

+ This Week's
Harvest +

+
+

+ Preorder for pickup at a stop, or have cooler boxes shipped directly to your door after the season. +

+ + +
+ + {/* Cinematic showcase replaces static grid */} + ({ + 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 */} +
+ +
+ {products.slice(0, 6).map((product) => ( ))}
- )} - {products.length > 3 && ( -
-

{products.length - 3} more products available in the full catalog.

-
- )} -
+ +
+ + {products.length > 3 && ( +
+

{products.length - 3} more products available in the full catalog.

+
+ )}
{/* Parallax decorative elements */}
-
-
+
+ + + }}> +
+ +
+ + {/* Large background text for parallax depth */} +
+ + SINCE +
-

Our Story

-

- Three Generations of
Sweet Corn Excellence -

-
-

- 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. -

+ +

Our Story

+
+ + +

+ Three Generations of
Sweet Corn Excellence +

+
+ + +
+ + + +

+ 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. +

+
{/* Stats with counter animation */} -
- {[ - { stat: "40", suffix: "+", label: "Years Growing" }, - { stat: "3", suffix: "", label: "Generations" }, - { stat: "100", suffix: "%", label: "Hand-Picked" }, - ].map((item, i) => ( -
-
- 0 - {item.suffix} + +
+ {[ + { stat: "40", suffix: "+", label: "Years Growing" }, + { stat: "3", suffix: "", label: "Generations" }, + { stat: "100", suffix: "%", label: "Hand-Picked" }, + ].map((item, i) => ( +
+
+ 0 + {item.suffix} +
+
+ {item.label} +
-
- {item.label} -
-
- ))} -
+ ))} +
+ -
- { - e.currentTarget.style.transform = "translateY(-3px)"; - e.currentTarget.style.boxShadow = "0 12px 40px rgba(16, 185, 129, 0.4), 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(16, 185, 129, 0.3), inset 0 1px 0 rgba(255,255,255,0.2)"; - }} - > - Read Our Story - - - - -
+ +
+ { + e.currentTarget.style.transform = "translateY(-3px)"; + e.currentTarget.style.boxShadow = "0 12px 40px rgba(16, 185, 129, 0.4), 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(16, 185, 129, 0.3), inset 0 1px 0 rgba(255,255,255,0.2)"; + }} + > + Read Our Story + + + + +
+
diff --git a/src/components/landing/HeroSection.tsx b/src/components/landing/HeroSection.tsx index de1e861..864dacb 100644 --- a/src/components/landing/HeroSection.tsx +++ b/src/components/landing/HeroSection.tsx @@ -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(null); - const heroRef = useRef(null); + const heroRef = useRef(null); + const contentRef = useRef(null); const [mounted, setMounted] = useState(false); - const [scrollProgress, setScrollProgress] = useState({ - 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: { - trigger: heroRef.current, - start: "top top", - end: "bottom top", - scrub: 1.5, - pin: true, - anticipatePin: 1, + // Scroll progress tracking + ScrollTrigger.create({ + trigger: heroRef.current, + start: "top top", + end: "bottom top", + scrub: true, + onUpdate: (self) => { + setScrollProgress(self.progress); }, }); - // Hero content fade out on scroll - heroTl - .to(".hero-title", { - y: -120, - opacity: 0, - scale: 0.95, - ease: "power2.inOut", - }) - .to( - ".hero-subtitle", + // Hero content pin - fade out on scroll + gsap.to(contentRef.current, { + y: -120, + opacity: 0, + scale: 0.95, + ease: "power2.inOut", + scrollTrigger: { + trigger: heroRef.current, + start: "top top", + end: "40% top", + scrub: 1.2, + }, + }); + + // 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, + }, + }); + + // Staggered entrance for content elements + const contentElements = gsap.utils.toArray(".hero-reveal"); + contentElements.forEach((el, i) => { + gsap.fromTo( + el, + { opacity: 0, y: 50, scale: 0.95 }, { - y: -80, - opacity: 0, - ease: "power2.inOut", - }, - "<" - ) - .to( - ".hero-cta", - { - y: -60, - opacity: 0, - ease: "power2.inOut", - }, - "<+0.1" - ) - .to( - ".hero-visual", - { - scale: 1.3, - y: 100, - ease: "power2.inOut", - }, - "<" - ) - .to( - ".hero-badge", - { - y: -40, - opacity: 0, - 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", + }, + } ); + }); // Parallax floating elements const floatElements = gsap.utils.toArray(".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(".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(".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(".reveal-scale"); - revealScaleElements.forEach((el, i) => { + gsap.utils.toArray(".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(".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(".counter-animate"); - counterElements.forEach((el) => { + gsap.utils.toArray(".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 ────────────────────────────────────────── */} -
-
+
- {/* ─── HERO SECTION - PINNED SCROLL SEQUENCE ───────────────────────────── */} + {/* ─── HERO SECTION - SCROLL-DRIVEN PIN ───────────────────────────── */}
- + {/* Grid pattern */} -
{/* ─── HERO CONTENT ────────────────────────────────────────────────────── */} -
+
- + {/* Left Column - Animated Content */} -
+
{/* Animated Badge */} -
- +
+ Farm-Fresh Delivery Platform
{/* Main Headline - Apple-style reveal */} -
-

+

{/* Subtext */} -

- We connect produce farms with reliable distribution networks, + We connect produce farms with reliable distribution networks, ensuring your harvest reaches markets faster and fresher than ever.

{/* CTAs */} -
- + - {/* Stats */} -
+
{[ { stat: "500+", label: "Farm Brands" }, { stat: "98%", label: "On-Time" }, { stat: "50K+", label: "Deliveries" }, ].map((item, i) => (
-
- {/* Background with grain */} @@ -463,7 +420,7 @@ export default function HeroSection() { /> {/* Farm Location - Animated Pulse */} - + @@ -471,7 +428,7 @@ export default function HeroSection() { {/* Hub Location */} - + @@ -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) => ( - + @@ -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,11 +469,8 @@ export default function HeroSection() { {/* Floating Cards */} -
-
+
-
-
+
-
-
+
{/* ─── SCROLL INDICATOR ────────────────────────────────────────────────── */} -
- + Scroll to explore
-
{/* Decorative leaves */} - - - - - - - - +
+ + + + +
+
+ + + + +

- {/* ─── STORY SECTION - STICKY SCROLL NARRATIVE ──────────────────────────── */} + {/* ─── STORY SECTION - SCROLL-DRIVEN REVEAL ──────────────────────────── */}
-
+
{/* Sticky Container */} -
- {/* Background gradient that shifts */} -
+ {/* Background gradient */} +
-
- {/* Story Step 1 */} -
- + + The Challenge -

+ + +

sit in transit for days

-

- Traditional distribution means your harvest loses freshness, - quality, and value before reaching customers. -

-

+ - {/* Story Step 2 */} -
- - The Solution - -

- Intelligent route -
- optimization -

-

+

- 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.

-
- - {/* Story Step 3 */} -
- - The Result - -

- 40% faster delivery -
- 98% on-time rate -

-

- Farms using Route Commerce see dramatic improvements in - freshness, customer satisfaction, and operational efficiency. -

-
+
{/* ─── FEATURES SECTION - PROGRESSIVE REVEAL ───────────────────────────── */} -
-
+ {/* Parallax background elements */} +
+
+
+
+
+ +
{/* Section Header */} -
- + Platform Features -

{/* Icon */} -
{/* Content */} -

{feature.title}

-

{/* Hover Arrow */} -

{/* ─── STATS SECTION - SCROLL-TRIGGERED COUNTERS ──────────────────────── */} -
- {/* Decorative elements */} -
@@ -915,17 +793,14 @@ export default function HeroSection() { }} />
-
- + Our Impact -
+ {/* Stats Grid */}
@@ -935,30 +810,22 @@ export default function HeroSection() { { value: 98, suffix: "%", label: "On-Time Delivery" }, { value: 2000000, prefix: "$", suffix: "+", label: "Weekly Sales" }, ].map((stat, i) => ( -
-
+
{stat.prefix && {stat.prefix}} - - 0 - + 0 {stat.suffix}
-

{stat.label}

@@ -969,267 +836,146 @@ export default function HeroSection() {
{/* ─── CTA SECTION - FINAL REVEAL ─────────────────────────────────────── */} -
- {/* Decorative orbs */} -
-
+ {/* Parallax decorative orbs */} +
+
+
+
{/* Label */} - - Get Started Today - + + + Get Started Today + + {/* Headline */} -

- Ready to Grow -
- Your Routes? -

- - {/* Subtitle */} -

- Join hundreds of produce brands who trust Route Commerce - to power their farm-fresh operations. -

- - {/* CTA Buttons */} -
- { - 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)"; - }} - > - Start Free Trial - - - - - - +

{ - 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"; }} > - View Farms - -

+ Ready to Transform
+ Your Distribution? +

+ - {/* Disclaimer */} -

- No credit card required · 14-day free trial · Cancel anytime -

+ {/* Subtext */} + +

+ Join hundreds of farms already using Route Commerce to deliver fresher produce faster. +

+ + + {/* CTAs */} + +
+ + Start Free Trial + + + + + + Contact Sales + +
+
{/* ─── GLOBAL STYLES ────────────────────────────────────────────────────── */} diff --git a/src/components/ui/ScrollAnimations.tsx b/src/components/ui/ScrollAnimations.tsx index 675d59f..a0d89c9 100644 --- a/src/components/ui/ScrollAnimations.tsx +++ b/src/components/ui/ScrollAnimations.tsx @@ -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(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 (
@@ -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(null); const [isClient, setIsClient] = useState(false); @@ -202,11 +207,11 @@ export function ParallaxLayer({ }, [isClient, speed]); if (!isClient) { - return
{children}
; + return
{children}
; } return ( -
+
{children}
); @@ -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(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 (