update d2c brands
This commit is contained in:
BIN
public/Gemini_Generated_Image_lxh416lxh416lxh4.png
Normal file
BIN
public/Gemini_Generated_Image_lxh416lxh416lxh4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
BIN
public/nerale (1).png
Normal file
BIN
public/nerale (1).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
@@ -2,8 +2,7 @@ import React from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
||||||
import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
|
import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
|
||||||
import { HeroVisualCollage } from "@/components/layout/HeroVisualCollage";
|
import { BrandsHero } from "@/components/heroes/BrandsHero";
|
||||||
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
|
||||||
import { FAQAccordion } from "@/components/ui/FAQAccordion";
|
import { FAQAccordion } from "@/components/ui/FAQAccordion";
|
||||||
import { AnimatedSection } from "@/components/motion/AnimatedSection";
|
import { AnimatedSection } from "@/components/motion/AnimatedSection";
|
||||||
import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
|
import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
|
||||||
@@ -22,13 +21,6 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const HERO_STATS = [
|
|
||||||
{ title: "Distribution Network", icon: "hub", desc: "Neighbourhood stores across city" },
|
|
||||||
{ title: "Retail Visibility", icon: "visibility", desc: "Premium shelf positioning" },
|
|
||||||
{ title: "Brand Campaigns", icon: "campaign", desc: "Hyperlocal promotions" },
|
|
||||||
{ title: "Real-Time Analytics", icon: "analytics", desc: "Sales & reach data" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const WHAT_WE_OFFER = [
|
const WHAT_WE_OFFER = [
|
||||||
{
|
{
|
||||||
icon: "hub",
|
icon: "hub",
|
||||||
@@ -153,67 +145,10 @@ const FAQ_ITEMS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function BrandsPage() {
|
export default function BrandsPage() {
|
||||||
const collageCards = [
|
|
||||||
{ text: "Local Distribution", icon: "hub" },
|
|
||||||
{ text: "Brand Campaigns", icon: "campaign" },
|
|
||||||
{ text: "Retail Analytics", icon: "analytics" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PremiumPageShell>
|
<PremiumPageShell>
|
||||||
{/* ─── Hero Section ─── */}
|
{/* ─── Hero Section ─── */}
|
||||||
<section className="w-full flex flex-col pt-6 md:pt-10 select-none bg-gradient-to-br from-white via-[#FEFCFF] to-[#F4ECFA]">
|
<BrandsHero />
|
||||||
<div className="border-b border-[#6330D6]/10">
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-[45%_55%] items-center gap-10 lg:gap-6 px-6 md:px-12 lg:px-16 pb-12 max-w-[1600px] mx-auto">
|
|
||||||
<div className="flex flex-col text-left max-w-xl">
|
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-black tracking-widest bg-[#6330D6]/10 text-[#6330D6] uppercase mb-6 w-fit">
|
|
||||||
Brand Partnerships
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<h1 className="font-display text-4xl sm:text-5xl lg:text-[48px] xl:text-[54px] font-black text-[#16001D] leading-[1.1] tracking-tight">
|
|
||||||
Expand Your Brand Across
|
|
||||||
<br />
|
|
||||||
<span className="text-[#6330D6]">Local Communities.</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p className="text-sm md:text-base text-[#5E5866] font-bold mt-6 leading-relaxed">
|
|
||||||
Nearle connects FMCG brands, food companies, and local manufacturers with verified neighbourhood stores and their loyal customer communities.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="hidden md:flex md:flex-row md:gap-3.5 mt-8">
|
|
||||||
<Link
|
|
||||||
href="/contact"
|
|
||||||
className="bg-[#6330D6] 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]"
|
|
||||||
>
|
|
||||||
Talk to Sales
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
href="#partnership"
|
|
||||||
className="border-2 border-[#6330D6]/20 hover:border-[#6330D6]/40 text-[#6330D6] font-black px-8 py-3.5 rounded-xl text-sm md:text-base text-center transition-all duration-200 hover:bg-[#6330D6]/5 active:scale-[0.98]"
|
|
||||||
>
|
|
||||||
See Partnership Process
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
|
||||||
<HeroVisualCollage
|
|
||||||
centerImage="/nearlenewlogo.png"
|
|
||||||
leftImage="/images/cat-daily-essentials.webp"
|
|
||||||
rightImage="/images/nearle-merchant/local-map.webp"
|
|
||||||
glassCards={collageCards}
|
|
||||||
isPhoneCenter={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-[#FAF7FD] border-b border-[#6330D6]/10">
|
|
||||||
<div className="px-6 md:px-12 lg:px-16 max-w-[1600px] mx-auto">
|
|
||||||
<FeatureStatsRow items={HERO_STATS} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* ─── What We Offer ─── */}
|
{/* ─── What We Offer ─── */}
|
||||||
<section className="py-10 md:py-14 xl:py-12 bg-white border-b border-[#6330D6]/10">
|
<section className="py-10 md:py-14 xl:py-12 bg-white border-b border-[#6330D6]/10">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const metadata: Metadata = {
|
|||||||
const HERO_STATS = [
|
const HERO_STATS = [
|
||||||
{ title: "Flexible Hours", icon: "schedule", desc: "Work when it suits you" },
|
{ title: "Flexible Hours", icon: "schedule", desc: "Work when it suits you" },
|
||||||
{ title: "Weekly Payouts", icon: "payments", desc: "Earnings settled every week" },
|
{ title: "Weekly Payouts", icon: "payments", desc: "Earnings settled every week" },
|
||||||
{ title: "Short Distances", icon: "near_me", desc: "Deliver within 2km radius" },
|
{ title: "Short Distances", icon: "near_me", desc: "Deliver within your neighbourhood" },
|
||||||
{ title: "Local Support", icon: "support_agent", desc: "Our team is always there" },
|
{ title: "Local Support", icon: "support_agent", desc: "Our team is always there" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ const BENEFITS = [
|
|||||||
{
|
{
|
||||||
icon: "near_me",
|
icon: "near_me",
|
||||||
title: "Short Local Deliveries",
|
title: "Short Local Deliveries",
|
||||||
desc: "Deliver within a 2km radius of your neighbourhood. No long-distance trips. No highway runs. Stay close to home.",
|
desc: "Deliver within your neighbourhood. No long-distance trips. No highway runs. Stay close to home.",
|
||||||
tone: "bg-[#6330D6]/8 text-[#6330D6]",
|
tone: "bg-[#6330D6]/8 text-[#6330D6]",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
||||||
|
import { HeroLogo } from "@/components/heroes/HeroLogo";
|
||||||
import { staggerContainer } from "@/lib/animations";
|
import { staggerContainer } from "@/lib/animations";
|
||||||
|
|
||||||
const HERO_STATS = [
|
const HERO_STATS = [
|
||||||
@@ -14,19 +15,20 @@ const HERO_STATS = [
|
|||||||
|
|
||||||
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||||
|
|
||||||
|
// Shared cascade timing: logo settles at ~1.2s, then badge → heading → description
|
||||||
const badgeVariant = {
|
const badgeVariant = {
|
||||||
hidden: { opacity: 0, y: 10, scale: 0.95 },
|
hidden: { opacity: 0, y: 10, scale: 0.95 },
|
||||||
visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.38, ease: EASE } },
|
visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.38, delay: 0.9, ease: EASE } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const titleVariant = {
|
const titleVariant = {
|
||||||
hidden: { opacity: 0, y: 28 },
|
hidden: { opacity: 0, y: 28 },
|
||||||
visible: { opacity: 1, y: 0, transition: { duration: 0.65, ease: EASE } },
|
visible: { opacity: 1, y: 0, transition: { duration: 0.65, delay: 1.05, ease: EASE } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const descVariant = {
|
const descVariant = {
|
||||||
hidden: { opacity: 0, y: 18 },
|
hidden: { opacity: 0, y: 18 },
|
||||||
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: EASE } },
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5, delay: 1.25, ease: EASE } },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AboutHero() {
|
export function AboutHero() {
|
||||||
@@ -60,6 +62,8 @@ export function AboutHero() {
|
|||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
>
|
>
|
||||||
|
<HeroLogo />
|
||||||
|
|
||||||
{/* Badge */}
|
{/* Badge */}
|
||||||
<motion.span
|
<motion.span
|
||||||
variants={badgeVariant}
|
variants={badgeVariant}
|
||||||
|
|||||||
99
src/components/heroes/BrandsHero.tsx
Normal file
99
src/components/heroes/BrandsHero.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import { HeroLogo } from "@/components/heroes/HeroLogo";
|
||||||
|
|
||||||
|
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Shared cascade timing: logo settles at ~1.2s, then badge → heading → description → CTA → trust
|
||||||
|
const badgeVariant = {
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5, delay: 0.9, ease: EASE } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const headingVariant = {
|
||||||
|
hidden: { opacity: 0, y: 24 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 1.0, ease: EASE } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const descVariant = {
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.55, delay: 1.1, ease: EASE } },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function BrandsHero() {
|
||||||
|
const shouldReduce = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="w-full flex flex-col select-none bg-white">
|
||||||
|
<div className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC]">
|
||||||
|
{/* Background: modern Indian shopping street — boutique storefronts, real photography */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 z-0"
|
||||||
|
initial={shouldReduce ? undefined : { scale: 1.08 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
transition={{ duration: 3, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/Gemini_Generated_Image_lxh416lxh416lxh4.png"
|
||||||
|
alt=""
|
||||||
|
fill
|
||||||
|
sizes="100vw"
|
||||||
|
className="absolute inset-0 h-full w-full object-cover object-[70%_center] md:object-[66%_center] xl:object-[60%_center]"
|
||||||
|
priority
|
||||||
|
fetchPriority="high"
|
||||||
|
aria-hidden="true"
|
||||||
|
unoptimized={true}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Overlay: dark behind the text on the left, fading out so the storefront stays vivid on the right */}
|
||||||
|
<div className="absolute inset-0 z-[1] bg-black/45 md:bg-[linear-gradient(90deg,rgba(9,4,19,0.82)_0%,rgba(9,4,19,0.62)_30%,rgba(9,4,19,0.28)_52%,rgba(9,4,19,0.04)_68%,rgba(9,4,19,0)_82%)]" />
|
||||||
|
|
||||||
|
<div className="page-container relative z-10 flex min-h-[560px] items-center justify-center px-6 pt-20 pb-10 sm:px-8 sm:pt-24 md:min-h-[85vh] md:py-20 lg:py-24">
|
||||||
|
<div className="relative z-20 flex w-full max-w-[860px] flex-col items-center text-center px-6 py-10 md:px-14 md:py-12">
|
||||||
|
<HeroLogo />
|
||||||
|
|
||||||
|
<motion.span
|
||||||
|
variants={badgeVariant}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6330D6] shadow-md uppercase mb-6 w-fit"
|
||||||
|
>
|
||||||
|
Nearle for D2C Brands
|
||||||
|
</motion.span>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
variants={headingVariant}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="font-display text-4xl sm:text-5xl lg:text-[48px] xl:text-[54px] font-extrabold text-white leading-[1.1] tracking-tight [text-shadow:0_4px_32px_rgba(0,0,0,0.5)]"
|
||||||
|
>
|
||||||
|
Grow Your <span className="text-[#A855F7]">Brand,</span>
|
||||||
|
<br />
|
||||||
|
Right Around
|
||||||
|
<br />
|
||||||
|
Your <span className="text-[#A855F7]">Customers.</span>
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
variants={descVariant}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="max-w-[520px] text-lg md:text-xl text-white/95 font-medium mt-6 leading-[1.6] [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
|
||||||
|
>
|
||||||
|
Help your D2C brand reach nearby customers, increase repeat purchases, and build a loyal local community—all from one powerful platform.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,11 +3,8 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
||||||
import {
|
import { HeroLogo } from "@/components/heroes/HeroLogo";
|
||||||
slideInLeftSubtle,
|
import { staggerContainer } from "@/lib/animations";
|
||||||
fadeIn,
|
|
||||||
staggerContainer,
|
|
||||||
} from "@/lib/animations";
|
|
||||||
|
|
||||||
const HERO_BENEFITS = [
|
const HERO_BENEFITS = [
|
||||||
{ title: "More Visibility Across Nearle", icon: "visibility", desc: "Get discovered by locals" },
|
{ title: "More Visibility Across Nearle", icon: "visibility", desc: "Get discovered by locals" },
|
||||||
@@ -16,16 +13,21 @@ const HERO_BENEFITS = [
|
|||||||
{ title: "Local Delivery Support", icon: "local_shipping", desc: "Deliver fast & verified" },
|
{ title: "Local Delivery Support", icon: "local_shipping", desc: "Deliver fast & verified" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const badgeVariant = slideInLeftSubtle;
|
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||||
|
|
||||||
|
// Shared cascade timing: logo settles at ~1.2s, then badge → heading → description
|
||||||
|
const badgeVariant = {
|
||||||
|
hidden: { opacity: 0, x: -20 },
|
||||||
|
visible: { opacity: 1, x: 0, transition: { duration: 0.5, delay: 0.9, ease: EASE } },
|
||||||
|
};
|
||||||
const titleVariant = {
|
const titleVariant = {
|
||||||
hidden: { opacity: 0, y: 28 },
|
hidden: { opacity: 0, y: 28 },
|
||||||
visible: {
|
visible: { opacity: 1, y: 0, transition: { duration: 0.55, delay: 1.05, ease: EASE } },
|
||||||
opacity: 1,
|
};
|
||||||
y: 0,
|
const descVariant = {
|
||||||
transition: { duration: 0.55, ease: [0.22, 1, 0.36, 1] as [number, number, number, number] },
|
hidden: { opacity: 0 },
|
||||||
},
|
visible: { opacity: 1, transition: { duration: 0.45, delay: 1.25, ease: "easeOut" as const } },
|
||||||
};
|
};
|
||||||
const descVariant = fadeIn;
|
|
||||||
|
|
||||||
export function BusinessHero() {
|
export function BusinessHero() {
|
||||||
return (
|
return (
|
||||||
@@ -52,6 +54,8 @@ export function BusinessHero() {
|
|||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
>
|
>
|
||||||
|
<HeroLogo />
|
||||||
|
|
||||||
<motion.span
|
<motion.span
|
||||||
variants={badgeVariant}
|
variants={badgeVariant}
|
||||||
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6330D6] shadow-md uppercase mb-6 w-fit"
|
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6330D6] shadow-md uppercase mb-6 w-fit"
|
||||||
|
|||||||
65
src/components/heroes/HeroLogo.tsx
Normal file
65
src/components/heroes/HeroLogo.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { motion, useReducedMotion, useInView } from "framer-motion";
|
||||||
|
|
||||||
|
// power4.out approximation
|
||||||
|
const EASE_LOGO = [0.16, 1, 0.3, 1] as [number, number, number, number];
|
||||||
|
// Keeps the logo readable over dark hero photography: brighten slightly, lift it off
|
||||||
|
// the background with a white outer glow, and ground it with a soft drop shadow.
|
||||||
|
const LOGO_FILTER = "brightness(1.12) drop-shadow(0 10px 30px rgba(0,0,0,0.35)) drop-shadow(0 0 10px rgba(255,255,255,0.18))";
|
||||||
|
|
||||||
|
type HeroLogoProps = {
|
||||||
|
/** Horizontal placement within its hero. Split-layout heroes that left-align
|
||||||
|
* their content (e.g. BrandsHero) pass "left" to stay flush with the badge below. */
|
||||||
|
align?: "center" | "left";
|
||||||
|
/** "compact" tightens the gap below the logo for heroes with more stacked content
|
||||||
|
* (e.g. BrandsHero, which also carries CTAs + trust indicators inside the hero). */
|
||||||
|
spacing?: "default" | "compact";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shared premium hero logo: identical size and entrance/float animation
|
||||||
|
// on every landing page hero (Home, Business, People, D2C Brands, About).
|
||||||
|
export function HeroLogo({ align = "center", spacing = "default" }: HeroLogoProps) {
|
||||||
|
const shouldReduce = useReducedMotion();
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const isInView = useInView(ref, { amount: 0.2 });
|
||||||
|
const [settled, setSettled] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={`${spacing === "compact" ? "mb-3.5" : "mb-[28px]"} ${align === "center" ? "flex justify-center" : ""}`}>
|
||||||
|
<motion.div
|
||||||
|
initial={shouldReduce ? undefined : { y: -80 }}
|
||||||
|
animate={
|
||||||
|
shouldReduce
|
||||||
|
? undefined
|
||||||
|
: settled
|
||||||
|
? { y: isInView ? [-6, 6, -6] : 0 }
|
||||||
|
: { y: 0 }
|
||||||
|
}
|
||||||
|
transition={
|
||||||
|
settled
|
||||||
|
? { duration: 4, ease: "easeInOut", repeat: isInView ? Infinity : 0 }
|
||||||
|
: { duration: 1, delay: 0.2, ease: EASE_LOGO }
|
||||||
|
}
|
||||||
|
onAnimationComplete={() => !settled && setSettled(true)}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
initial={shouldReduce ? undefined : { opacity: 0, scale: 0.9, filter: `blur(8px) ${LOGO_FILTER}` }}
|
||||||
|
animate={{ opacity: 1, scale: 1, filter: LOGO_FILTER }}
|
||||||
|
transition={{ duration: 1, delay: 0.2, ease: EASE_LOGO }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/nerale (1).png"
|
||||||
|
alt="Nearle"
|
||||||
|
width={465}
|
||||||
|
height={384}
|
||||||
|
className="w-[180px] sm:w-[220px] md:w-[280px] lg:w-[320px] h-auto"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,12 +3,8 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
|
||||||
import {
|
import { HeroLogo } from "@/components/heroes/HeroLogo";
|
||||||
scaleInSpring,
|
import { staggerFast } from "@/lib/animations";
|
||||||
fadeUpStrong,
|
|
||||||
fadeUp,
|
|
||||||
staggerFast,
|
|
||||||
} from "@/lib/animations";
|
|
||||||
|
|
||||||
const HERO_STATS = [
|
const HERO_STATS = [
|
||||||
{ title: "Verified Local Stores", icon: "store", desc: "Growing local commerce" },
|
{ title: "Verified Local Stores", icon: "store", desc: "Growing local commerce" },
|
||||||
@@ -17,9 +13,21 @@ const HERO_STATS = [
|
|||||||
{ title: "Trusted Platform", icon: "star", desc: "Loved by local shoppers" },
|
{ title: "Trusted Platform", icon: "star", desc: "Loved by local shoppers" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const badgeVariant = scaleInSpring;
|
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||||
const titleVariant = fadeUpStrong;
|
|
||||||
const descVariant = fadeUp;
|
// Shared cascade timing: logo settles at ~1.2s, then badge → heading → description
|
||||||
|
const badgeVariant = {
|
||||||
|
hidden: { opacity: 0, scale: 0.88 },
|
||||||
|
visible: { opacity: 1, scale: 1, transition: { type: "spring" as const, stiffness: 280, damping: 20, delay: 0.9 } },
|
||||||
|
};
|
||||||
|
const titleVariant = {
|
||||||
|
hidden: { opacity: 0, y: 36 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.65, delay: 1.05, ease: EASE } },
|
||||||
|
};
|
||||||
|
const descVariant = {
|
||||||
|
hidden: { opacity: 0, y: 24 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5, delay: 1.25, ease: EASE } },
|
||||||
|
};
|
||||||
|
|
||||||
export function PeopleHero() {
|
export function PeopleHero() {
|
||||||
return (
|
return (
|
||||||
@@ -45,6 +53,8 @@ export function PeopleHero() {
|
|||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
>
|
>
|
||||||
|
<HeroLogo />
|
||||||
|
|
||||||
<motion.span
|
<motion.span
|
||||||
variants={badgeVariant}
|
variants={badgeVariant}
|
||||||
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6330D6] shadow-md uppercase mb-6 w-fit"
|
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6330D6] shadow-md uppercase mb-6 w-fit"
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Fragment } from "react";
|
import { Fragment, useRef, type MouseEvent } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { motion, useReducedMotion, useMotionValue, useSpring, useTransform } from "framer-motion";
|
||||||
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
||||||
|
import { HeroLogo } from "@/components/heroes/HeroLogo";
|
||||||
import { viewport } from "@/lib/animations";
|
import { viewport } from "@/lib/animations";
|
||||||
|
|
||||||
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||||
|
// power4.out approximation, matches HeroLogo's entrance ease
|
||||||
|
const EASE_LOGO = [0.16, 1, 0.3, 1] as [number, number, number, number];
|
||||||
|
|
||||||
const PROOF_ITEMS = [
|
const PROOF_ITEMS = [
|
||||||
{ label: "Verified", text: "Local Stores", icon: "store" },
|
{ label: "Verified", text: "Local Stores", icon: "store" },
|
||||||
@@ -15,31 +18,25 @@ const PROOF_ITEMS = [
|
|||||||
{ label: "Trusted", text: "Local Platform", icon: "star" },
|
{ label: "Trusted", text: "Local Platform", icon: "star" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const heroStagger = {
|
// Shared cascade timing: logo settles at ~1.2s, then badge → heading → description
|
||||||
hidden: {},
|
|
||||||
visible: { transition: { staggerChildren: 0.12, delayChildren: 0.05 } },
|
|
||||||
};
|
|
||||||
|
|
||||||
const badgeVariant = {
|
const badgeVariant = {
|
||||||
hidden: { opacity: 0, y: 10, scale: 0.95 },
|
hidden: { opacity: 0, y: 25 },
|
||||||
visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.38, ease: EASE } },
|
visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 0.9, ease: EASE } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lines stagger internally
|
|
||||||
const lineStagger = {
|
const lineStagger = {
|
||||||
hidden: {},
|
hidden: {},
|
||||||
visible: { transition: { staggerChildren: 0.09 } },
|
visible: { transition: { staggerChildren: 0.15, delayChildren: 1.05 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// True mask reveal: text slides up from below a clipping parent
|
|
||||||
const lineReveal = {
|
const lineReveal = {
|
||||||
hidden: { y: "110%" },
|
hidden: { y: 60, opacity: 0, filter: "blur(8px)" },
|
||||||
visible: { y: 0, transition: { duration: 0.72, ease: EASE } },
|
visible: { y: 0, opacity: 1, filter: "blur(0px)", transition: { duration: 0.9, ease: EASE_LOGO } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const descVariant = {
|
const descVariant = {
|
||||||
hidden: { opacity: 0, y: 18 },
|
hidden: { opacity: 0, y: 30 },
|
||||||
visible: { opacity: 1, y: 0, transition: { duration: 0.55, ease: EASE } },
|
visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 1.25, ease: EASE } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const statStagger = {
|
const statStagger = {
|
||||||
@@ -54,47 +51,83 @@ const statItem = {
|
|||||||
|
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const shouldReduce = useReducedMotion();
|
const shouldReduce = useReducedMotion();
|
||||||
|
const sectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Subtle mouse parallax: background drifts 5px toward the cursor
|
||||||
|
const mouseX = useMotionValue(0);
|
||||||
|
const mouseY = useMotionValue(0);
|
||||||
|
const springX = useSpring(mouseX, { stiffness: 60, damping: 20, mass: 0.5 });
|
||||||
|
const springY = useSpring(mouseY, { stiffness: 60, damping: 20, mass: 0.5 });
|
||||||
|
const bgParallaxX = useTransform(springX, [-0.5, 0.5], [-5, 5]);
|
||||||
|
const bgParallaxY = useTransform(springY, [-0.5, 0.5], [-5, 5]);
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent<HTMLDivElement>) {
|
||||||
|
if (shouldReduce) return;
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
mouseX.set((e.clientX - rect.left) / rect.width - 0.5);
|
||||||
|
mouseY.set((e.clientY - rect.top) / rect.height - 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
mouseX.set(0);
|
||||||
|
mouseY.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="bg-white select-none w-full">
|
<section className="bg-white select-none w-full">
|
||||||
<div className="relative overflow-hidden bg-[#FAF8FC]">
|
<div
|
||||||
|
ref={sectionRef}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
className="relative overflow-hidden bg-[#FAF8FC]"
|
||||||
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
initial={{ scale: 1 }}
|
style={shouldReduce ? undefined : { x: bgParallaxX, y: bgParallaxY }}
|
||||||
animate={shouldReduce ? undefined : { scale: 1.06 }}
|
initial={shouldReduce ? undefined : { scale: 1.08 }}
|
||||||
transition={{ duration: 28, ease: "linear", repeat: Infinity, repeatType: "reverse" }}
|
animate={{ scale: 1 }}
|
||||||
|
transition={{ duration: 3, ease: "easeOut" }}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/images/home1.webp"
|
src="/images/home1.webp"
|
||||||
alt=""
|
alt=""
|
||||||
fill
|
fill
|
||||||
sizes="100vw"
|
sizes="100vw"
|
||||||
className="absolute inset-0 h-full w-full object-cover object-[62%_center] md:object-[58%_center] xl:object-center"
|
className="absolute inset-0 h-full w-full object-cover object-[70%_center] md:object-[66%_center] xl:object-[60%_center]"
|
||||||
priority
|
priority
|
||||||
fetchPriority="high"
|
fetchPriority="high"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<div className="absolute inset-0 z-[1] bg-black/40 md:bg-[radial-gradient(ellipse_100%_100%_at_50%_45%,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0.4)_40%,rgba(0,0,0,0.1)_100%)]" />
|
<motion.div
|
||||||
|
className="absolute inset-0 z-[1] bg-black/40 md:bg-[radial-gradient(ellipse_100%_100%_at_50%_45%,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0.4)_40%,rgba(0,0,0,0.1)_100%)]"
|
||||||
|
initial={shouldReduce ? undefined : { opacity: 0.6 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 1.2, ease: "easeOut" }}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="page-container relative z-10 flex min-h-[560px] items-center justify-center px-6 pt-20 pb-10 sm:px-8 sm:pt-24 md:min-h-[85vh] md:py-20 lg:py-24">
|
<div className="page-container relative z-10 flex min-h-[560px] items-center justify-center px-6 pt-20 pb-10 sm:px-8 sm:pt-24 md:min-h-[85vh] md:py-20 lg:py-24">
|
||||||
|
|
||||||
<motion.div
|
<div className="relative z-20 flex w-full max-w-[860px] flex-col items-center text-center px-6 py-10 md:px-14 md:py-12">
|
||||||
className="relative z-20 flex w-full max-w-[860px] flex-col items-center text-center px-6 py-10 md:px-14 md:py-12"
|
<HeroLogo />
|
||||||
variants={heroStagger}
|
|
||||||
initial="hidden"
|
|
||||||
animate="visible"
|
|
||||||
>
|
|
||||||
{/* Badge */}
|
{/* Badge */}
|
||||||
<motion.span
|
<motion.span
|
||||||
variants={badgeVariant}
|
variants={badgeVariant}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6D28D9] shadow-md uppercase mb-6 w-fit"
|
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6D28D9] shadow-md uppercase mb-6 w-fit"
|
||||||
>
|
>
|
||||||
Welcome to Nearle
|
Welcome to Nearle
|
||||||
</motion.span>
|
</motion.span>
|
||||||
|
|
||||||
{/* H1: line-by-line mask reveal */}
|
{/* H1: line-by-line reveal */}
|
||||||
<motion.h1 variants={lineStagger} className="font-display text-[34px] sm:text-5xl lg:text-[68px] xl:text-[76px] font-extrabold text-white tracking-[-0.05em] [text-shadow:0_4px_32px_rgba(0,0,0,0.5)]">
|
<motion.h1
|
||||||
|
variants={lineStagger}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="font-display text-[34px] sm:text-5xl lg:text-[68px] xl:text-[76px] font-extrabold text-white tracking-[-0.05em] [text-shadow:0_4px_32px_rgba(0,0,0,0.5)]"
|
||||||
|
>
|
||||||
<div className="overflow-hidden pb-1">
|
<div className="overflow-hidden pb-1">
|
||||||
<motion.div variants={lineReveal} className="leading-[0.95]">
|
<motion.div variants={lineReveal} className="leading-[0.95]">
|
||||||
Everything Local.
|
Everything Local.
|
||||||
@@ -110,11 +143,13 @@ export function Hero() {
|
|||||||
{/* Description */}
|
{/* Description */}
|
||||||
<motion.p
|
<motion.p
|
||||||
variants={descVariant}
|
variants={descVariant}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
className="max-w-[520px] text-lg md:text-xl text-white/95 leading-[1.6] font-medium mt-6 [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
|
className="max-w-[520px] text-lg md:text-xl text-white/95 leading-[1.6] font-medium mt-6 [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
|
||||||
>
|
>
|
||||||
Discover verified local stores, exclusive offers, fast delivery, and a better way to shop within your neighbourhood.
|
Discover verified local stores, exclusive offers, fast delivery, and a better way to shop within your neighbourhood.
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</motion.div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +185,7 @@ export function Hero() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
{idx < PROOF_ITEMS.length - 1 && (
|
{idx < PROOF_ITEMS.length - 1 && (
|
||||||
<div
|
<div
|
||||||
className="hidden lg:block w-px h-[72px] bg-[#E9D8FD] shrink-0 self-center"
|
className="hidden lg:block w-px h-[72px] bg-[#E9D8FD] shrink-0 self-center lg:mx-4 xl:mx-6"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const COMPANY = [
|
|||||||
const PARTNERSHIPS = [
|
const PARTNERSHIPS = [
|
||||||
{ label: "Merchant Partner", href: "/merchants" },
|
{ label: "Merchant Partner", href: "/merchants" },
|
||||||
{ label: "Delivery Partner", href: "/delivery-partners" },
|
{ label: "Delivery Partner", href: "/delivery-partners" },
|
||||||
{ label: "Direct to Customer Brands", href: "/brands" },
|
{ label: "D2C Brands", href: "/brands" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const LEGAL = [
|
const LEGAL = [
|
||||||
@@ -135,7 +135,7 @@ export function PremiumFooter() {
|
|||||||
|
|
||||||
<p className="max-w-[430px] text-base leading-[1.65] text-white/70 md:text-lg xl:text-[19px]">
|
<p className="max-w-[430px] text-base leading-[1.65] text-white/70 md:text-lg xl:text-[19px]">
|
||||||
India's leading hyperlocal neighborhood network, empowering
|
India's leading hyperlocal neighborhood network, empowering
|
||||||
communities and verified local businesses within a 2km radius.
|
communities and verified local businesses.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Social icons with scale spring on hover */}
|
{/* Social icons with scale spring on hover */}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const NAV_LINKS = [
|
|||||||
{ label: "Home", href: "/" },
|
{ label: "Home", href: "/" },
|
||||||
{ label: "Nearle for Business", href: "/businesses" },
|
{ label: "Nearle for Business", href: "/businesses" },
|
||||||
{ label: "Nearle for People", href: "/people" },
|
{ label: "Nearle for People", href: "/people" },
|
||||||
|
{ label: "Nearle for D2C Brands", href: "/brands" },
|
||||||
{ label: "About", href: "/about" },
|
{ label: "About", href: "/about" },
|
||||||
{ label: "Contact", href: "/contact" },
|
{ label: "Contact", href: "/contact" },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ export const siteContent = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
// TODO: Populate once app is confirmed live on both stores.
|
|
||||||
// Candidate iOS: https://apps.apple.com/us/app/nearle/id1596895375
|
// Candidate iOS: https://apps.apple.com/us/app/nearle/id1596895375
|
||||||
// Candidate Android: https://play.google.com/store/apps/details?id=com.nearle.gear
|
// Candidate Android: https://play.google.com/store/apps/details?id=com.nearle.gear
|
||||||
androidUrl: "",
|
androidUrl: "https://play.google.com/store/apps/details?id=com.nearle.gear",
|
||||||
iosUrl: "",
|
iosUrl: "https://apps.apple.com/us/app/nearle/id1596895375",
|
||||||
status: "coming-soon",
|
status: "coming-soon",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user