update animation pages

This commit is contained in:
2026-06-29 11:34:56 +05:30
parent a89cd2939a
commit 9449accea6
19 changed files with 468 additions and 158 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 657 KiB

43
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "nearle-new", "name": "nearle-new",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"framer-motion": "^12.42.0",
"jsdom": "^29.1.1", "jsdom": "^29.1.1",
"next": "16.2.9", "next": "16.2.9",
"puppeteer-core": "^24.43.1", "puppeteer-core": "^24.43.1",
@@ -4306,6 +4307,33 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/framer-motion": {
"version": "12.42.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.0.tgz",
"integrity": "sha512-wp7EJnfWaaEScVygKv3e20udoRz+LbtxScsuTkakAxfXmt+ReC6WyPW2nINRAGvd+hG9odwcjBLyOTPjH5pBRA==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.42.0",
"motion-utils": "^12.39.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5811,6 +5839,21 @@
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/motion-dom": {
"version": "12.42.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.0.tgz",
"integrity": "sha512-M63h4n8R+quJdNhBwuLlgxM+OLYa9+I/T2pzDRboB9fLXRdbou+Gw7Zury+SkpaCyACP1JHSjHgZ1EgTkBr30w==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.39.0"
}
},
"node_modules/motion-utils": {
"version": "12.39.0",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@@ -9,6 +9,7 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"framer-motion": "^12.42.0",
"jsdom": "^29.1.1", "jsdom": "^29.1.1",
"next": "16.2.9", "next": "16.2.9",
"puppeteer-core": "^24.43.1", "puppeteer-core": "^24.43.1",

View File

@@ -262,7 +262,7 @@ export default function BrandsPage() {
Your Category Has a Place on Nearle Your Category Has a Place on Nearle
</h2> </h2>
<p className="text-sm md:text-base text-[#5E5866] max-w-2xl mx-auto leading-relaxed"> <p className="text-sm md:text-base text-[#5E5866] max-w-2xl mx-auto leading-relaxed">
Nearle's neighbourhood store network stocks a wide range of everyday product categories. If customers buy it daily, it belongs here. Nearle&apos;s neighbourhood store network stocks a wide range of everyday product categories. If customers buy it daily, it belongs here.
</p> </p>
</div> </div>

View File

@@ -64,7 +64,7 @@ export default function ContactPage() {
</div> </div>
<div className="text-left"> <div className="text-left">
<h4 className="text-[#16001D] text-sm font-black">Email Us</h4> <h4 className="text-[#16001D] text-sm font-black">Email Us</h4>
<p className="text-[#5E5866] text-sm font-semibold mt-0.5">support@nearle.in partners@nearle.in</p> <p className="text-[#5E5866] text-sm font-semibold mt-0.5">nearle@care.in</p>
</div> </div>
</div> </div>
@@ -74,13 +74,13 @@ export default function ContactPage() {
<MaterialIcon icon="location_on" size={18} /> <MaterialIcon icon="location_on" size={18} />
</div> </div>
<div className="text-left"> <div className="text-left">
<h4 className="text-[#16001D] text-sm font-black">Visit Us</h4> <h4 className="text-[#16001D] text-sm font-black">Address </h4>
<p className="text-[#5E5866] text-sm font-semibold mt-0.5">424, DB Rd, R.S. Puram, Coimbatore, TN 641002</p> <p className="text-[#5E5866] text-sm font-semibold mt-0.5">424, DB Rd, R.S. Puram, Coimbatore, TN 641002</p>
</div> </div>
</div> </div>
{/* Helpdesk */} {/* Helpdesk */}
<div className="flex gap-4 py-4.5 border-b border-[#6330D6]/10 items-center"> {/* <div className="flex gap-4 py-4.5 border-b border-[#6330D6]/10 items-center">
<div className="w-10 h-10 rounded-full bg-[#6330D6]/10 text-[#6330D6] flex items-center justify-center shrink-0"> <div className="w-10 h-10 rounded-full bg-[#6330D6]/10 text-[#6330D6] flex items-center justify-center shrink-0">
<MaterialIcon icon="support_agent" size={18} /> <MaterialIcon icon="support_agent" size={18} />
</div> </div>
@@ -88,7 +88,7 @@ export default function ContactPage() {
<h4 className="text-[#16001D] text-sm font-black">Helpdesk</h4> <h4 className="text-[#16001D] text-sm font-black">Helpdesk</h4>
<p className="text-[#5E5866] text-sm font-semibold mt-0.5">24/7 Phone Helpdesk Active</p> <p className="text-[#5E5866] text-sm font-semibold mt-0.5">24/7 Phone Helpdesk Active</p>
</div> </div>
</div> </div> */}
</div> </div>
</div> </div>

View File

@@ -365,7 +365,7 @@ export default function DeliveryPartnersPage() {
</section> </section>
{/* ─── Earnings Illustration ─── */} {/* ─── Earnings Illustration ─── */}
<section id="earnings" className="py-10 md:py-14 xl:py-12 bg-[#FAF7FD] border-b border-[#6330D6]/10"> {/* <section id="earnings" 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 max-w-[1600px] mx-auto">
<div className="text-center mb-10 md:mb-12"> <div className="text-center mb-10 md:mb-12">
<span className="text-xs text-secondary uppercase tracking-widest font-black bg-secondary/5 px-3.5 py-1 rounded-full"> <span className="text-xs text-secondary uppercase tracking-widest font-black bg-secondary/5 px-3.5 py-1 rounded-full">
@@ -410,7 +410,7 @@ export default function DeliveryPartnersPage() {
Estimates include base delivery fees only. Peak-hour surge pricing, performance bonuses, and festive incentives are additional. Earnings vary by zone. Estimates include base delivery fees only. Peak-hour surge pricing, performance bonuses, and festive incentives are additional. Earnings vary by zone.
</p> </p>
</div> </div>
</section> </section> */}
{/* ─── FAQ ─── */} {/* ─── FAQ ─── */}
<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">

View File

@@ -79,6 +79,7 @@ export default function RootLayout({
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
{/* eslint-disable-next-line @next/next/no-page-custom-font */}
<link <link
href="https://fonts.googleapis.com/css2?family=Jost:wght@400;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Jost:wght@400;600;700;800&display=swap"
rel="stylesheet" rel="stylesheet"

View File

@@ -1,6 +1,10 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, fadeIn, scaleIn, staggerContainer, viewport } from "@/lib/animations";
const FEATURES = [ const FEATURES = [
{ {
@@ -68,17 +72,17 @@ export function BusinessSection() {
<div className="pointer-events-none absolute -left-16 top-24 h-64 w-64 rounded-full bg-primary/5 blur-3xl" /> <div className="pointer-events-none absolute -left-16 top-24 h-64 w-64 rounded-full bg-primary/5 blur-3xl" />
<div className="pointer-events-none absolute -right-20 bottom-10 h-64 w-64 rounded-full bg-secondary/5 blur-3xl" /> <div className="pointer-events-none absolute -right-20 bottom-10 h-64 w-64 rounded-full bg-secondary/5 blur-3xl" />
{/*
* Grid layout:
* mobile (<768px) : 1 col — text → cards (1/row) → success story [via CSS order]
* tablet (7681279px) : 1 col — text → success story → cards 2×2 [via CSS order]
* desktop (≥1280px) : 2 col — [left: text + success story] | [right: cards 2×2]
*/}
<div className="relative grid gap-6 grid-cols-1 md:gap-7 <div className="relative grid gap-6 grid-cols-1 md:gap-7
xl:grid-cols-[420px_1fr] xl:gap-8 xl:items-start"> xl:grid-cols-[420px_1fr] xl:gap-8 xl:items-start">
{/* ── 1. Left text content (always col-1 row-1) ── */} {/* ── 1. Left text content ── */}
<div className="min-w-0 xl:col-start-1 xl:row-start-1"> <motion.div
className="min-w-0 xl:col-start-1 xl:row-start-1"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{/* Badge */} {/* Badge */}
<div className="flex w-fit items-center gap-2 rounded-full border border-secondary/20 bg-secondary/5 px-3 py-1.5"> <div className="flex w-fit items-center gap-2 rounded-full border border-secondary/20 bg-secondary/5 px-3 py-1.5">
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-secondary/15"> <div className="flex h-5 w-5 items-center justify-center rounded-full bg-secondary/15">
@@ -133,20 +137,23 @@ export function BusinessSection() {
<MaterialIcon icon="arrow_forward" size={18} /> <MaterialIcon icon="arrow_forward" size={18} />
</Link> </Link>
</div> </div>
</div> </motion.div>
{/* ── 2. Feature cards — 2×2 grid (right col on desktop) ── {/* ── 2. Feature cards ── */}
mobile : order-2 → appears after text, before success story <motion.div
tablet : order-3 → appears after text + success story className="min-w-0
desktop : explicit col-2, row-span-2
*/}
<div className="min-w-0
order-2 md:order-3 order-2 md:order-3
xl:order-none xl:col-start-2 xl:row-start-1 xl:row-span-2 xl:order-none xl:col-start-2 xl:row-start-1 xl:row-span-2
grid grid-cols-1 gap-4 md:grid-cols-2"> grid grid-cols-1 gap-4 md:grid-cols-2"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{FEATURES.map((feature) => ( {FEATURES.map((feature) => (
<article <motion.article
key={feature.title} key={feature.title}
variants={fadeUp}
className="merchant-feature-card flex min-w-0 flex-col overflow-hidden rounded-[1.5rem] border border-primary/10 bg-white shadow-[0_4px_24px_rgba(22,0,29,0.07)]" className="merchant-feature-card flex min-w-0 flex-col overflow-hidden rounded-[1.5rem] border border-primary/10 bg-white shadow-[0_4px_24px_rgba(22,0,29,0.07)]"
> >
<div className="p-5"> <div className="p-5">
@@ -174,20 +181,22 @@ export function BusinessSection() {
className="block h-auto w-full object-cover" className="block h-auto w-full object-cover"
/> />
</div> </div>
</article> </motion.article>
))} ))}
</div> </motion.div>
{/* ── 3. Success story ── {/* ── 3. Success story ── */}
mobile : order-3 → appears after feature cards <motion.div
tablet : order-2 → appears after text, before cards className="min-w-0
desktop : explicit col-1, row-2 (below text in left column)
*/}
<div className="min-w-0
order-3 md:order-2 order-3 md:order-2
xl:order-none xl:col-start-1 xl:row-start-2 xl:order-none xl:col-start-1 xl:row-start-2
overflow-hidden rounded-[1.5rem] border border-primary/10 bg-white/90 shadow-[0_4px_24px_rgba(22,0,29,0.07)] overflow-hidden rounded-[1.5rem] border border-primary/10 bg-white/90 shadow-[0_4px_24px_rgba(22,0,29,0.07)]
grid grid-cols-[100px_minmax(0,1fr)]"> grid grid-cols-[100px_minmax(0,1fr)]"
variants={scaleIn}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div className="relative min-h-[128px] overflow-hidden bg-brand-accent-light"> <div className="relative min-h-[128px] overflow-hidden bg-brand-accent-light">
<Image <Image
src="/images/nearle-merchant/success-owner.webp" src="/images/nearle-merchant/success-owner.webp"
@@ -208,14 +217,20 @@ export function BusinessSection() {
Nearle helped us reach more customers in our area and manage orders effortlessly. Nearle helped us reach more customers in our area and manage orders effortlessly.
</p> </p>
</div> </div>
</div> </motion.div>
</div> </div>
{/* ── Benefits strip ── */} {/* ── Benefits strip ── */}
<div className="relative mt-6 overflow-hidden rounded-[1.5rem] border border-primary/10 bg-white/90 shadow-[0_4px_24px_rgba(22,0,29,0.06)]"> <motion.div
className="relative mt-6 overflow-hidden rounded-[1.5rem] border border-primary/10 bg-white/90 shadow-[0_4px_24px_rgba(22,0,29,0.06)]"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 divide-y sm:divide-y-0 sm:divide-x divide-primary/8 xl:divide-x xl:divide-y-0"> <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 divide-y sm:divide-y-0 sm:divide-x divide-primary/8 xl:divide-x xl:divide-y-0">
{BENEFITS.map((b) => ( {BENEFITS.map((b) => (
<div key={b.title} className="flex items-start gap-3 p-5"> <motion.div key={b.title} variants={fadeIn} className="flex items-start gap-3 p-5">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-secondary/10 text-secondary"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-secondary/10 text-secondary">
<MaterialIcon icon={b.icon} size={20} /> <MaterialIcon icon={b.icon} size={20} />
</div> </div>
@@ -223,10 +238,10 @@ export function BusinessSection() {
<div className="text-sm font-black text-brand-dark">{b.title}</div> <div className="text-sm font-black text-brand-dark">{b.title}</div>
<div className="mt-0.5 text-xs leading-relaxed text-on-surface-variant">{b.desc}</div> <div className="mt-0.5 text-xs leading-relaxed text-on-surface-variant">{b.desc}</div>
</div> </div>
</div> </motion.div>
))} ))}
</div> </div>
</div> </motion.div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,6 +1,15 @@
"use client";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import {
fadeUp,
slideInRight,
staggerContainer,
viewport,
} from "@/lib/animations";
const PROOF_ITEMS = [ const PROOF_ITEMS = [
{ label: "Verified", text: "Local Stores", icon: "store" }, { label: "Verified", text: "Local Stores", icon: "store" },
@@ -13,7 +22,7 @@ export function Hero() {
return ( return (
<section className="relative overflow-hidden bg-gradient-to-b from-[#FAF8FC] to-[#FFFFFF] select-none w-full"> <section className="relative overflow-hidden bg-gradient-to-b from-[#FAF8FC] to-[#FFFFFF] select-none w-full">
{/* ─── DECORATIONS (BEHIND CONTENT) ─── */} {/* ─── DECORATIONS (BEHIND CONTENT) ─── */}
{/* 1. Large pale lavender partial circle (top-left) */} {/* 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" /> <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" />
@@ -21,10 +30,10 @@ export function Hero() {
<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 animate-pulse" />
{/* 3. Striped purple circle (lower-middle collage area) */} {/* 3. Striped purple circle (lower-middle collage area) */}
<div <div
className="hidden lg:block absolute left-[45%] bottom-[15%] w-[100px] h-[100px] rounded-full opacity-80 z-0 pointer-events-none" className="hidden lg:block absolute left-[45%] bottom-[15%] w-[100px] h-[100px] rounded-full opacity-80 z-0 pointer-events-none"
style={{ style={{
backgroundImage: 'repeating-linear-gradient(45deg, rgba(109, 40, 217, 0.2) 0px, rgba(109, 40, 217, 0.2) 2px, transparent 2px, transparent 8px)' backgroundImage: 'repeating-linear-gradient(45deg, rgba(109, 40, 217, 0.2) 0px, rgba(109, 40, 217, 0.2) 2px, transparent 2px, transparent 8px)'
}} }}
/> />
@@ -48,24 +57,41 @@ export function Hero() {
{/* ─── TWO-COLUMN CONTENT GRID ─── */} {/* ─── 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"> <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">
{/* LEFT COLUMN: Editorial Typography & Copy */}
<div className="flex flex-col text-left max-w-xl z-20 relative">
<span 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
</span>
<h1 className="font-display text-4xl sm:text-5xl lg:text-[68px] xl:text-[76px] font-black text-[#16001D] leading-[0.95] tracking-[-0.05em]"> {/* LEFT COLUMN: Editorial Typography & Copy */}
<motion.div
className="flex flex-col text-left max-w-xl z-20 relative"
variants={staggerContainer}
initial="hidden"
animate="visible"
>
<motion.span
variants={fadeUp}
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. Everything Local.
<br /> <br />
Everything <span className="text-[#6D28D9]">Near.</span> Everything <span className="text-[#6D28D9]">Near.</span>
</h1> </motion.h1>
<p className="max-w-[480px] text-base md:text-lg text-[#6D6172] leading-relaxed font-bold mt-6"> <motion.p
variants={fadeUp}
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. Discover verified local stores, exclusive offers, fast delivery, and a better way to shop within your neighbourhood.
</p> </motion.p>
<div className="flex flex-col sm:flex-row gap-3.5 mt-8 w-full sm:w-auto"> <motion.div
variants={fadeUp}
className="flex flex-col sm:flex-row gap-3.5 mt-8 w-full sm:w-auto"
>
<Link <Link
href="/people" 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]" 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]"
@@ -78,20 +104,25 @@ export function Hero() {
> >
Get the App Get the App
</Link> </Link>
</div> </motion.div>
</div> </motion.div>
{/* RIGHT COLUMN: Creative Overlapping Strips Collage */} {/* RIGHT COLUMN: Creative Overlapping Strips Collage */}
<div className="w-full z-20"> <motion.div
className="w-full z-20"
variants={slideInRight}
initial="hidden"
animate="visible"
>
{/* DESKTOP LAYOUT (lg and above) */} {/* DESKTOP LAYOUT (lg and above) */}
<div className="hidden lg:block relative h-[500px] lg:h-[560px] w-full"> <div className="hidden lg:block relative h-[500px] lg:h-[560px] w-full">
{/* left image: absolute left-[10%] top-[20%] w-[170px] h-[360px] */} {/* 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]"> <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]">
<Image <Image
src="/images/cat-daily-essentials.png" src="/images/cat-daily-essentials.png"
alt="Local grocery products" alt="Local grocery products"
fill fill
className="object-cover" className="object-cover"
sizes="170px" sizes="170px"
/> />
@@ -113,10 +144,10 @@ export function Hero() {
{/* right image: absolute right-[2%] top-[18%] w-[180px] h-[390px] */} {/* 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]"> <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]">
<Image <Image
src="/nearle people.png" src="/nearle people.png"
alt="Local customer shopping" alt="Local customer shopping"
fill fill
className="object-cover" className="object-cover"
sizes="180px" sizes="180px"
/> />
@@ -126,44 +157,50 @@ export function Hero() {
{/* MOBILE/TABLET COLLAGE (below lg) */} {/* 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="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"> <div className="relative w-[95px] h-[200px] rounded-2xl overflow-hidden border border-white shadow-md z-10">
<Image <Image
src="/images/cat-daily-essentials.png" src="/images/cat-daily-essentials.png"
alt="Grocery products" alt="Grocery products"
fill fill
className="object-cover" className="object-cover"
sizes="95px" sizes="95px"
/> />
</div> </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"> <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 <Image
src="/nearlenewlogo.png" src="/nearlenewlogo.png"
alt="Central logo panel" alt="Central logo panel"
width={100} width={100}
height={100} height={100}
className="object-contain max-h-[90px] w-auto animate-[pulse_6s_infinite]" className="object-contain max-h-[90px] w-auto animate-[pulse_6s_infinite]"
priority priority
/> />
</div> </div>
<div className="relative w-[105px] h-[220px] rounded-2xl overflow-hidden border border-white shadow-md z-10"> <div className="relative w-[105px] h-[220px] rounded-2xl overflow-hidden border border-white shadow-md z-10">
<Image <Image
src="/nearle people.png" src="/nearle people.png"
alt="Customer" alt="Customer"
fill fill
className="object-cover" className="object-cover"
sizes="105px" sizes="105px"
/> />
</div> </div>
</div> </div>
</div> </motion.div>
</div> </div>
{/* ─── BOTTOM PROOF STATS ROW ─── */} {/* ─── BOTTOM PROOF STATS ROW ─── */}
<div className="border-t border-[#EEE7F4]/80 bg-white"> <div className="border-t border-[#EEE7F4]/80 bg-white">
<div className="page-container py-8 grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4 relative z-20"> <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}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{PROOF_ITEMS.map((item, idx) => ( {PROOF_ITEMS.map((item, idx) => (
<div key={item.label} className="flex items-center gap-4 text-left md:justify-center relative"> <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 */} {/* Subtle vertical separator for desktop, except for the first item */}
{idx > 0 && ( {idx > 0 && (
<div className="hidden md:block absolute left-0 top-1/2 -translate-y-1/2 w-[1px] h-10 bg-[#EEE7F4]" /> <div className="hidden md:block absolute left-0 top-1/2 -translate-y-1/2 w-[1px] h-10 bg-[#EEE7F4]" />
@@ -179,9 +216,9 @@ export function Hero() {
{item.text} {item.text}
</div> </div>
</div> </div>
</div> </motion.div>
))} ))}
</div> </motion.div>
</div> </div>
</section> </section>

View File

@@ -1,5 +1,16 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import {
fadeUp,
scaleIn,
staggerContainer,
slideInLeft,
slideInRight,
viewport,
} from "@/lib/animations";
const STEPS = [ const STEPS = [
{ {
@@ -174,7 +185,6 @@ function PhoneMapVisual() {
function StepVisual({ type }: { type: string }) { function StepVisual({ type }: { type: string }) {
if (type === "store") { if (type === "store") {
// Inset box gives the storefront image breathing room on all sides
return ( return (
<div className="absolute inset-x-4 bottom-0 top-2"> <div className="absolute inset-x-4 bottom-0 top-2">
<div className="relative h-full w-full"> <div className="relative h-full w-full">
@@ -198,7 +208,6 @@ function StepVisual({ type }: { type: string }) {
); );
} }
// Bag: chips on the left, image on the right keep bag from overflowing right edge
return ( return (
<div className="flex h-full items-end justify-end gap-2 px-2"> <div className="flex h-full items-end justify-end gap-2 px-2">
<div className="mb-4 hidden shrink-0 space-y-2 sm:block"> <div className="mb-4 hidden shrink-0 space-y-2 sm:block">
@@ -227,7 +236,13 @@ export function HowItWorks() {
{/* HERO AREA */} {/* HERO AREA */}
<div className="mx-auto w-[calc(100%-96px)] max-w-[1720px]"> <div className="mx-auto w-[calc(100%-96px)] max-w-[1720px]">
<div className="grid min-w-0 grid-cols-1 items-center gap-10 lg:grid-cols-[38fr_62fr]"> <div className="grid min-w-0 grid-cols-1 items-center gap-10 lg:grid-cols-[38fr_62fr]">
<div className="min-w-0"> <motion.div
className="min-w-0"
variants={slideInLeft}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<span className="inline-flex items-center gap-2 rounded-full border border-secondary/15 bg-secondary/5 px-4 py-2 text-xs font-black uppercase tracking-widest text-secondary"> <span className="inline-flex items-center gap-2 rounded-full border border-secondary/15 bg-secondary/5 px-4 py-2 text-xs font-black uppercase tracking-widest text-secondary">
<MaterialIcon icon="eco" size={16} /> <MaterialIcon icon="eco" size={16} />
Together, We Grow Together, We Grow
@@ -245,9 +260,15 @@ export function HowItWorks() {
<MaterialIcon icon="forum" size={20} /> <MaterialIcon icon="forum" size={20} />
Talk to an Expert Talk to an Expert
</button> </button>
</div> </motion.div>
<div className="relative flex min-h-[320px] justify-center md:min-h-[440px] xl:min-h-[400px]"> <motion.div
className="relative flex min-h-[320px] justify-center md:min-h-[440px] xl:min-h-[400px]"
variants={scaleIn}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div className="relative w-full max-w-[680px]"> <div className="relative w-full max-w-[680px]">
<div className="relative h-[280px] overflow-hidden rounded-[2rem] bg-brand-accent-light shadow-[0_26px_70px_rgba(22,0,29,0.16)] md:h-[440px] xl:h-[400px]"> <div className="relative h-[280px] overflow-hidden rounded-[2rem] bg-brand-accent-light shadow-[0_26px_70px_rgba(22,0,29,0.16)] md:h-[440px] xl:h-[400px]">
<Image <Image
@@ -263,7 +284,7 @@ export function HowItWorks() {
<StorePhone /> <StorePhone />
<GrowthPill /> <GrowthPill />
</div> </div>
</div> </motion.div>
</div> </div>
</div> </div>
@@ -274,7 +295,13 @@ export function HowItWorks() {
{/* CARDS SECTION */} {/* CARDS SECTION */}
<div className="mx-auto w-full max-w-[2200px] px-6 lg:px-10 xl:px-12"> <div className="mx-auto w-full max-w-[2200px] px-6 lg:px-10 xl:px-12">
<div className="mt-10 text-center md:mt-14 xl:mt-10"> <motion.div
className="mt-10 text-center md:mt-14 xl:mt-10"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<span className="inline-flex rounded-full bg-primary/8 px-4 py-2 text-xs font-black uppercase tracking-widest text-primary"> <span className="inline-flex rounded-full bg-primary/8 px-4 py-2 text-xs font-black uppercase tracking-widest text-primary">
How It Works How It Works
</span> </span>
@@ -284,12 +311,19 @@ export function HowItWorks() {
<p className="mx-auto mt-4 max-w-2xl text-base leading-relaxed text-on-surface-variant md:text-lg"> <p className="mx-auto mt-4 max-w-2xl text-base leading-relaxed text-on-surface-variant md:text-lg">
Three simple milestones to scale your retail reach from offline streets straight into neighbourhood homes. Three simple milestones to scale your retail reach from offline streets straight into neighbourhood homes.
</p> </p>
</div> </motion.div>
<div className="mt-10 grid min-w-0 grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3"> <motion.div
className="mt-10 grid min-w-0 grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{STEPS.map((step) => ( {STEPS.map((step) => (
<article <motion.article
key={step.num} key={step.num}
variants={fadeUp}
className="relative flex min-h-[380px] min-w-0 flex-col overflow-hidden rounded-3xl border border-primary/10 bg-white p-7 shadow-[0_18px_45px_rgba(22,0,29,0.08)] md:min-h-[420px] lg:min-h-[460px] xl:min-h-[420px] lg:p-8" className="relative flex min-h-[380px] min-w-0 flex-col overflow-hidden rounded-3xl border border-primary/10 bg-white p-7 shadow-[0_18px_45px_rgba(22,0,29,0.08)] md:min-h-[420px] lg:min-h-[460px] xl:min-h-[420px] lg:p-8"
> >
<span className="absolute right-5 top-4 font-display text-6xl font-black leading-none text-primary/[0.07]"> <span className="absolute right-5 top-4 font-display text-6xl font-black leading-none text-primary/[0.07]">
@@ -309,9 +343,9 @@ export function HowItWorks() {
<div className={`relative z-0 mt-auto w-full overflow-hidden rounded-[1.5rem] bg-brand-accent-light/60 ${step.visual === "phone" ? "h-64 lg:h-72" : "h-48 lg:h-52"}`}> <div className={`relative z-0 mt-auto w-full overflow-hidden rounded-[1.5rem] bg-brand-accent-light/60 ${step.visual === "phone" ? "h-64 lg:h-72" : "h-48 lg:h-52"}`}>
<StepVisual type={step.visual} /> <StepVisual type={step.visual} />
</div> </div>
</article> </motion.article>
))} ))}
</div> </motion.div>
</div> </div>
</section> </section>
); );

View File

@@ -1,6 +1,10 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, slideInLeft, staggerContainer, viewport } from "@/lib/animations";
const FEATURES = [ const FEATURES = [
{ {
@@ -33,11 +37,17 @@ export function HyperlocalFeatures() {
<div className="absolute right-[15%] top-1/2 translate-x-1/2 -translate-y-1/2 w-[55%] h-[55%] rounded-full bg-primary/[0.03] blur-[130px] pointer-events-none z-0" /> <div className="absolute right-[15%] top-1/2 translate-x-1/2 -translate-y-1/2 w-[55%] h-[55%] rounded-full bg-primary/[0.03] blur-[130px] pointer-events-none z-0" />
{/* Inner container */} {/* Inner container */}
<div className="relative z-10 mx-auto w-full max-w-[2200px] px-2 md:px-4 lg:px-5 xl:px-6"> <div className="relative z-10 mx-auto w-full max-w-[2200px] px-2 md:px-4 lg:px-5 xl:px-6">
<div className="grid grid-cols-1 lg:grid-cols-[0.95fr_1.05fr] gap-8 lg:gap-10 items-center"> <div className="grid grid-cols-1 lg:grid-cols-[0.95fr_1.05fr] gap-8 lg:gap-10 items-center">
{/* Left Column: Visual Storytelling */} {/* Left Column: Visual Storytelling */}
<div className="w-full max-w-[880px] justify-self-center relative rounded-[2rem] border border-outline-variant/25 bg-[#F8F3FC] z-10 overflow-hidden aspect-[10/11] lg:h-[580px] shadow-[0_24px_80px_rgba(101,20,154,0.10)]"> <motion.div
className="w-full max-w-[880px] justify-self-center relative rounded-[2rem] border border-outline-variant/25 bg-[#F8F3FC] z-10 overflow-hidden aspect-[10/11] lg:h-[580px] shadow-[0_24px_80px_rgba(101,20,154,0.10)]"
variants={slideInLeft}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_52%_42%,rgba(124,58,237,0.12),transparent_34%),linear-gradient(145deg,rgba(255,255,255,0.94),rgba(243,232,246,0.62))]" /> <div className="absolute inset-0 bg-[radial-gradient(circle_at_52%_42%,rgba(124,58,237,0.12),transparent_34%),linear-gradient(145deg,rgba(255,255,255,0.94),rgba(243,232,246,0.62))]" />
<div className="absolute inset-6 rounded-[1.5rem] overflow-hidden bg-white/55 ring-1 ring-white/80 shadow-[inset_0_1px_0_rgba(255,255,255,0.9)]"> <div className="absolute inset-6 rounded-[1.5rem] overflow-hidden bg-white/55 ring-1 ring-white/80 shadow-[inset_0_1px_0_rgba(255,255,255,0.9)]">
<svg <svg
@@ -135,32 +145,55 @@ export function HyperlocalFeatures() {
</div> </div>
</div> </div>
</div> </motion.div>
{/* Right Column: Feature Panel directly on page background */} {/* Right Column: Feature Panel directly on page background */}
<div className="w-full py-6 md:py-8 lg:py-10 pl-4 md:pl-8 lg:pl-12 xl:pl-44 relative z-10 text-[#16001D]"> <div className="w-full py-6 md:py-8 lg:py-10 pl-4 md:pl-8 lg:pl-12 xl:pl-44 relative z-10 text-[#16001D]">
<span className="text-xs text-primary uppercase tracking-widest font-black bg-primary/10 px-3.5 py-1.5 rounded-full border border-primary/20"> <motion.span
className="text-xs text-primary uppercase tracking-widest font-black bg-primary/10 px-3.5 py-1.5 rounded-full border border-primary/20"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
Community & Trust Community & Trust
</span> </motion.span>
<h3 className="text-4xl md:text-5xl lg:text-[44px] font-display font-black text-brand-dark mt-4 mb-6 leading-tight tracking-tight"> <motion.h3
className="text-4xl md:text-5xl lg:text-[44px] font-display font-black text-brand-dark mt-4 mb-6 leading-tight tracking-tight"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
Built Around Your<br /> Built Around Your<br />
<span className="text-primary font-black">Neighbourhood</span> <span className="text-primary font-black">Neighbourhood</span>
</h3> </motion.h3>
<p className="text-body-lg text-on-surface-variant mb-10 max-w-lg leading-relaxed"> <motion.p
className="text-body-lg text-on-surface-variant mb-10 max-w-lg leading-relaxed"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
Nearle brings the convenience of an app to the stores you already know and trust, making it easy to support local shops. Nearle brings the convenience of an app to the stores you already know and trust, making it easy to support local shops.
</p> </motion.p>
{/* Features List with vertical connector line */} {/* Features List with vertical connector line */}
<div className="relative space-y-8"> <motion.div
className="relative space-y-8"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{/* Vertical connector line */} {/* Vertical connector line */}
<div className="absolute left-6 top-6 bottom-6 w-0.5 bg-primary/20 -translate-x-1/2 z-0" /> <div className="absolute left-6 top-6 bottom-6 w-0.5 bg-primary/20 -translate-x-1/2 z-0" />
{FEATURES.map((item) => ( {FEATURES.map((item) => (
<div key={item.title} className="flex gap-5 group relative z-10"> <motion.div key={item.title} variants={fadeUp} className="flex gap-5 group relative z-10">
{/* Icon circle */} {/* Icon circle */}
<div className="flex-shrink-0 w-12 h-12 rounded-full bg-primary text-white flex items-center justify-center shadow-md relative z-10 group-hover:scale-105 transition-transform duration-300"> <div className="flex-shrink-0 w-12 h-12 rounded-full bg-primary text-white flex items-center justify-center shadow-md relative z-10 group-hover:scale-105 transition-transform duration-300">
<MaterialIcon <MaterialIcon
@@ -169,7 +202,7 @@ export function HyperlocalFeatures() {
size={22} size={22}
/> />
</div> </div>
<div> <div>
<h4 className="text-lg md:text-xl font-black text-brand-dark group-hover:text-primary transition-colors"> <h4 className="text-lg md:text-xl font-black text-brand-dark group-hover:text-primary transition-colors">
{item.title} {item.title}
@@ -178,15 +211,21 @@ export function HyperlocalFeatures() {
{item.desc} {item.desc}
</p> </p>
</div> </div>
</div> </motion.div>
))} ))}
</div> </motion.div>
{/* Divider Line above CTA Area */} {/* Divider Line above CTA Area */}
<hr className="border-t border-[#EEE7F4] my-6 relative z-10" /> <hr className="border-t border-[#EEE7F4] my-6 relative z-10" />
{/* Onboarding Call to Action */} {/* Onboarding Call to Action */}
<div className="mt-2 flex flex-wrap gap-6 items-center relative z-10"> <motion.div
className="mt-2 flex flex-wrap gap-6 items-center relative z-10"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<Link <Link
href="/#download" href="/#download"
className="bg-primary hover:bg-primary-container text-white font-black px-8 py-3.5 rounded-full text-sm md:text-base hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0 active:scale-95 transition-all duration-200" className="bg-primary hover:bg-primary-container text-white font-black px-8 py-3.5 rounded-full text-sm md:text-base hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0 active:scale-95 transition-all duration-200"
@@ -204,7 +243,7 @@ export function HyperlocalFeatures() {
className="transition-transform group-hover:translate-x-1" className="transition-transform group-hover:translate-x-1"
/> />
</Link> </Link>
</div> </motion.div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,12 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, fadeIn, staggerContainer, viewport } from "@/lib/animations";
const MotionLink = motion(Link);
const CATEGORIES = [ const CATEGORIES = [
{ {
@@ -58,7 +64,13 @@ export function ServiceCategories() {
<section className="section bg-white w-full"> <section className="section bg-white w-full">
<div className="page-container"> <div className="page-container">
{/* Header Section */} {/* Header Section */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-10 md:mb-12 xl:mb-10 gap-6"> <motion.div
className="flex flex-col md:flex-row justify-between items-start md:items-end mb-10 md:mb-12 xl:mb-10 gap-6"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div> <div>
<span className="text-xs text-primary uppercase tracking-widest font-black bg-primary/10 px-3 py-1.5 rounded-full"> <span className="text-xs text-primary uppercase tracking-widest font-black bg-primary/10 px-3 py-1.5 rounded-full">
Neighbourhood Offerings Neighbourhood Offerings
@@ -77,14 +89,22 @@ export function ServiceCategories() {
View All Categories View All Categories
<MaterialIcon icon="arrow_forward" size={18} className="transition-transform group-hover:translate-x-1" /> <MaterialIcon icon="arrow_forward" size={18} className="transition-transform group-hover:translate-x-1" />
</Link> </Link>
</div> </motion.div>
{/* Categories Premium Grid */} {/* Categories Premium Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> {/* fadeIn (opacity only) preserves .premium-card:hover CSS transform */}
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{CATEGORIES.map((cat) => ( {CATEGORIES.map((cat) => (
<Link <MotionLink
key={cat.name} key={cat.name}
href="/people" href="/people"
variants={fadeIn}
className="bg-white rounded-[2rem] border border-outline-variant/10 shadow-lg overflow-hidden premium-card flex flex-col group h-full cursor-pointer" className="bg-white rounded-[2rem] border border-outline-variant/10 shadow-lg overflow-hidden premium-card flex flex-col group h-full cursor-pointer"
> >
{/* Image Container */} {/* Image Container */}
@@ -132,11 +152,10 @@ export function ServiceCategories() {
</div> </div>
</div> </div>
</div> </div>
</Link> </MotionLink>
))} ))}
</div> </motion.div>
</div> </div>
</section> </section>
); );
} }

View File

@@ -1,5 +1,9 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, scaleIn, slideInRight, staggerContainer, viewport } from "@/lib/animations";
const FLOW_STEPS = [ const FLOW_STEPS = [
{ label: "Scan QR", icon: "qr_code_scanner" }, { label: "Scan QR", icon: "qr_code_scanner" },
@@ -17,12 +21,18 @@ export function StoreQRSection() {
<div className="absolute left-[-10%] bottom-[-10%] w-[50%] h-[50%] rounded-full bg-primary/10 blur-[120px] pointer-events-none" /> <div className="absolute left-[-10%] bottom-[-10%] w-[50%] h-[50%] rounded-full bg-primary/10 blur-[120px] pointer-events-none" />
<div className="w-full relative z-10"> <div className="w-full relative z-10">
{/* ─── DESKTOP LAYOUT (lg and above) ─── */} {/* ─── DESKTOP LAYOUT (lg and above) ─── */}
<div className="hidden lg:grid lg:grid-cols-12 gap-12 xl:gap-16 items-center"> <div className="hidden lg:grid lg:grid-cols-12 gap-12 xl:gap-16 items-center">
{/* Left Column: Heading, flow steps, download buttons */} {/* Left Column: Heading, flow steps, download buttons */}
<div className="lg:col-span-6"> <motion.div
className="lg:col-span-6"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<span className="text-xs text-brand-accent uppercase tracking-widest font-black bg-brand-accent/10 px-3.5 py-1.5 rounded-full border border-brand-accent/20"> <span className="text-xs text-brand-accent uppercase tracking-widest font-black bg-brand-accent/10 px-3.5 py-1.5 rounded-full border border-brand-accent/20">
Instant Connections & App Instant Connections & App
</span> </span>
@@ -83,12 +93,21 @@ export function StoreQRSection() {
</div> </div>
</div> </div>
</div> </div>
</div> </motion.div>
{/* Right Column: Visual components (QR Sticker & Phone Mockup) */} {/* Right Column: Visual components (QR Sticker & Phone Mockup) */}
<div className="lg:col-span-6 flex items-center justify-center gap-8 relative select-none"> <motion.div
className="lg:col-span-6 flex items-center justify-center gap-8 relative select-none"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{/* Small supporting visual: QR Sticker (Secondary) */} {/* Small supporting visual: QR Sticker (Secondary) */}
<div className="relative w-[210px] shrink-0 aspect-[9/11.5] bg-white rounded-[2rem] p-6 text-brand-dark flex flex-col justify-between items-center shadow-2xl border-2 border-white/20"> <motion.div
variants={scaleIn}
className="relative w-[210px] shrink-0 aspect-[9/11.5] bg-white rounded-[2rem] p-6 text-brand-dark flex flex-col justify-between items-center shadow-2xl border-2 border-white/20"
>
<div className="text-center w-full"> <div className="text-center w-full">
<p className="text-[10px] font-black text-primary tracking-wider uppercase">Order Online</p> <p className="text-[10px] font-black text-primary tracking-wider uppercase">Order Online</p>
<h4 className="font-display font-black text-brand-dark text-base mt-0.5 leading-none">Store QR</h4> <h4 className="font-display font-black text-brand-dark text-base mt-0.5 leading-none">Store QR</h4>
@@ -108,10 +127,13 @@ export function StoreQRSection() {
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
Scan to Shop Scan to Shop
</span> </span>
</div> </motion.div>
{/* Primary large visual: Phone Mockup (Significantly Larger) */} {/* Primary large visual: Phone Mockup (Significantly Larger) */}
<div className="relative w-[290px] shrink-0 aspect-[9/18.5] bg-[#120217] rounded-[2.75rem] border-[7px] border-brand-dark-surface shadow-2xl overflow-hidden ring-4 ring-outline-variant/10"> <motion.div
variants={slideInRight}
className="relative w-[290px] shrink-0 aspect-[9/18.5] bg-[#120217] rounded-[2.75rem] border-[7px] border-brand-dark-surface shadow-2xl overflow-hidden ring-4 ring-outline-variant/10"
>
<Image <Image
src="/mobile image.jpeg" src="/mobile image.jpeg"
alt="Nearle mobile app interface" alt="Nearle mobile app interface"
@@ -119,16 +141,19 @@ export function StoreQRSection() {
height={760} height={760}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </motion.div>
</motion.div>
</div>
</div> </div>
{/* ─── MOBILE & TABLET LAYOUT (below lg) ─── */} {/* ─── MOBILE & TABLET LAYOUT (below lg) ─── */}
<div className="lg:hidden flex flex-col gap-12 text-center"> <div className="lg:hidden flex flex-col gap-12 text-center">
{/* 1. Heading and description */} {/* 1. Heading and description */}
<div> <motion.div
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<span className="text-xs text-brand-accent uppercase tracking-widest font-black bg-brand-accent/10 px-3.5 py-1.5 rounded-full border border-brand-accent/20 inline-block"> <span className="text-xs text-brand-accent uppercase tracking-widest font-black bg-brand-accent/10 px-3.5 py-1.5 rounded-full border border-brand-accent/20 inline-block">
Instant Connections & App Instant Connections & App
</span> </span>
@@ -138,10 +163,16 @@ export function StoreQRSection() {
<p className="text-body-md text-white/70 max-w-md mx-auto leading-relaxed"> <p className="text-body-md text-white/70 max-w-md mx-auto leading-relaxed">
Scan Nearle QRs to instantly save local stores, and order from them anytime in one convenient app. Scan Nearle QRs to instantly save local stores, and order from them anytime in one convenient app.
</p> </p>
</div> </motion.div>
{/* 2. QR Sticker */} {/* 2. QR Sticker */}
<div className="mx-auto w-[240px] aspect-[9/11.5] bg-white rounded-[2rem] p-6 text-brand-dark flex flex-col justify-between items-center shadow-xl border-2 border-white/20 select-none"> <motion.div
className="mx-auto w-[240px] aspect-[9/11.5] bg-white rounded-[2rem] p-6 text-brand-dark flex flex-col justify-between items-center shadow-xl border-2 border-white/20 select-none"
variants={scaleIn}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div className="text-center w-full"> <div className="text-center w-full">
<p className="text-[10px] font-black text-primary tracking-wider uppercase">Order Online</p> <p className="text-[10px] font-black text-primary tracking-wider uppercase">Order Online</p>
<h4 className="font-display font-black text-brand-dark text-base mt-0.5">Store QR</h4> <h4 className="font-display font-black text-brand-dark text-base mt-0.5">Store QR</h4>
@@ -161,7 +192,7 @@ export function StoreQRSection() {
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
Scan to Shop Scan to Shop
</span> </span>
</div> </motion.div>
{/* 3. Scan → Save → Order steps */} {/* 3. Scan → Save → Order steps */}
<div className="flex items-center justify-center gap-4 max-w-sm mx-auto select-none"> <div className="flex items-center justify-center gap-4 max-w-sm mx-auto select-none">
@@ -183,7 +214,13 @@ export function StoreQRSection() {
</div> </div>
{/* 4. Phone Mockup */} {/* 4. Phone Mockup */}
<div className="mx-auto w-[240px] aspect-[9/18.5] bg-[#120217] rounded-[2.5rem] border-[5px] border-brand-dark-surface shadow-xl relative overflow-hidden select-none"> <motion.div
className="mx-auto w-[240px] aspect-[9/18.5] bg-[#120217] rounded-[2.5rem] border-[5px] border-brand-dark-surface shadow-xl relative overflow-hidden select-none"
variants={slideInRight}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<Image <Image
src="/mobile image.jpeg" src="/mobile image.jpeg"
alt="Nearle mobile app interface" alt="Nearle mobile app interface"
@@ -191,7 +228,7 @@ export function StoreQRSection() {
height={760} height={760}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </motion.div>
{/* 5. App store buttons — TODO: replace with real URLs once app is live */} {/* 5. App store buttons — TODO: replace with real URLs once app is live */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-4"> <div className="flex flex-col sm:flex-row items-center justify-center gap-4">

View File

@@ -1,5 +1,9 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, fadeIn, staggerContainer, viewport } from "@/lib/animations";
const CARDS = [ const CARDS = [
{ {
@@ -41,7 +45,13 @@ export function WhyNearle() {
<section className="section section-soft w-full overflow-hidden"> <section className="section section-soft w-full overflow-hidden">
<div className="page-container relative"> <div className="page-container relative">
{/* Header Section */} {/* Header Section */}
<div className="relative text-center mb-8 md:mb-10"> <motion.div
className="relative text-center mb-8 md:mb-10"
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
<div className="pointer-events-none absolute -left-2 top-0 hidden h-36 w-36 overflow-hidden rounded-[2rem] bg-primary/5 shadow-[0_18px_45px_rgba(101,20,154,0.12)] md:block lg:-left-4 lg:h-44 lg:w-44"> <div className="pointer-events-none absolute -left-2 top-0 hidden h-36 w-36 overflow-hidden rounded-[2rem] bg-primary/5 shadow-[0_18px_45px_rgba(101,20,154,0.12)] md:block lg:-left-4 lg:h-44 lg:w-44">
<Image <Image
src="/images/nearle-redesign/heading-shopper.webp" src="/images/nearle-redesign/heading-shopper.webp"
@@ -69,13 +79,20 @@ export function WhyNearle() {
<p className="text-body-lg text-on-surface-variant mt-2.5 max-w-xl mx-auto leading-relaxed"> <p className="text-body-lg text-on-surface-variant mt-2.5 max-w-xl mx-auto leading-relaxed">
Nearle brings the convenience of an app to the stores you already know and trust. Nearle brings the convenience of an app to the stores you already know and trust.
</p> </p>
</div> </motion.div>
{/* Cards Grid */} {/* Cards Grid — fadeIn preserves .premium-card:hover CSS transform */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5 lg:gap-7"> <motion.div
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5 lg:gap-7"
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
>
{CARDS.map((card) => ( {CARDS.map((card) => (
<div <motion.div
key={card.title} key={card.title}
variants={fadeIn}
className="premium-card bg-white rounded-3xl p-5 md:p-6 border border-outline-variant/15 shadow-lg flex flex-col h-full hover:scale-[1.02] transition-transform duration-300" className="premium-card bg-white rounded-3xl p-5 md:p-6 border border-outline-variant/15 shadow-lg flex flex-col h-full hover:scale-[1.02] transition-transform duration-300"
> >
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center border mb-5 shrink-0 ${card.color}`}> <div className={`w-14 h-14 rounded-2xl flex items-center justify-center border mb-5 shrink-0 ${card.color}`}>
@@ -96,11 +113,10 @@ export function WhyNearle() {
className="object-cover" className="object-cover"
/> />
</div> </div>
</div> </motion.div>
))} ))}
</div> </motion.div>
</div> </div>
</section> </section>
); );
} }

View File

@@ -0,0 +1,8 @@
"use client";
import { MotionConfig } from "framer-motion";
import React from "react";
export function MotionProvider({ children }: { children: React.ReactNode }) {
return <MotionConfig reducedMotion="user">{children}</MotionConfig>;
}

View File

@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { PremiumNavbar } from "./PremiumNavbar"; import { PremiumNavbar } from "./PremiumNavbar";
import { PremiumFooter } from "./PremiumFooter"; import { PremiumFooter } from "./PremiumFooter";
import { MotionProvider } from "./MotionProvider";
type PremiumPageShellProps = { type PremiumPageShellProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -9,16 +10,18 @@ type PremiumPageShellProps = {
export function PremiumPageShell({ children, className = "" }: PremiumPageShellProps) { export function PremiumPageShell({ children, className = "" }: PremiumPageShellProps) {
return ( return (
<div className={`min-h-screen bg-white font-body text-[#16001D] antialiased flex flex-col ${className}`}> <MotionProvider>
{/* Header/Navbar */} <div className={`min-h-screen bg-white font-body text-[#16001D] antialiased flex flex-col ${className}`}>
<PremiumNavbar /> {/* Header/Navbar */}
<PremiumNavbar />
{/* Page Content */} {/* Page Content */}
<main className="w-full overflow-x-hidden bg-white"> <main className="w-full overflow-x-hidden bg-white">
{children} {children}
</main> </main>
<PremiumFooter /> <PremiumFooter />
</div> </div>
</MotionProvider>
); );
} }

View File

@@ -0,0 +1,56 @@
import type { Variants } from "framer-motion";
export const fadeUp: Variants = {
hidden: { opacity: 0, y: 24 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] },
},
};
// Use for elements with CSS transform-based hover effects (e.g. .premium-card:hover)
// to avoid inline-style override of CSS hover transforms.
export const fadeIn: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: 0.45, ease: "easeOut" },
},
};
export const scaleIn: Variants = {
hidden: { opacity: 0, scale: 0.95 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] },
},
};
export const slideInLeft: Variants = {
hidden: { opacity: 0, x: -40 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
},
};
export const slideInRight: Variants = {
hidden: { opacity: 0, x: 40 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
},
};
export const staggerContainer: Variants = {
hidden: {},
visible: {
transition: { staggerChildren: 0.09 },
},
};
export const viewport = { once: true, margin: "-60px" } as const;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const puppeteer = require('puppeteer-core'); const puppeteer = require('puppeteer-core');
const path = require('path'); const path = require('path');