animation fixed in section
This commit is contained in:
@@ -2,14 +2,11 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { motion } from "framer-motion";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
||||
import {
|
||||
fadeUp,
|
||||
slideInRight,
|
||||
staggerContainer,
|
||||
viewport,
|
||||
} from "@/lib/animations";
|
||||
import { viewport } from "@/lib/animations";
|
||||
|
||||
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||
|
||||
const PROOF_ITEMS = [
|
||||
{ label: "Verified", text: "Local Stores", icon: "store" },
|
||||
@@ -18,16 +15,86 @@ const PROOF_ITEMS = [
|
||||
{ label: "Trusted", text: "Local Platform", icon: "star" },
|
||||
];
|
||||
|
||||
// Left column: badge → line1 mask-reveal → line2 mask-reveal → desc → primary CTA → secondary CTA
|
||||
const heroStagger = {
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.12, delayChildren: 0.05 } },
|
||||
};
|
||||
|
||||
const badgeVariant = {
|
||||
hidden: { opacity: 0, y: 10, scale: 0.95 },
|
||||
visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.38, ease: EASE } },
|
||||
};
|
||||
|
||||
// Lines stagger internally
|
||||
const lineStagger = {
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.09 } },
|
||||
};
|
||||
|
||||
// True mask reveal: text slides up from below a clipping parent
|
||||
const lineReveal = {
|
||||
hidden: { y: "110%" },
|
||||
visible: { y: 0, transition: { duration: 0.72, ease: EASE } },
|
||||
};
|
||||
|
||||
const descVariant = {
|
||||
hidden: { opacity: 0, y: 18 },
|
||||
visible: { opacity: 1, y: 0, transition: { duration: 0.55, ease: EASE } },
|
||||
};
|
||||
|
||||
const primaryCtaVariant = {
|
||||
hidden: { opacity: 0, y: 14, scale: 0.97 },
|
||||
visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.45, ease: EASE } },
|
||||
};
|
||||
|
||||
const secondaryCtaVariant = {
|
||||
hidden: { opacity: 0, x: -14 },
|
||||
visible: { opacity: 1, x: 0, transition: { duration: 0.4, ease: EASE } },
|
||||
};
|
||||
|
||||
// Right column: each element enters independently
|
||||
const imgFromLeft = {
|
||||
hidden: { opacity: 0, x: -36 },
|
||||
visible: { opacity: 1, x: 0, transition: { duration: 0.65, ease: EASE } },
|
||||
};
|
||||
|
||||
const centerCardReveal = {
|
||||
hidden: { opacity: 0, scale: 0.86, y: 24 },
|
||||
visible: { opacity: 1, scale: 1, y: 0, transition: { duration: 0.75, ease: EASE } },
|
||||
};
|
||||
|
||||
const imgFromRight = {
|
||||
hidden: { opacity: 0, x: 36 },
|
||||
visible: { opacity: 1, x: 0, transition: { duration: 0.65, ease: EASE } },
|
||||
};
|
||||
|
||||
const statStagger = {
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.08 } },
|
||||
};
|
||||
|
||||
const statItem = {
|
||||
hidden: { opacity: 0, y: 14 },
|
||||
visible: { opacity: 1, y: 0, transition: { duration: 0.45, ease: EASE } },
|
||||
};
|
||||
|
||||
export function Hero() {
|
||||
const reducedMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-gradient-to-b from-[#FAF8FC] to-[#FFFFFF] select-none w-full">
|
||||
{/* ─── DECORATIONS (BEHIND CONTENT) ─── */}
|
||||
|
||||
{/* 1. Large pale lavender partial circle (top-left) */}
|
||||
<div className="hidden md:block absolute top-[-100px] left-[-100px] w-[350px] h-[350px] rounded-full border-[1.5px] border-[#6D28D9]/15 bg-[#6D28D9]/5 opacity-[0.55] z-0 pointer-events-none" />
|
||||
<motion.div
|
||||
className="hidden md:block absolute top-[-100px] left-[-100px] w-[350px] h-[350px] rounded-full border-[1.5px] border-[#6D28D9]/15 bg-[#6D28D9]/5 opacity-[0.55] z-0 pointer-events-none"
|
||||
animate={{ y: [0, -14, 0] }}
|
||||
transition={{ duration: 14, repeat: Infinity, ease: "easeInOut" }}
|
||||
/>
|
||||
|
||||
{/* 2. Purple solid circle (center-right around collage) */}
|
||||
<div className="hidden lg:block absolute right-[22%] top-[38%] w-[84px] h-[84px] rounded-full bg-[#6D28D9] z-0 shadow-[0_8px_32px_rgba(109,40,217,0.3)] pointer-events-none animate-pulse" />
|
||||
<div className="hidden lg:block absolute right-[22%] top-[38%] w-[84px] h-[84px] rounded-full bg-[#6D28D9] z-0 shadow-[0_8px_32px_rgba(109,40,217,0.3)] pointer-events-none motion-safe:animate-pulse" />
|
||||
|
||||
{/* 3. Striped purple circle (lower-middle collage area) */}
|
||||
<div
|
||||
@@ -53,7 +120,11 @@ export function Hero() {
|
||||
<div className="absolute bottom-[14%] right-[5%] text-[#6D28D9]/25 font-bold text-xl pointer-events-none select-none z-0">✕</div>
|
||||
|
||||
{/* 6. Large partial purple circle (bottom-right corner) */}
|
||||
<div className="absolute bottom-[-100px] right-[-100px] w-[200px] h-[200px] rounded-full bg-[#7C3AED]/80 z-0 pointer-events-none" />
|
||||
<motion.div
|
||||
className="absolute bottom-[-100px] right-[-100px] w-[200px] h-[200px] rounded-full bg-[#7C3AED]/80 z-0 pointer-events-none"
|
||||
animate={{ y: [0, 12, 0] }}
|
||||
transition={{ duration: 12, repeat: Infinity, ease: "easeInOut" }}
|
||||
/>
|
||||
|
||||
{/* ─── TWO-COLUMN CONTENT GRID ─── */}
|
||||
<div className="page-container relative grid min-h-[650px] grid-cols-1 lg:grid-cols-[0.92fr_1.08fr] items-center py-16 md:py-20 lg:py-24 z-10 gap-12 lg:gap-6">
|
||||
@@ -61,64 +132,84 @@ export function Hero() {
|
||||
{/* LEFT COLUMN: Editorial Typography & Copy */}
|
||||
<motion.div
|
||||
className="flex flex-col text-left max-w-xl z-20 relative"
|
||||
variants={staggerContainer}
|
||||
variants={heroStagger}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
{/* Badge */}
|
||||
<motion.span
|
||||
variants={fadeUp}
|
||||
variants={badgeVariant}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-black tracking-widest bg-[#6D28D9]/10 text-[#6D28D9] uppercase mb-6 w-fit"
|
||||
>
|
||||
Welcome to Nearle
|
||||
</motion.span>
|
||||
|
||||
<motion.h1
|
||||
variants={fadeUp}
|
||||
className="font-display text-4xl sm:text-5xl lg:text-[68px] xl:text-[76px] font-black text-[#16001D] leading-[0.95] tracking-[-0.05em]"
|
||||
>
|
||||
Everything Local.
|
||||
<br />
|
||||
Everything <span className="text-[#6D28D9]">Near.</span>
|
||||
</motion.h1>
|
||||
{/* H1: line-by-line mask reveal */}
|
||||
<motion.div variants={lineStagger} className="font-display text-4xl sm:text-5xl lg:text-[68px] xl:text-[76px] font-black text-[#16001D] tracking-[-0.05em]">
|
||||
<div className="overflow-hidden pb-1">
|
||||
<motion.div variants={lineReveal} className="leading-[0.95]">
|
||||
Everything Local.
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="overflow-hidden pb-1">
|
||||
<motion.div variants={lineReveal} className="leading-[0.95]">
|
||||
Everything <span className="text-[#6D28D9]">Near.</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Description */}
|
||||
<motion.p
|
||||
variants={fadeUp}
|
||||
variants={descVariant}
|
||||
className="max-w-[480px] text-base md:text-lg text-[#6D6172] leading-relaxed font-bold mt-6"
|
||||
>
|
||||
Discover verified local stores, exclusive offers, fast delivery, and a better way to shop within your neighbourhood.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
variants={fadeUp}
|
||||
className="flex flex-col sm:flex-row gap-3.5 mt-8 w-full sm:w-auto"
|
||||
>
|
||||
<Link
|
||||
href="/people"
|
||||
className="bg-[#6D28D9] hover:bg-[#5225b8] text-white font-black px-8 py-3.5 rounded-xl text-sm md:text-base text-center shadow-md hover:shadow-lg transition-all duration-200 active:scale-[0.98]"
|
||||
{/* CTAs: primary scales in, secondary slides from left */}
|
||||
<div className="flex flex-col sm:flex-row gap-3.5 mt-8 w-full sm:w-auto">
|
||||
<motion.div
|
||||
variants={primaryCtaVariant}
|
||||
whileHover={{ scale: 1.03, transition: { duration: 0.2, ease: EASE } }}
|
||||
whileTap={{ scale: 0.97, transition: { duration: 0.1 } }}
|
||||
>
|
||||
Explore Stores
|
||||
</Link>
|
||||
<Link
|
||||
href="/people#download-section"
|
||||
className="border-2 border-[#6D28D9]/20 hover:border-[#6D28D9]/40 text-[#6D28D9] font-black px-8 py-3.5 rounded-xl text-sm md:text-base text-center transition-all duration-200 hover:bg-[#6D28D9]/5 active:scale-[0.98]"
|
||||
<Link
|
||||
href="/people"
|
||||
className="bg-[#6D28D9] hover:bg-[#5225b8] text-white font-black px-8 py-3.5 rounded-xl text-sm md:text-base text-center shadow-md hover:shadow-lg transition-colors duration-200 block"
|
||||
>
|
||||
Explore Stores
|
||||
</Link>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
variants={secondaryCtaVariant}
|
||||
whileHover={{ scale: 1.03, transition: { duration: 0.2, ease: EASE } }}
|
||||
whileTap={{ scale: 0.97, transition: { duration: 0.1 } }}
|
||||
>
|
||||
Get the App
|
||||
</Link>
|
||||
</motion.div>
|
||||
<Link
|
||||
href="/people#download-section"
|
||||
className="border-2 border-[#6D28D9]/20 hover:border-[#6D28D9]/40 text-[#6D28D9] font-black px-8 py-3.5 rounded-xl text-sm md:text-base text-center transition-colors duration-200 hover:bg-[#6D28D9]/5 block"
|
||||
>
|
||||
Get the App
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* RIGHT COLUMN: Creative Overlapping Strips Collage */}
|
||||
<motion.div
|
||||
className="w-full z-20"
|
||||
variants={slideInRight}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
{/* RIGHT COLUMN: Individual image + center card animations */}
|
||||
<div className="w-full z-20">
|
||||
|
||||
{/* DESKTOP LAYOUT (lg and above) */}
|
||||
<div className="hidden lg:block relative h-[500px] lg:h-[560px] w-full">
|
||||
{/* left image: absolute left-[10%] top-[20%] w-[170px] h-[360px] */}
|
||||
<div className="absolute left-[10%] top-[20%] w-[170px] h-[360px] rounded-[1.5rem] overflow-hidden shadow-lg border-2 border-white z-10 transition-transform duration-300 hover:scale-[1.02]">
|
||||
|
||||
{/* Left image: slides in from left */}
|
||||
<motion.div
|
||||
className="absolute left-[10%] top-[20%] w-[170px] h-[360px] rounded-[1.5rem] overflow-hidden shadow-lg border-2 border-white z-10"
|
||||
variants={imgFromLeft}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ delay: 0.3 }}
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.25, ease: EASE } }}
|
||||
>
|
||||
<Image
|
||||
src="/images/cat-daily-essentials.png"
|
||||
alt="Local grocery products"
|
||||
@@ -126,37 +217,63 @@ export function Hero() {
|
||||
className="object-cover"
|
||||
sizes="170px"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* middle image: central logo panel absolute left-[38%] top-[8%] */}
|
||||
<div className="absolute left-[38%] top-[8%] z-20 transition-transform duration-300 hover:scale-[1.02]">
|
||||
<div className="relative w-[180px] sm:w-[220px] h-[280px] sm:h-[340px] rounded-[24px] border-[5px] sm:border-[6px] border-white/95 shadow-[0_20px_40px_rgba(99,48,214,0.1)] bg-[#FAF8FC] overflow-hidden flex items-center justify-center p-6">
|
||||
<Image
|
||||
src="/nearlenewlogo.png"
|
||||
alt="Central logo panel"
|
||||
width={160}
|
||||
height={160}
|
||||
className="object-contain max-h-[140px] w-auto animate-[pulse_6s_infinite]"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Center card: scales up from below, then gently bobs */}
|
||||
<motion.div
|
||||
className="absolute left-[38%] top-[8%] z-20"
|
||||
variants={centerCardReveal}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ delay: 0.15 }}
|
||||
>
|
||||
<motion.div
|
||||
animate={!reducedMotion ? { y: [0, -10, 0] } : {}}
|
||||
transition={{ delay: 0.9, duration: 4, repeat: Infinity, ease: "easeInOut" }}
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.25, ease: EASE } }}
|
||||
>
|
||||
<div className="relative w-[180px] sm:w-[220px] h-[280px] sm:h-[340px] rounded-[24px] border-[5px] sm:border-[6px] border-white/95 shadow-[0_20px_40px_rgba(99,48,214,0.1)] bg-[#FAF8FC] overflow-hidden flex items-center justify-center p-6">
|
||||
<Image
|
||||
src="/nearlenewlogo.png"
|
||||
alt="Central logo panel"
|
||||
width={160}
|
||||
height={160}
|
||||
className="object-contain max-h-[140px] w-auto motion-safe:animate-[pulse_6s_infinite]"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* right image: absolute right-[2%] top-[18%] w-[180px] h-[390px] */}
|
||||
<div className="absolute right-[2%] top-[18%] w-[180px] h-[390px] rounded-[1.5rem] overflow-hidden shadow-lg border-2 border-white z-10 transition-transform duration-300 hover:scale-[1.02]">
|
||||
{/* Right image: slides in from right */}
|
||||
<motion.div
|
||||
className="absolute right-[2%] top-[18%] w-[180px] h-[390px] rounded-[1.5rem] overflow-hidden shadow-lg border-2 border-white z-10"
|
||||
variants={imgFromRight}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ delay: 0.42 }}
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.25, ease: EASE } }}
|
||||
>
|
||||
<Image
|
||||
src="/nearle people.png"
|
||||
alt="Local customer shopping"
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
sizes="180px"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* MOBILE/TABLET COLLAGE (below lg) */}
|
||||
<div className="lg:hidden flex items-center justify-center gap-2 mt-2 w-full relative h-[280px]">
|
||||
<div className="relative w-[95px] h-[200px] rounded-2xl overflow-hidden border border-white shadow-md z-10">
|
||||
<motion.div
|
||||
className="relative w-[95px] h-[200px] rounded-2xl overflow-hidden border border-white shadow-md z-10"
|
||||
variants={imgFromLeft}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ delay: 0.28 }}
|
||||
>
|
||||
<Image
|
||||
src="/images/cat-daily-essentials.png"
|
||||
alt="Grocery products"
|
||||
@@ -164,18 +281,37 @@ export function Hero() {
|
||||
className="object-cover"
|
||||
sizes="95px"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative w-[115px] h-[240px] rounded-2xl border-4 border-white shadow-lg bg-[#FAF8FC] overflow-hidden flex items-center justify-center p-3 -mx-5 z-20">
|
||||
<Image
|
||||
src="/nearlenewlogo.png"
|
||||
alt="Central logo panel"
|
||||
width={100}
|
||||
height={100}
|
||||
className="object-contain max-h-[90px] w-auto animate-[pulse_6s_infinite]"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="relative w-[105px] h-[220px] rounded-2xl overflow-hidden border border-white shadow-md z-10">
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="relative w-[115px] h-[240px] rounded-2xl border-4 border-white shadow-lg bg-[#FAF8FC] overflow-hidden flex items-center justify-center p-3 -mx-5 z-20"
|
||||
variants={centerCardReveal}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ delay: 0.15 }}
|
||||
>
|
||||
<motion.div
|
||||
animate={!reducedMotion ? { y: [0, -6, 0] } : {}}
|
||||
transition={{ delay: 0.85, duration: 3.5, repeat: Infinity, ease: "easeInOut" }}
|
||||
>
|
||||
<Image
|
||||
src="/nearlenewlogo.png"
|
||||
alt="Central logo panel"
|
||||
width={100}
|
||||
height={100}
|
||||
className="object-contain max-h-[90px] w-auto motion-safe:animate-[pulse_6s_infinite]"
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="relative w-[105px] h-[220px] rounded-2xl overflow-hidden border border-white shadow-md z-10"
|
||||
variants={imgFromRight}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
transition={{ delay: 0.36 }}
|
||||
>
|
||||
<Image
|
||||
src="/nearle people.png"
|
||||
alt="Customer"
|
||||
@@ -183,10 +319,10 @@ export function Hero() {
|
||||
className="object-cover"
|
||||
sizes="105px"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -194,14 +330,13 @@ export function Hero() {
|
||||
<div className="border-t border-[#EEE7F4]/80 bg-white">
|
||||
<motion.div
|
||||
className="page-container py-8 grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4 relative z-20"
|
||||
variants={staggerContainer}
|
||||
variants={statStagger}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewport}
|
||||
>
|
||||
{PROOF_ITEMS.map((item, idx) => (
|
||||
<motion.div key={item.label} variants={fadeUp} className="flex items-center gap-4 text-left md:justify-center relative">
|
||||
{/* Subtle vertical separator for desktop, except for the first item */}
|
||||
<motion.div key={item.label} variants={statItem} className="flex items-center gap-4 text-left md:justify-center relative">
|
||||
{idx > 0 && (
|
||||
<div className="hidden md:block absolute left-0 top-1/2 -translate-y-1/2 w-[1px] h-10 bg-[#EEE7F4]" />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user