update d2c brands

This commit is contained in:
2026-07-02 18:08:29 +05:30
parent e7e1d1479c
commit 60147da53e
3 changed files with 231 additions and 33 deletions

View File

@@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image";
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";
@@ -7,6 +8,8 @@ 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";
import { CTABanner } from "@/components/ui/CTABanner"; import { CTABanner } from "@/components/ui/CTABanner";
import { NetworkBackground } from "@/components/brands/NetworkBackground";
import { PartnershipConnector } from "@/components/brands/PartnershipConnector";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -151,8 +154,9 @@ export default function BrandsPage() {
<BrandsHero /> <BrandsHero />
{/* ─── 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="relative overflow-hidden py-10 md:py-14 xl:py-12 bg-white border-b border-[#6330D6]/10">
<div className="px-6 md:px-12 lg:px-16 xl:px-20 max-w-[1600px] mx-auto"> <NetworkBackground />
<div className="relative z-10 px-6 md:px-12 lg:px-16 xl:px-20 2xl:px-24">
<AnimatedSection> <AnimatedSection>
<div className="text-center mb-10 md:mb-12"> <div className="text-center mb-10 md:mb-12">
<span className="text-xs text-[#6330D6] uppercase tracking-widest font-black bg-[#6330D6]/5 px-3.5 py-1 rounded-full"> <span className="text-xs text-[#6330D6] uppercase tracking-widest font-black bg-[#6330D6]/5 px-3.5 py-1 rounded-full">
@@ -183,7 +187,7 @@ export default function BrandsPage() {
</div> </div>
<div> <div>
<h3 className="text-base font-black text-[#16001D] leading-tight mb-2">{item.title}</h3> <h3 className="text-base font-black text-[#16001D] leading-tight mb-2">{item.title}</h3>
<p className="text-sm text-[#5E5866] leading-relaxed">{item.desc}</p> <p className="text-m text-[#5E5866] leading-relaxed">{item.desc}</p>
</div> </div>
</AnimatedCard> </AnimatedCard>
))} ))}
@@ -193,40 +197,78 @@ export default function BrandsPage() {
{/* ─── Industries ─── */} {/* ─── Industries ─── */}
<section className="py-10 md:py-14 xl:py-12 bg-[#FAF7FD] border-b border-[#6330D6]/10"> <section className="py-10 md:py-14 xl:py-12 bg-[#FAF7FD] border-b border-[#6330D6]/10">
<div className="px-6 md:px-12 lg:px-16 xl:px-20 max-w-[1600px] mx-auto"> <div className="px-6 md:px-12 lg:px-16 xl:px-20 2xl:px-24">
<AnimatedSection> <div className="grid grid-cols-1 lg:grid-cols-[30%_1fr] gap-10 xl:gap-16 items-center">
<div className="text-center mb-10"> {/* Real product collage */}
<span className="text-xs text-secondary uppercase tracking-widest font-black bg-secondary/5 px-3.5 py-1 rounded-full"> <AnimatedSection className="order-2 lg:order-1" variant="fadeUp">
Industries We Serve <div className="relative mx-auto h-[280px] w-full max-w-[420px] sm:h-[360px] lg:mx-0 lg:h-[440px]">
</span> <div className="absolute left-0 top-0 h-[62%] w-[72%] overflow-hidden rounded-[28px] border-4 border-white shadow-[0_20px_50px_rgba(22,0,29,0.16)]">
<h2 className="font-display text-3xl md:text-4xl font-black text-[#16001D] mt-4 mb-3 tracking-tight"> <Image
Your Category Has a Place on Nearle src="/images/cat-daily-essentials.webp"
</h2> alt="Daily essentials — milk, bread, and fresh produce stocked on Nearle stores"
<p className="text-sm md:text-base text-[#5E5866] max-w-2xl mx-auto leading-relaxed"> fill
Nearle&apos;s neighbourhood store network stocks a wide range of everyday product categories. If customers buy it daily, it belongs here. sizes="(max-width: 1023px) 300px, 22vw"
</p> className="object-cover"
</div> />
</AnimatedSection>
<AnimatedGrid className="grid grid-cols-3 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-9 gap-4">
{INDUSTRIES.map((industry) => (
<AnimatedCard
key={industry.label}
className="flex flex-col items-center gap-2.5 p-4 rounded-2xl bg-white border border-[#6330D6]/8 hover:border-[#6330D6]/20 hover:shadow-md transition-all duration-200 cursor-default"
>
<div className="w-10 h-10 rounded-full bg-[#6330D6]/8 text-[#6330D6] flex items-center justify-center">
<MaterialIcon icon={industry.icon} size={20} />
</div> </div>
<span className="text-[10px] font-black text-[#16001D] text-center leading-tight">{industry.label}</span> <div className="absolute right-0 top-[6%] h-[42%] w-[52%] overflow-hidden rounded-[22px] border-4 border-white shadow-[0_16px_40px_rgba(22,0,29,0.14)]">
</AnimatedCard> <Image
))} src="/images/cat-health-wellness.webp"
</AnimatedGrid> alt="Health and personal care products stocked on Nearle stores"
fill
sizes="(max-width: 1023px) 220px, 16vw"
className="object-cover"
/>
</div>
<div className="absolute bottom-0 left-[10%] h-[38%] w-[54%] overflow-hidden rounded-[22px] border-4 border-white shadow-[0_16px_40px_rgba(22,0,29,0.14)]">
<Image
src="/images/cat-pet-essentials.webp"
alt="Pet essentials brand stocked on Nearle stores"
fill
sizes="(max-width: 1023px) 220px, 16vw"
className="object-cover"
/>
</div>
</div>
</AnimatedSection>
{/* Content */}
<div className="order-1 lg:order-2">
<AnimatedSection>
<div className="mb-10 text-left">
<span className="text-xs text-secondary uppercase tracking-widest font-black bg-secondary/5 px-3.5 py-1 rounded-full">
Industries We Serve
</span>
<h2 className="font-display text-3xl md:text-4xl font-black text-[#16001D] mt-4 mb-3 tracking-tight">
Your Category Has a Place on Nearle
</h2>
<p className="text-sm md:text-base text-[#5E5866] max-w-2xl leading-relaxed">
Nearle&apos;s neighbourhood store network stocks a wide range of everyday product categories. If customers buy it daily, it belongs here.
</p>
</div>
</AnimatedSection>
<AnimatedGrid className="grid grid-cols-3 gap-4">
{INDUSTRIES.map((industry) => (
<AnimatedCard
key={industry.label}
className="flex flex-col items-center gap-2.5 p-4 rounded-2xl bg-white border border-[#6330D6]/8 hover:border-[#6330D6]/20 hover:shadow-md transition-all duration-200 cursor-default"
>
<div className="w-10 h-10 rounded-full bg-[#6330D6]/8 text-[#6330D6] flex items-center justify-center">
<MaterialIcon icon={industry.icon} size={20} />
</div>
<span className="text-[16px] font-black text-[#16001D] text-center leading-tight">{industry.label}</span>
</AnimatedCard>
))}
</AnimatedGrid>
</div>
</div>
</div> </div>
</section> </section>
{/* ─── Partnership Process ─── */} {/* ─── Partnership Process ─── */}
<section id="partnership" className="py-10 md:py-14 xl:py-12 bg-white border-b border-[#6330D6]/10"> <section id="partnership" className="py-10 md:py-14 xl:py-12 bg-white border-b border-[#6330D6]/10">
<div className="px-6 md:px-12 lg:px-16 xl:px-20 max-w-[1600px] mx-auto"> <div className="px-6 md:px-12 lg:px-16 xl:px-20 2xl:px-24">
<AnimatedSection> <AnimatedSection>
<div className="text-center mb-10 md:mb-12"> <div className="text-center mb-10 md:mb-12">
<span className="text-xs text-[#6330D6] uppercase tracking-widest font-black bg-[#6330D6]/5 px-3.5 py-1 rounded-full"> <span className="text-xs text-[#6330D6] uppercase tracking-widest font-black bg-[#6330D6]/5 px-3.5 py-1 rounded-full">
@@ -241,6 +283,8 @@ export default function BrandsPage() {
</div> </div>
</AnimatedSection> </AnimatedSection>
<PartnershipConnector steps={PARTNERSHIP_STEPS.length} />
<AnimatedGrid className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6"> <AnimatedGrid className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
{PARTNERSHIP_STEPS.map((step) => ( {PARTNERSHIP_STEPS.map((step) => (
<AnimatedCard <AnimatedCard
@@ -256,7 +300,7 @@ export default function BrandsPage() {
<h3 className="font-display text-xl font-black text-[#16001D] leading-tight mb-2"> <h3 className="font-display text-xl font-black text-[#16001D] leading-tight mb-2">
{step.title} {step.title}
</h3> </h3>
<p className="text-sm text-[#5E5866] leading-relaxed">{step.desc}</p> <p className="text-md text-[#5E5866] leading-relaxed">{step.desc}</p>
</AnimatedCard> </AnimatedCard>
))} ))}
</AnimatedGrid> </AnimatedGrid>
@@ -265,10 +309,24 @@ export default function BrandsPage() {
{/* ─── FAQ ─── */} {/* ─── FAQ ─── */}
<section className="py-10 md:py-14 xl:py-12 bg-[#FAF7FD] border-b border-[#6330D6]/10"> <section className="py-10 md:py-14 xl:py-12 bg-[#FAF7FD] border-b border-[#6330D6]/10">
<div className="px-6 md:px-12 lg:px-16 xl:px-20 max-w-[1600px] mx-auto"> <div className="px-6 md:px-12 lg:px-16 xl:px-20 2xl:px-24">
<div className="grid grid-cols-1 lg:grid-cols-[360px_1fr] gap-10 lg:gap-16 items-start"> <div className="grid grid-cols-1 lg:grid-cols-[360px_1fr] gap-10 lg:gap-16 items-start">
<AnimatedSection> <AnimatedSection>
<div className="lg:sticky lg:top-28"> <div className="lg:sticky lg:top-28">
<div className="relative mb-6">
<div className="absolute -left-6 -top-6 h-28 w-28 rounded-full bg-[#6330D6]/25 blur-3xl -z-10" />
<div className="absolute -right-4 -bottom-4 h-24 w-24 rounded-full bg-secondary/20 blur-3xl -z-10" />
<div className="relative h-[220px] sm:h-[260px] rounded-[28px] overflow-hidden shadow-[0_20px_50px_rgba(22,0,29,0.16)]">
<Image
src="/images/nearle-merchant/success-owner.webp"
alt="Neighbourhood store owner reviewing brand orders on his phone"
fill
sizes="(max-width: 1023px) 90vw, 360px"
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-[#16001D]/70 via-[#6330D6]/20 to-transparent" />
</div>
</div>
<span className="text-xs text-[#6330D6] uppercase tracking-widest font-black bg-[#6330D6]/5 px-3.5 py-1 rounded-full"> <span className="text-xs text-[#6330D6] uppercase tracking-widest font-black bg-[#6330D6]/5 px-3.5 py-1 rounded-full">
FAQs FAQs
</span> </span>

View File

@@ -0,0 +1,85 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
const NODES = [
{ cx: 60, cy: 80 }, { cx: 220, cy: 40 }, { cx: 380, cy: 120 },
{ cx: 520, cy: 60 }, { cx: 680, cy: 140 }, { cx: 140, cy: 260 },
{ cx: 320, cy: 300 }, { cx: 480, cy: 260 }, { cx: 620, cy: 320 },
{ cx: 80, cy: 420 }, { cx: 260, cy: 460 }, { cx: 440, cy: 420 },
{ cx: 600, cy: 480 }, { cx: 740, cy: 400 },
];
const LINES: [number, number][] = [
[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [1, 6], [2, 7], [3, 8], [4, 8],
[5, 6], [6, 7], [7, 8], [5, 9], [6, 10], [7, 11], [8, 12], [9, 10], [10, 11], [11, 12], [12, 13], [8, 13],
];
const PARTICLES = [
{ cx: 100, cy: 500 }, { cx: 260, cy: 80 }, { cx: 420, cy: 520 }, { cx: 580, cy: 100 },
{ cx: 720, cy: 480 }, { cx: 40, cy: 250 }, { cx: 380, cy: 200 }, { cx: 660, cy: 260 },
];
/** Ambient network backdrop for Section 1 — nodes, routes, and drifting particles at very low opacity. Purely decorative. */
export function NetworkBackground() {
const reduce = useReducedMotion();
return (
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-[0.07]" aria-hidden="true">
<motion.div
className="absolute left-1/2 top-1/2 h-[520px] w-[520px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#6330D6] blur-[130px]"
animate={reduce ? undefined : { opacity: [0.45, 0.9, 0.45] }}
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
/>
<svg
className="absolute inset-0 h-full w-full"
viewBox="0 0 800 600"
fill="none"
preserveAspectRatio="xMidYMid slice"
>
{LINES.map(([a, b], i) => {
const n1 = NODES[a];
const n2 = NODES[b];
return (
<motion.line
key={`l-${a}-${b}`}
x1={n1.cx}
y1={n1.cy}
x2={n2.cx}
y2={n2.cy}
stroke="#6330D6"
strokeWidth={1}
strokeDasharray="4 7"
animate={reduce ? undefined : { strokeDashoffset: [0, -44] }}
transition={{ duration: 6 + (i % 5), repeat: Infinity, ease: "linear" }}
/>
);
})}
{NODES.map((n, i) => (
<motion.circle
key={`n-${i}`}
cx={n.cx}
cy={n.cy}
fill="#6330D6"
animate={reduce ? undefined : { r: [3, 5, 3], opacity: [0.4, 1, 0.4] }}
initial={{ r: 3, opacity: 0.6 }}
transition={{ duration: 3 + (i % 4), repeat: Infinity, ease: "easeInOut", delay: (i % 5) * 0.4 }}
/>
))}
{PARTICLES.map((p, i) => (
<motion.circle
key={`p-${i}`}
cx={p.cx}
r={2}
fill="#6330D6"
initial={{ cy: p.cy, opacity: 0 }}
animate={reduce ? undefined : { cy: [p.cy, p.cy - 46, p.cy], opacity: [0, 0.85, 0] }}
transition={{ duration: 7 + (i % 4), repeat: Infinity, ease: "easeInOut", delay: i * 0.6 }}
/>
))}
</svg>
</div>
);
}

View File

@@ -0,0 +1,55 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
/** One-shot workflow connector for the Partnership Process — draws once on scroll into view, then a glow dot travels Step 1 → Step 4 and stops. No looping. Desktop-only (aligns to the single-row xl:grid-cols-4 layout). */
export function PartnershipConnector({ steps }: { steps: number }) {
const reduce = useReducedMotion();
const positions = Array.from({ length: steps }, (_, i) => ((i + 0.5) / steps) * 100);
const start = positions[0];
const end = positions[positions.length - 1];
const lineDuration = 0.9;
const dotDuration = 1.1;
return (
<div className="relative mb-3 hidden h-6 xl:block" aria-hidden="true">
<motion.div
className="absolute top-1/2 h-0 origin-left -translate-y-1/2 border-t-2 border-dashed border-[#6330D6]/20"
style={{ left: `${start}%`, right: `${100 - end}%` }}
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
viewport={{ once: true, amount: 0.4 }}
transition={reduce ? { duration: 0 } : { duration: lineDuration, ease: EASE }}
/>
{positions.map((left, i) => (
<motion.div
key={i}
className="absolute top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#6330D6]/40"
style={{ left: `${left}%` }}
initial={{ opacity: 0, scale: 0.4 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true, amount: 0.4 }}
transition={reduce ? { duration: 0 } : { duration: 0.35, ease: EASE, delay: lineDuration * 0.6 }}
/>
))}
<motion.div
className="absolute top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#6330D6] shadow-[0_0_14px_4px_rgba(99,48,214,0.55)]"
initial={{ left: `${start}%`, opacity: 0 }}
whileInView={{ left: `${end}%`, opacity: 1 }}
viewport={{ once: true, amount: 0.4 }}
transition={
reduce
? { duration: 0 }
: {
left: { duration: dotDuration, ease: EASE, delay: lineDuration },
opacity: { duration: 0.2, delay: lineDuration },
}
}
/>
</div>
);
}