update SEO

This commit is contained in:
2026-07-01 17:05:32 +05:30
parent 208f6258ce
commit f37e2392f3
45 changed files with 227 additions and 567 deletions

22
package-lock.json generated
View File

@@ -10,11 +10,11 @@
"dependencies": { "dependencies": {
"framer-motion": "^12.42.0", "framer-motion": "^12.42.0",
"jsdom": "^29.1.1", "jsdom": "^29.1.1",
"lucide-react": "^1.22.0",
"next": "16.2.9", "next": "16.2.9",
"puppeteer-core": "^24.43.1", "puppeteer-core": "^24.43.1",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4"
"react-icons": "^5.6.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@@ -5760,6 +5760,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lucide-react": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.22.0.tgz",
"integrity": "sha512-c9o3l0PiNcgOQDW4F31BEYHudE7kgxVt3o30qMl36ZPwTxXlGB4QnLilhERvVM4uh/pl5MDyY1/gzZSYcHDtBg==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -6512,15 +6521,6 @@
"react": "^19.2.4" "react": "^19.2.4"
} }
}, },
"node_modules/react-icons": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz",
"integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@@ -8,14 +8,23 @@
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "eslint"
}, },
"browserslist": [
"last 2 Chrome versions",
"last 2 Edge versions",
"last 2 Firefox versions",
"last 2 Safari versions",
"last 2 iOS versions",
"not dead",
"not op_mini all"
],
"dependencies": { "dependencies": {
"framer-motion": "^12.42.0", "framer-motion": "^12.42.0",
"jsdom": "^29.1.1", "jsdom": "^29.1.1",
"lucide-react": "^1.22.0",
"next": "16.2.9", "next": "16.2.9",
"puppeteer-core": "^24.43.1", "puppeteer-core": "^24.43.1",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4"
"react-icons": "^5.6.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",

BIN
public/about.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/contact-page1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

BIN
public/contact.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
public/contect.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
public/images/home1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
public/images/people1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
public/map1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
public/mobile-image.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
public/nearle-business.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

BIN
public/nearle-people.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
public/vegetable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -38,7 +38,7 @@ const STRATEGY_CARDS = [
descr: "Nearle is essential for a business to grow by leveraging the full potential of its neighbourhood. It is an indispensable tool for consumers to take advantage of what their neighbourhood has to offer.", descr: "Nearle is essential for a business to grow by leveraging the full potential of its neighbourhood. It is an indispensable tool for consumers to take advantage of what their neighbourhood has to offer.",
icon: "help_outline", icon: "help_outline",
iconClass: "bg-[#6330D6]/10 text-[#6330D6] border-[#6330D6]/20", iconClass: "bg-[#6330D6]/10 text-[#6330D6] border-[#6330D6]/20",
image: "/images/app-mockup.png", image: "/images/app-mockup.webp",
imageAlt: "Nearle app on a smartphone", imageAlt: "Nearle app on a smartphone",
imgFit: "object-contain object-bottom" imgFit: "object-contain object-bottom"
} }

View File

@@ -199,7 +199,7 @@ export default function BrandsPage() {
<div className="w-full"> <div className="w-full">
<HeroVisualCollage <HeroVisualCollage
centerImage="/nearlenewlogo.png" centerImage="/nearlenewlogo.png"
leftImage="/images/cat-daily-essentials.png" leftImage="/images/cat-daily-essentials.webp"
rightImage="/images/nearle-merchant/local-map.webp" rightImage="/images/nearle-merchant/local-map.webp"
glassCards={collageCards} glassCards={collageCards}
isPhoneCenter={false} isPhoneCenter={false}

View File

@@ -125,36 +125,6 @@ const TIMELINE = [
}, },
]; ];
const EARNINGS = [
{
profile: "Part-Time (23 hrs/day)",
deliveries: "812 deliveries",
daily: "₹450 ₹700",
monthly: "₹9,000 ₹14,000",
icon: "hourglass_empty",
color: "border-[#6330D6]/15 bg-[#FAF7FD]",
accent: "text-[#6330D6]",
},
{
profile: "Full-Time (68 hrs/day)",
deliveries: "2232 deliveries",
daily: "₹1,100 ₹1,600",
monthly: "₹22,000 ₹32,000",
icon: "bolt",
color: "border-[#006D37]/15 bg-[#F0FAF4]",
accent: "text-[#006D37]",
},
{
profile: "Peak Hours + Bonuses",
deliveries: "Varies",
daily: "+ ₹200 ₹400",
monthly: "Up to ₹5,000 extra",
icon: "trending_up",
color: "border-amber-500/15 bg-amber-50",
accent: "text-amber-600",
},
];
const FAQ_ITEMS = [ const FAQ_ITEMS = [
{ {
question: "Do I need to own a vehicle to join?", question: "Do I need to own a vehicle to join?",
@@ -237,7 +207,7 @@ export default function DeliveryPartnersPage() {
<div className="w-full"> <div className="w-full">
<HeroVisualCollage <HeroVisualCollage
centerImage="/nearlenewlogo.png" centerImage="/nearlenewlogo.png"
leftImage="/nearle people.png" leftImage="/nearle-people.webp"
rightImage="/images/nearle-merchant/delivery-support.webp" rightImage="/images/nearle-merchant/delivery-support.webp"
glassCards={collageCards} glassCards={collageCards}
isPhoneCenter={false} isPhoneCenter={false}

View File

@@ -1,5 +1,13 @@
@import "tailwindcss"; @import "tailwindcss";
@font-face {
font-family: "Material Symbols Outlined";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/material-symbols-outlined.woff2") format("woff2");
}
/* ─── Design Tokens (Material Design 3 inspired) ─── */ /* ─── Design Tokens (Material Design 3 inspired) ─── */
:root { :root {
--surface: #FAF8FC; --surface: #FAF8FC;
@@ -124,8 +132,8 @@
--spacing-container-max: 1440px; --spacing-container-max: 1440px;
/* Fonts */ /* Fonts */
--font-display: "Jost", sans-serif; --font-display: var(--font-jost), sans-serif;
--font-body: "Jost", sans-serif; --font-body: var(--font-jost), sans-serif;
/* Border radius */ /* Border radius */
@@ -238,6 +246,18 @@ body {
/* Material Symbols config */ /* Material Symbols config */
.material-symbols-outlined { .material-symbols-outlined {
font-family: "Material Symbols Outlined";
font-weight: normal;
font-style: normal;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: "liga";
-webkit-font-smoothing: antialiased;
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24; font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24;
} }
@@ -618,373 +638,6 @@ body {
max-width: 320px; max-width: 320px;
} }
.footer-social-link {
width: 58px;
height: 58px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
transition: all 0.3s ease;
}
.footer-social-link:hover {
background: var(--brand-accent);
color: var(--brand-dark);
}
.footer-column-title {
font-size: 17px;
font-weight: 700;
color: #E5E7EB;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 24px;
}
.footer-links-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.footer-link {
font-size: 17px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
}
.footer-link:hover {
color: var(--brand-accent);
transform: translateX(6px);
}
/* Contact Form Card styling */
.footer-brand {
min-width: 0;
}
.footer-company {
min-width: 0;
}
.footer-partnerships {
min-width: 0;
}
.footer-legal {
margin-top: 12px;
}
.footer-contact-wrap {
min-width: 0;
width: 100%;
}
.footer-contact-card {
width: 100%;
max-width: 540px;
min-width: 0;
box-sizing: border-box;
border-radius: 32px;
}
@media (max-width: 640px) {
.footer-contact-card {
max-width: 100%;
border-radius: 24px;
}
}
.footer-contact-card input,
.footer-contact-card textarea,
.footer-contact-card button {
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
}
.footer-contact-title {
font-size: clamp(30px, 2.2vw, 34px);
font-weight: 800;
line-height: 1.15;
}
.footer-contact-subtitle {
font-size: 16px;
color: var(--on-surface-variant);
margin-top: 8px;
margin-bottom: 24px;
white-space: normal;
word-wrap: break-word;
}
.footer-form-row {
/* Layout handled via Tailwind grid classes in Footer.tsx */
}
.footer-input {
box-sizing: border-box;
width: 100%;
background: var(--surface);
border: 1px solid var(--outline-variant);
color: var(--on-surface);
border-radius: 14px;
padding-inline: 16px;
font-size: 16px;
outline: none;
transition: all 0.2s ease;
}
.footer-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(77, 4, 106, 0.2);
}
.footer-input.has-error {
border-color: var(--error);
}
.footer-input.has-error:focus {
box-shadow: 0 0 0 2px rgba(186, 26, 26, 0.2);
}
.footer-input::placeholder {
font-size: 16px;
color: rgba(77, 67, 79, 0.6);
}
.footer-textarea {
box-sizing: border-box;
width: 100%;
background: var(--surface);
border: 1px solid var(--outline-variant);
color: var(--on-surface);
border-radius: 14px;
padding: 16px;
font-size: 16px;
outline: none;
resize: none;
transition: all 0.2s ease;
}
.footer-textarea:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(77, 4, 106, 0.2);
}
.footer-textarea.has-error {
border-color: var(--error);
}
.footer-textarea.has-error:focus {
box-shadow: 0 0 0 2px rgba(186, 26, 26, 0.2);
}
.footer-textarea::placeholder {
font-size: 16px;
color: rgba(77, 67, 79, 0.6);
}
.footer-error-text {
font-size: 15px;
color: var(--error);
font-weight: 700;
margin-top: 4px;
padding-left: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.footer-submit-btn {
width: 100%;
background: var(--primary);
color: var(--on-primary);
font-weight: 700;
font-size: 18px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
cursor: pointer;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
border: none;
}
.footer-submit-btn:hover:not(:disabled) {
background: var(--primary-container);
}
.footer-submit-btn:disabled {
background: rgba(77, 4, 106, 0.5);
cursor: not-allowed;
}
.footer-submit-btn:active:not(:disabled) {
transform: translateY(1px);
}
.footer-bottom-text {
font-size: 15px;
color: rgba(255, 255, 255, 0.4);
}
/* Helpdesk override for sizing constraints */
.helpdesk-subtitle {
font-size: 15px;
font-weight: 700;
color: rgba(18, 2, 23, 0.8);
margin-top: 2px;
}
.helpdesk-btn {
display: flex;
align-items: center;
gap: 12px;
background-color: var(--brand-dark);
color: var(--brand-accent);
font-weight: 900;
padding: 14px 32px;
border-radius: 9999px;
font-size: 16px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
transition: all 0.2s ease;
}
.helpdesk-btn:hover {
background-color: rgba(18, 2, 23, 0.9);
}
@media (max-width: 1200px) {
.footer-grid,
.footer-grid-contact-page {
grid-template-columns: repeat(3, 1fr);
gap: 40px;
}
.footer-brand-col {
grid-column: 1 / -1;
}
.footer-contact-card-wrapper {
grid-column: 1 / -1;
display: flex;
justify-content: center;
width: 100%;
}
}
@media (max-width: 768px) {
.footer-grid {
grid-template-columns: 1fr;
grid-template-areas:
"brand"
"company"
"partnerships"
"legal"
"form";
gap: 32px;
}
.footer-grid-contact-page {
grid-template-columns: 1fr;
grid-template-areas:
"brand"
"company"
"partnerships"
"legal";
gap: 32px;
}
}
/* ─── Footer Vertical Spacing & Height Alignment ─── */
.footer {
--helpdesk-height: 150px;
--helpdesk-overlap: 42px;
position: relative;
height: auto !important;
min-height: 0 !important;
padding: 0 !important;
overflow: visible !important;
}
.footer-helpdesk-wrap {
box-sizing: border-box;
position: relative;
z-index: 5;
width: 100%;
max-width: 1720px;
margin: 0 auto;
padding-inline: clamp(24px, 5%, 76px);
margin-top: -42px;
margin-bottom: 48px;
}
.footer-main {
padding-top: 0 !important;
padding-bottom: 56px !important;
}
.footer-divider {
width: 100%;
margin: 0 auto !important;
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.footer-bottom {
width: 100%;
margin: 0 auto;
padding: 28px 0 34px !important;
}
.footer-spacer,
.footer-empty-space {
display: none !important;
}
@media (max-width: 768px) {
.footer-helpdesk-wrap {
margin-top: 0;
margin-bottom: 32px;
}
}
/* ─── Stable Subpage Hero Height to Prevent Layout Shift ─── */
.hero-section {
position: relative !important;
min-height: clamp(680px, 48vw, 820px) !important;
height: clamp(680px, 48vw, 820px) !important;
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
overflow: hidden !important;
}
.hero-section > div.relative {
width: 100% !important;
}
.hero-rotator-text {
display: inline-flex !important;
align-items: center;
justify-content: center;
min-height: 2.4em;
vertical-align: middle;
}
/* ─── Experience Bento Mosaic Grid Redesign ─── */ /* ─── Experience Bento Mosaic Grid Redesign ─── */
.experience-grid { .experience-grid {
display: grid; display: grid;
@@ -1106,33 +759,6 @@ body {
} }
} }
/* ─── Hero Section Background & Overlay Styling ─── */
.hero-background {
position: absolute;
inset: 0;
z-index: 0;
overflow: hidden;
}
.hero-background img {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
object-position: center right !important;
}
.hero-background::after {
content: "";
position: absolute;
inset: 0;
background: rgba(18, 4, 28, 0.48);
}
.hero-content {
position: relative;
z-index: 1;
}
/* ─── Shared Premium Framed Layout Classes ─── */ /* ─── Shared Premium Framed Layout Classes ─── */
.page-canvas { .page-canvas {
background-color: #E8D8ED; background-color: #E8D8ED;

View File

@@ -1,11 +1,17 @@
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Jost } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { MotionProvider } from "@/components/layout/MotionProvider"; import { MotionProvider } from "@/components/layout/MotionProvider";
import { PremiumNavbar } from "@/components/layout/PremiumNavbar"; import { PremiumNavbar } from "@/components/layout/PremiumNavbar";
import { PremiumFooter } from "@/components/layout/PremiumFooter"; import { PremiumFooter } from "@/components/layout/PremiumFooter";
const jost = { variable: "font-display" }; const jost = Jost({
const jostBody = { variable: "font-body" }; subsets: ["latin"],
weight: ["400", "600", "700", "800"],
variable: "--font-jost",
display: "swap",
preload: true,
});
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL("https://nearle.in"), metadataBase: new URL("https://nearle.in"),
@@ -72,23 +78,10 @@ export default function RootLayout({
return ( return (
<html <html
lang="en" lang="en"
className={`${jost.variable} ${jostBody.variable}`} className={jost.variable}
data-scroll-behavior="smooth" data-scroll-behavior="smooth"
suppressHydrationWarning suppressHydrationWarning
> >
<head>
{/* Material Symbols Outlined */}
{/* eslint-disable-next-line @next/next/no-page-custom-font */}
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet"
/>
{/* eslint-disable-next-line @next/next/no-page-custom-font */}
<link
href="https://fonts.googleapis.com/css2?family=Jost:wght@400;600;700;800&display=swap"
rel="stylesheet"
/>
</head>
<body className="bg-[#E8DCF2] text-on-surface font-body antialiased" suppressHydrationWarning> <body className="bg-[#E8DCF2] text-on-surface font-body antialiased" suppressHydrationWarning>
<MotionProvider> <MotionProvider>
<div className="min-h-screen bg-white font-body text-[#16001D] antialiased flex flex-col"> <div className="min-h-screen bg-white font-body text-[#16001D] antialiased flex flex-col">

View File

@@ -211,7 +211,7 @@ export default function MerchantsPage() {
<HeroVisualCollage <HeroVisualCollage
centerImage="/nearlenewlogo.png" centerImage="/nearlenewlogo.png"
leftImage="/images/nearle-merchant/catalogue-ui.webp" leftImage="/images/nearle-merchant/catalogue-ui.webp"
rightImage="/Nearle Business.png" rightImage="/nearle-business.webp"
glassCards={collageCards} glassCards={collageCards}
isPhoneCenter={false} isPhoneCenter={false}
/> />
@@ -452,4 +452,3 @@ export default function MerchantsPage() {
</PremiumPageShell> </PremiumPageShell>
); );
} }

View File

@@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
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 { PeopleHero } from "@/components/heroes/PeopleHero"; import { PeopleHero } from "@/components/heroes/PeopleHero";
@@ -22,7 +21,7 @@ const OFFERINGS = [
descr: "Enjoy the comfort of elegant living. Nearle is India's first hyper-local managed platform bringing all amenities and requirements directly to your fingertips.", descr: "Enjoy the comfort of elegant living. Nearle is India's first hyper-local managed platform bringing all amenities and requirements directly to your fingertips.",
icon: "local_florist", icon: "local_florist",
color: "from-purple-500/10 to-indigo-500/10 text-primary border-purple-100", color: "from-purple-500/10 to-indigo-500/10 text-primary border-purple-100",
image: "/nearle people.png", image: "/nearle-people.webp",
isWide: false isWide: false
}, },
{ {
@@ -31,7 +30,7 @@ const OFFERINGS = [
descr: "Pick hot deals within your neighbourhood. Stay updated on localized sales, discounts, and flash promotions happening today.", descr: "Pick hot deals within your neighbourhood. Stay updated on localized sales, discounts, and flash promotions happening today.",
icon: "local_activity", icon: "local_activity",
color: "from-amber-500/10 to-orange-500/10 text-amber-700 border-amber-100", color: "from-amber-500/10 to-orange-500/10 text-amber-700 border-amber-100",
image: "/images/app-mockup.png", image: "/images/app-mockup.webp",
isWide: false isWide: false
}, },
{ {
@@ -40,7 +39,7 @@ const OFFERINGS = [
descr: "Enjoy the ease of the World Wide Web, fulfilled entirely by trusted local businesses. Avoid traffic lights, parking hassles, and pollution.", descr: "Enjoy the ease of the World Wide Web, fulfilled entirely by trusted local businesses. Avoid traffic lights, parking hassles, and pollution.",
icon: "travel_explore", icon: "travel_explore",
color: "from-green-500/10 to-emerald-500/10 text-secondary border-green-100", color: "from-green-500/10 to-emerald-500/10 text-secondary border-green-100",
image: "/images/slider-courier-mask-purple-v5.png", image: "/images/slider-courier-mask-purple-v5.webp",
isWide: false isWide: false
}, },
{ {
@@ -49,7 +48,7 @@ const OFFERINGS = [
descr: "Enjoy safe, convenient shopping with custom pricing, resident promotions, and bulk order discounts negotiated for your community.", descr: "Enjoy safe, convenient shopping with custom pricing, resident promotions, and bulk order discounts negotiated for your community.",
icon: "shopping_bag", icon: "shopping_bag",
color: "from-rose-500/10 to-pink-500/10 text-rose-700 border-rose-100", color: "from-rose-500/10 to-pink-500/10 text-rose-700 border-rose-100",
image: "/images/cat-pet-essentials.png", image: "/images/cat-pet-essentials.webp",
isWide: false isWide: false
}, },
{ {
@@ -58,7 +57,7 @@ const OFFERINGS = [
descr: "Order everything you need in your neighborhood with a single tap. A fast, organized last-mile delivery network ensures prompt fulfillment.", descr: "Order everything you need in your neighborhood with a single tap. A fast, organized last-mile delivery network ensures prompt fulfillment.",
icon: "touch_app", icon: "touch_app",
color: "from-sky-500/10 to-blue-500/10 text-sky-700 border-sky-100", color: "from-sky-500/10 to-blue-500/10 text-sky-700 border-sky-100",
image: "/about.png", image: "/about.webp",
isWide: true isWide: true
}, },
{ {
@@ -67,7 +66,7 @@ const OFFERINGS = [
descr: "Get your fresh fruits, farm-fresh vegetables, dairy, and grocery items delivered instantly from verified local green grocers.", descr: "Get your fresh fruits, farm-fresh vegetables, dairy, and grocery items delivered instantly from verified local green grocers.",
icon: "eco", icon: "eco",
color: "from-emerald-500/10 to-teal-500/10 text-emerald-700 border-emerald-100", color: "from-emerald-500/10 to-teal-500/10 text-emerald-700 border-emerald-100",
image: "/images/cat-daily-essentials.png", image: "/images/cat-daily-essentials.webp",
isWide: false isWide: false
}, },
{ {
@@ -76,7 +75,7 @@ const OFFERINGS = [
descr: "Choose from the best local restaurants and neighborhood eateries close to your home. Hot food delivered with relevance and speed.", descr: "Choose from the best local restaurants and neighborhood eateries close to your home. Hot food delivered with relevance and speed.",
icon: "restaurant", icon: "restaurant",
color: "from-orange-500/10 to-red-500/10 text-orange-700 border-orange-100", color: "from-orange-500/10 to-red-500/10 text-orange-700 border-orange-100",
image: "/images/cat-hot-food.png", image: "/images/cat-hot-food.webp",
isWide: false isWide: false
}, },
{ {
@@ -85,7 +84,7 @@ const OFFERINGS = [
descr: "Access custom-tailored local neighborhood services designed to integrate convenience directly into residential society living.", descr: "Access custom-tailored local neighborhood services designed to integrate convenience directly into residential society living.",
icon: "stars", icon: "stars",
color: "from-violet-500/10 to-purple-500/10 text-violet-700 border-violet-100", color: "from-violet-500/10 to-purple-500/10 text-violet-700 border-violet-100",
image: "/images/cat-health-wellness.png", image: "/images/cat-health-wellness.webp",
isWide: false isWide: false
}, },
{ {
@@ -94,7 +93,7 @@ const OFFERINGS = [
descr: "Book certified local service providers at your convenience. From handymen to laundry, pet care to pharmacy delivery, all in one place.", descr: "Book certified local service providers at your convenience. From handymen to laundry, pet care to pharmacy delivery, all in one place.",
icon: "construction", icon: "construction",
color: "from-cyan-500/10 to-blue-500/10 text-cyan-700 border-cyan-100", color: "from-cyan-500/10 to-blue-500/10 text-cyan-700 border-cyan-100",
image: "/images/cat-home-services.png", image: "/images/cat-home-services.webp",
isWide: false isWide: false
} }
]; ];
@@ -174,7 +173,7 @@ export default function PeoplePage() {
src={offering.image} src={offering.image}
alt={offering.title} alt={offering.title}
fill fill
sizes={offering.isWide ? "(max-width: 1023px) 100vw, 66vw" : "(max-width: 1023px) 100vw, 33vw"} sizes={offering.isWide ? "(max-width: 639px) calc(100vw - 48px), (max-width: 1023px) calc(100vw - 96px), 860px" : "(max-width: 639px) calc(100vw - 48px), (max-width: 1023px) calc(100vw - 96px), 420px"}
className="experience-card__image" className="experience-card__image"
/> />
<div className="experience-card__overlay" /> <div className="experience-card__overlay" />

View File

@@ -1,11 +1,9 @@
"use client"; "use client";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow"; import { FeatureStatsRow } from "@/components/ui/FeatureStatsRow";
import { fadeUp, fadeUpStrong, staggerContainer } from "@/lib/animations"; import { staggerContainer } from "@/lib/animations";
const HERO_STATS = [ const HERO_STATS = [
{ title: "2km Coverage", icon: "explore", desc: "Hyperlocal radius" }, { title: "2km Coverage", icon: "explore", desc: "Hyperlocal radius" },
@@ -40,23 +38,24 @@ export function AboutHero() {
{/* Background image — scooter anchored to the right */} {/* Background image — scooter anchored to the right */}
<Image <Image
src="/about.png" src="/about.webp"
alt="" alt=""
fill fill
sizes="100vw" sizes="100vw"
className="absolute inset-0 z-0 h-full w-full object-cover object-[68%_center] md:object-[64%_center] xl:object-center" className="absolute inset-0 z-0 h-full w-full object-cover object-[68%_center] md:object-[64%_center] xl:object-center"
priority priority
fetchPriority="high"
aria-hidden="true" aria-hidden="true"
/> />
{/* Gradient overlay — dark-purple toned to preserve the purple scene and protect white text */} {/* Gradient overlay */}
<div className="absolute inset-0 z-[1] bg-[linear-gradient(180deg,rgba(23,5,31,0.42)_0%,rgba(23,5,31,0.26)_40%,rgba(23,5,31,0.08)_72%,rgba(23,5,31,0)_100%)] md:bg-[radial-gradient(ellipse_58%_62%_at_50%_42%,rgba(23,5,31,0.38)_0%,rgba(23,5,31,0.16)_50%,rgba(23,5,31,0.04)_75%,rgba(23,5,31,0)_92%)]" /> <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%)]" />
{/* Content */} {/* Content */}
<div className="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:px-12 md:py-20 lg:px-16 lg:py-24 max-w-[1600px] mx-auto"> <div className="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:px-12 md:py-20 lg:px-16 lg:py-24 max-w-[1600px] mx-auto">
<motion.div <motion.div
className="flex flex-col items-center text-center w-full max-w-[860px] relative z-20 bg-[radial-gradient(ellipse_80%_75%_at_50%_45%,rgba(0,0,0,0.18)_0%,rgba(0,0,0,0.06)_55%,rgba(0,0,0,0)_82%)] px-10 py-10 md:px-14 md:py-12" className="flex flex-col items-center text-center w-full max-w-[860px] relative z-20 px-6 py-10 md:px-14 md:py-12"
variants={staggerContainer} variants={staggerContainer}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
@@ -64,7 +63,7 @@ export function AboutHero() {
{/* Badge */} {/* Badge */}
<motion.span <motion.span
variants={badgeVariant} variants={badgeVariant}
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-black tracking-widest bg-white/15 backdrop-blur-[8px] border border-white/20 text-white 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/20 backdrop-blur-[8px] border border-white/20 text-white shadow-md uppercase mb-6 w-fit"
> >
About Nearle About Nearle
</motion.span> </motion.span>
@@ -72,7 +71,7 @@ export function AboutHero() {
{/* Heading */} {/* Heading */}
<motion.h1 <motion.h1
variants={titleVariant} variants={titleVariant}
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_2px_10px_rgba(0,0,0,0.22)]" 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)]"
> >
Built for Every Built for Every
<br /> <br />
@@ -82,30 +81,10 @@ export function AboutHero() {
{/* Description */} {/* Description */}
<motion.p <motion.p
variants={descVariant} variants={descVariant}
className="mt-6 max-w-[560px] text-base font-semibold leading-[1.6] text-white/[0.96] md:text-lg [text-shadow:0_1px_6px_rgba(0,0,0,0.28)]" className="mt-6 max-w-[560px] text-lg sm:text-xl md:text-xl lg:text-2xl font-medium leading-[1.6] text-white/95 [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
> >
Nearle is India&apos;s first hyper-local managed &ldquo;Neighbourhood living platform&rdquo; which brings all the amenities and requirements of residents to their fingertips. Nearle is India&apos;s first hyper-local managed &ldquo;Neighbourhood living platform&rdquo; which brings all the amenities and requirements of residents to their fingertips.
</motion.p> </motion.p>
{/* CTA Buttons */}
<motion.div
variants={descVariant}
className="mt-8 flex flex-wrap justify-center gap-3"
>
<Link
href="/businesses"
className="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full bg-white px-7 py-3.5 text-base font-black text-[#6330D6] shadow-sm transition-all duration-200 hover:bg-white/90 hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0 active:scale-95"
>
Partner with Nearle
<MaterialIcon icon="arrow_forward" size={18} />
</Link>
<Link
href="/people"
className="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full border border-white/30 bg-white/10 px-7 py-3.5 text-base font-black text-white backdrop-blur-[6px] transition-all duration-200 hover:bg-white/18 hover:border-white/45 hover:-translate-y-0.5 active:translate-y-0 active:scale-95"
>
Explore Nearle
</Link>
</motion.div>
</motion.div> </motion.div>
</div> </div>

View File

@@ -32,35 +32,36 @@ export function BusinessHero() {
<section className="w-full flex flex-col select-none bg-white"> <section className="w-full flex flex-col select-none bg-white">
<div className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC]"> <div className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC]">
<Image <Image
src="/images/business1.png" src="/images/business1.webp"
alt="" alt=""
fill fill
sizes="100vw" sizes="100vw"
className="absolute inset-0 z-0 h-full w-full object-cover object-[62%_center] md:object-[58%_center] xl:object-center" className="absolute inset-0 z-0 h-full w-full object-cover object-[62%_center] md:object-[58%_center] xl:object-center"
priority priority
fetchPriority="high"
aria-hidden="true" aria-hidden="true"
/> />
<div className="absolute inset-0 z-[1] bg-[linear-gradient(180deg,rgba(0,0,0,0.14)_0%,rgba(0,0,0,0.05)_40%,rgba(0,0,0,0)_70%)] md:bg-[radial-gradient(ellipse_55%_55%_at_50%_38%,rgba(0,0,0,0.13)_0%,rgba(0,0,0,0.04)_55%,rgba(0,0,0,0)_80%)]" /> <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%)]" />
<div className="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:px-12 md:py-20 lg:px-16 lg:py-24 xl:px-8 max-w-[1600px] mx-auto"> <div className="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:px-12 md:py-20 lg:px-16 lg:py-24 xl:px-8 max-w-[1600px] mx-auto">
{/* Left Column: animated with unique stagger */} {/* Left Column: animated with unique stagger */}
<motion.div <motion.div
className="flex flex-col items-center text-center w-full max-w-[860px] relative z-20 bg-[radial-gradient(ellipse_80%_75%_at_50%_45%,rgba(0,0,0,0.18)_0%,rgba(0,0,0,0.06)_55%,rgba(0,0,0,0)_82%)] px-10 py-10 md:px-14 md:py-12" className="flex flex-col items-center text-center w-full max-w-[860px] relative z-20 px-6 py-10 md:px-14 md:py-12"
variants={staggerContainer} variants={staggerContainer}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
> >
<motion.span <motion.span
variants={badgeVariant} variants={badgeVariant}
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" 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 Business Nearle for Business
</motion.span> </motion.span>
<motion.h1 <motion.h1
variants={titleVariant} variants={titleVariant}
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_2px_10px_rgba(0,0,0,0.22)]" 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 Local Business, Grow Your Local Business,
<br /> <br />
@@ -69,7 +70,7 @@ export function BusinessHero() {
<motion.p <motion.p
variants={descVariant} variants={descVariant}
className="max-w-[780px] text-lg sm:text-xl md:text-xl text-white/[0.96] font-semibold mt-6 leading-[1.6] [text-shadow:0_1px_6px_rgba(0,0,0,0.28)]" className="max-w-[780px] text-lg sm:text-xl md:text-xl text-white/95 font-medium mt-6 leading-[1.6] [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
> >
Get discovered by nearby customers, manage orders, and build repeat business from one simple platform. Get discovered by nearby customers, manage orders, and build repeat business from one simple platform.
</motion.p> </motion.p>

View File

@@ -4,7 +4,7 @@ import Image from "next/image";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { ContactForm } from "@/components/contact/ContactForm"; import { ContactForm } from "@/components/contact/ContactForm";
import { fadeUp, fadeUpStrong, slideInRight, staggerContainer, viewport } from "@/lib/animations"; import { fadeUp, fadeUpStrong, staggerContainer } from "@/lib/animations";
const CONTACT_INFO = [ const CONTACT_INFO = [
{ {
@@ -55,12 +55,13 @@ export function ContactHero() {
return ( return (
<section className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC] min-h-[560px] md:min-h-[85vh] px-4 py-10 md:px-6 md:py-12 lg:px-8 lg:py-14 xl:px-10 xl:py-16 2xl:px-12 2xl:py-20"> <section className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC] min-h-[560px] md:min-h-[85vh] px-4 py-10 md:px-6 md:py-12 lg:px-8 lg:py-14 xl:px-10 xl:py-16 2xl:px-12 2xl:py-20">
<Image <Image
src="/contact page1.png" src="/contact-page1.webp"
alt="" alt=""
fill fill
sizes="100vw" sizes="100vw"
className="absolute inset-0 z-0 h-full w-full object-cover object-[62%_center] md:object-center" className="absolute inset-0 z-0 h-full w-full object-cover object-[62%_center] md:object-center"
priority priority
fetchPriority="high"
aria-hidden="true" aria-hidden="true"
/> />
<div className="absolute inset-0 z-[1] bg-[linear-gradient(180deg,rgba(250,248,252,0.62)_0%,rgba(250,248,252,0.38)_48%,rgba(250,248,252,0.10)_100%)] md:bg-[linear-gradient(90deg,rgba(250,248,252,0.72)_0%,rgba(250,248,252,0.48)_40%,rgba(250,248,252,0.12)_68%,rgba(250,248,252,0)_92%)]" /> <div className="absolute inset-0 z-[1] bg-[linear-gradient(180deg,rgba(250,248,252,0.62)_0%,rgba(250,248,252,0.38)_48%,rgba(250,248,252,0.10)_100%)] md:bg-[linear-gradient(90deg,rgba(250,248,252,0.72)_0%,rgba(250,248,252,0.48)_40%,rgba(250,248,252,0.12)_68%,rgba(250,248,252,0)_92%)]" />

View File

@@ -26,34 +26,35 @@ export function PeopleHero() {
<section className="w-full flex flex-col select-none bg-white"> <section className="w-full flex flex-col select-none bg-white">
<div className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC]"> <div className="relative overflow-hidden border-b border-[#6330D6]/10 bg-[#FAF8FC]">
<Image <Image
src="/images/people1.png" src="/images/people1.webp"
alt="" alt=""
fill fill
sizes="100vw" sizes="100vw"
className="absolute inset-0 z-0 h-full w-full object-cover object-[68%_center] md:object-[62%_center] xl:object-center" className="absolute inset-0 z-0 h-full w-full object-cover object-[68%_center] md:object-[62%_center] xl:object-center"
priority priority
fetchPriority="high"
aria-hidden="true" aria-hidden="true"
/> />
<div className="absolute inset-0 z-[1] bg-[linear-gradient(180deg,rgba(0,0,0,0.14)_0%,rgba(0,0,0,0.05)_40%,rgba(0,0,0,0)_70%)] md:bg-[radial-gradient(ellipse_55%_55%_at_50%_38%,rgba(0,0,0,0.13)_0%,rgba(0,0,0,0.04)_55%,rgba(0,0,0,0)_80%)]" /> <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%)]" />
<div className="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:px-12 md:py-20 lg:px-16 lg:py-24 max-w-[1600px] mx-auto"> <div className="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:px-12 md:py-20 lg:px-16 lg:py-24 max-w-[1600px] mx-auto">
<motion.div <motion.div
className="flex flex-col items-center text-center w-full max-w-[860px] relative z-20 bg-[radial-gradient(ellipse_80%_75%_at_50%_45%,rgba(0,0,0,0.18)_0%,rgba(0,0,0,0.06)_55%,rgba(0,0,0,0)_82%)] px-10 py-10 md:px-14 md:py-12" className="flex flex-col items-center text-center w-full max-w-[860px] relative z-20 px-6 py-10 md:px-14 md:py-12"
variants={staggerFast} variants={staggerFast}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
> >
<motion.span <motion.span
variants={badgeVariant} variants={badgeVariant}
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" 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 People Nearle for People
</motion.span> </motion.span>
<motion.h1 <motion.h1
variants={titleVariant} variants={titleVariant}
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_2px_10px_rgba(0,0,0,0.22)]" 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)]"
> >
Your Neighbourhood, Your Neighbourhood,
<br /> <br />
@@ -62,7 +63,7 @@ export function PeopleHero() {
<motion.p <motion.p
variants={descVariant} variants={descVariant}
className="text-lg sm:text-xl md:text-xl lg:text-2xl text-white/[0.96] font-semibold mt-6 leading-[1.6] [text-shadow:0_1px_6px_rgba(0,0,0,0.28)]" className="text-lg sm:text-xl md:text-xl lg:text-2xl text-white/95 font-medium mt-6 leading-[1.6] [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
> >
Discover verified local stores, save your favorite merchants, and order everyday items with superfast local fulfillment. Discover verified local stores, save your favorite merchants, and order everyday items with superfast local fulfillment.
</motion.p> </motion.p>

View File

@@ -65,21 +65,22 @@ export function Hero() {
transition={{ duration: 28, ease: "linear", repeat: Infinity, repeatType: "reverse" }} transition={{ duration: 28, ease: "linear", repeat: Infinity, repeatType: "reverse" }}
> >
<Image <Image
src="/images/home1.png" 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-[62%_center] md:object-[58%_center] xl:object-center"
priority priority
fetchPriority="high"
aria-hidden="true" aria-hidden="true"
/> />
</motion.div> </motion.div>
<div className="absolute inset-0 z-[1] bg-[linear-gradient(180deg,rgba(0,0,0,0.14)_0%,rgba(0,0,0,0.05)_40%,rgba(0,0,0,0)_70%)] md:bg-[radial-gradient(ellipse_55%_55%_at_50%_38%,rgba(0,0,0,0.13)_0%,rgba(0,0,0,0.04)_55%,rgba(0,0,0,0)_80%)]" /> <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%)]" />
<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 <motion.div
className="relative z-20 flex w-full max-w-[860px] flex-col items-center text-center bg-[radial-gradient(ellipse_80%_75%_at_50%_45%,rgba(0,0,0,0.18)_0%,rgba(0,0,0,0.06)_55%,rgba(0,0,0,0)_82%)] px-10 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"
variants={heroStagger} variants={heroStagger}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
@@ -87,13 +88,13 @@ export function Hero() {
{/* Badge */} {/* Badge */}
<motion.span <motion.span
variants={badgeVariant} variants={badgeVariant}
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-black tracking-widest bg-[#6D28D9]/10 text-[#6D28D9] uppercase mb-6 w-fit" 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 mask reveal */}
<motion.div 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_2px_10px_rgba(0,0,0,0.22)]"> <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)]">
<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.
@@ -104,12 +105,12 @@ export function Hero() {
Everything <span className="text-[#A855F7]">Near.</span> Everything <span className="text-[#A855F7]">Near.</span>
</motion.div> </motion.div>
</div> </div>
</motion.div> </motion.h1>
{/* Description */} {/* Description */}
<motion.p <motion.p
variants={descVariant} variants={descVariant}
className="max-w-[520px] text-lg md:text-xl text-white/[0.96] leading-[1.6] font-semibold mt-6 [text-shadow:0_1px_6px_rgba(0,0,0,0.28)]" 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>

View File

@@ -8,7 +8,6 @@ import {
scaleIn, scaleIn,
staggerContainer, staggerContainer,
slideInLeft, slideInLeft,
slideInRight,
viewport, viewport,
} from "@/lib/animations"; } from "@/lib/animations";

View File

@@ -115,10 +115,10 @@ export function HyperlocalFeatures() {
<div className="absolute left-[30%] right-[4%] top-[49%] z-40 rounded-[1.35rem] border border-white/80 bg-white/88 p-2.5 shadow-[0_24px_70px_rgba(23,0,31,0.16)] backdrop-blur-xl md:left-[45%] md:right-[5%] md:top-[52%] md:p-4"> <div className="absolute left-[30%] right-[4%] top-[49%] z-40 rounded-[1.35rem] border border-white/80 bg-white/88 p-2.5 shadow-[0_24px_70px_rgba(23,0,31,0.16)] backdrop-blur-xl md:left-[45%] md:right-[5%] md:top-[52%] md:p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-2xl bg-brand-accent/40 ring-1 ring-outline-variant/20 md:h-20 md:w-20"> <div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-2xl bg-brand-accent/40 ring-1 ring-outline-variant/20 md:h-20 md:w-20">
<Image src="/images/cat-daily-essentials.png" alt="Sri Krishna Groceries storefront" fill sizes="80px" className="object-cover" /> <Image src="/images/cat-daily-essentials.webp" alt="Sri Krishna Groceries storefront" fill sizes="80px" className="object-cover" />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h5 className="truncate text-sm font-black text-brand-dark md:text-base">Sri Krishna Groceries</h5> <div className="truncate text-sm font-black text-brand-dark md:text-base">Sri Krishna Groceries</div>
<p className="mt-0.5 truncate text-xs font-medium text-on-surface-variant md:text-sm">Fresh Harvest & Daily Needs</p> <p className="mt-0.5 truncate text-xs font-medium text-on-surface-variant md:text-sm">Fresh Harvest & Daily Needs</p>
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs md:text-sm"> <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs md:text-sm">
<span className="inline-flex items-center gap-1 font-bold text-on-surface-variant"> <span className="inline-flex items-center gap-1 font-bold text-on-surface-variant">
@@ -159,7 +159,7 @@ export function HyperlocalFeatures() {
Community & Trust Community & Trust
</motion.span> </motion.span>
<motion.h3 <motion.h2
className="text-4xl md:text-5xl lg:text-[44px] font-display font-black text-brand-dark mt-4 mb-6 leading-tight tracking-tight" 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} variants={fadeUp}
initial="hidden" initial="hidden"
@@ -168,7 +168,7 @@ export function HyperlocalFeatures() {
> >
Built Around Your<br /> Built Around Your<br />
<span className="text-primary font-black">Neighbourhood</span> <span className="text-primary font-black">Neighbourhood</span>
</motion.h3> </motion.h2>
<motion.p <motion.p
className="text-body-lg text-on-surface-variant mb-10 max-w-lg leading-relaxed" className="text-body-lg text-on-surface-variant mb-10 max-w-lg leading-relaxed"
@@ -204,9 +204,9 @@ export function HyperlocalFeatures() {
</div> </div>
<div> <div>
<h4 className="text-lg md:text-xl font-black text-brand-dark group-hover:text-primary transition-colors"> <h3 className="text-lg md:text-xl font-black text-brand-dark group-hover:text-primary transition-colors">
{item.title} {item.title}
</h4> </h3>
<p className="text-sm md:text-base text-on-surface-variant mt-1 leading-relaxed"> <p className="text-sm md:text-base text-on-surface-variant mt-1 leading-relaxed">
{item.desc} {item.desc}
</p> </p>

View File

@@ -11,7 +11,7 @@ const MotionLink = motion.create(Link);
const CATEGORIES = [ const CATEGORIES = [
{ {
name: "Groceries & Daily Essentials", name: "Groceries & Daily Essentials",
image: "/images/cat-daily-essentials.png", image: "/images/cat-daily-essentials.webp",
tag: "Local Store", tag: "Local Store",
tagStyle: "bg-secondary text-white", tagStyle: "bg-secondary text-white",
desc: "Fresh groceries, dairy, and household essentials from nearby shops.", desc: "Fresh groceries, dairy, and household essentials from nearby shops.",
@@ -19,7 +19,7 @@ const CATEGORIES = [
}, },
{ {
name: "Wellness", name: "Wellness",
image: "/images/cat-health-wellness.png", image: "/images/cat-health-wellness.webp",
tag: "Available Near You", tag: "Available Near You",
tagStyle: "bg-primary text-white", tagStyle: "bg-primary text-white",
desc: "Prescription medicines and wellness supplies from verified pharmacies.", desc: "Prescription medicines and wellness supplies from verified pharmacies.",
@@ -27,7 +27,7 @@ const CATEGORIES = [
}, },
{ {
name: "Food & Snacks", name: "Food & Snacks",
image: "/images/cat-hot-food.png", image: "/images/cat-hot-food.webp",
tag: "Nearby", tag: "Nearby",
tagStyle: "bg-brand-accent text-brand-dark", tagStyle: "bg-brand-accent text-brand-dark",
desc: "Fresh meals, bakes, and local snacks from neighbourhood kitchens.", desc: "Fresh meals, bakes, and local snacks from neighbourhood kitchens.",
@@ -35,7 +35,7 @@ const CATEGORIES = [
}, },
{ {
name: "Fresh Fruits & Vegetables", name: "Fresh Fruits & Vegetables",
image: "/images/cat-daily-essentials.png", image: "/vegetable.png",
tag: "Local Store", tag: "Local Store",
tagStyle: "bg-secondary text-white", tagStyle: "bg-secondary text-white",
desc: "Farm-fresh fruits and vegetables from trusted local vendors.", desc: "Farm-fresh fruits and vegetables from trusted local vendors.",
@@ -43,7 +43,7 @@ const CATEGORIES = [
}, },
{ {
name: "Home Needs", name: "Home Needs",
image: "/images/cat-pet-essentials.png", image: "/images/cat-pet-essentials.webp",
tag: "Available Near You", tag: "Available Near You",
tagStyle: "bg-orange-500 text-white", tagStyle: "bg-orange-500 text-white",
desc: "Kitchen supplies, household cleaners, and daily utilities.", desc: "Kitchen supplies, household cleaners, and daily utilities.",
@@ -51,7 +51,7 @@ const CATEGORIES = [
}, },
{ {
name: "Local Services", name: "Local Services",
image: "/images/cat-home-services.png", image: "/images/cat-home-services.webp",
tag: "Nearby", tag: "Nearby",
tagStyle: "bg-blue-600 text-white", tagStyle: "bg-blue-600 text-white",
desc: "Verified local handymen, laundry, tailors, and household help.", desc: "Verified local handymen, laundry, tailors, and household help.",
@@ -117,6 +117,7 @@ export function ServiceCategories() {
alt={cat.name} alt={cat.name}
width={400} width={400}
height={300} height={300}
sizes="(max-width: 767px) calc(100vw - 48px), (max-width: 1023px) calc((100vw - 80px) / 2), 405px"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/> />
) : ( ) : (

View File

@@ -134,7 +134,7 @@ export function StoreQRSection() {
> >
<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> <div className="font-display font-black text-brand-dark text-base mt-0.5 leading-none">Store QR</div>
</div> </div>
<div className="relative w-32 h-32 bg-white flex items-center justify-center"> <div className="relative w-32 h-32 bg-white flex items-center justify-center">
@@ -143,6 +143,7 @@ export function StoreQRSection() {
alt="Nearle Store QR Code" alt="Nearle Store QR Code"
width={132} width={132}
height={132} height={132}
sizes="132px"
className="object-contain" className="object-contain"
/> />
</div> </div>
@@ -159,10 +160,11 @@ export function StoreQRSection() {
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" 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.webp"
alt="Nearle mobile app interface" alt="Nearle mobile app interface"
width={360} width={360}
height={760} height={760}
sizes="290px"
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</motion.div> </motion.div>
@@ -199,7 +201,7 @@ export function StoreQRSection() {
> >
<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> <div className="font-display font-black text-brand-dark text-base mt-0.5">Store QR</div>
</div> </div>
<div className="relative w-36 h-36 bg-white flex items-center justify-center"> <div className="relative w-36 h-36 bg-white flex items-center justify-center">
@@ -208,6 +210,7 @@ export function StoreQRSection() {
alt="Nearle Store QR Code" alt="Nearle Store QR Code"
width={144} width={144}
height={144} height={144}
sizes="144px"
className="object-contain" className="object-contain"
/> />
</div> </div>
@@ -246,10 +249,11 @@ export function StoreQRSection() {
viewport={viewport} viewport={viewport}
> >
<Image <Image
src="/mobile image.jpeg" src="/mobile-image.webp"
alt="Nearle mobile app interface" alt="Nearle mobile app interface"
width={360} width={360}
height={760} height={760}
sizes="240px"
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</motion.div> </motion.div>

View File

@@ -3,7 +3,7 @@
import Image from "next/image"; import Image from "next/image";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { GlassInfoCard } from "../ui/GlassInfoCard"; import { GlassInfoCard } from "../ui/GlassInfoCard";
import { scaleIn, staggerContainer, viewport } from "@/lib/animations"; import { scaleIn, viewport } from "@/lib/animations";
import type { Variants } from "framer-motion"; import type { Variants } from "framer-motion";
type GlassCardItem = { type GlassCardItem = {
@@ -134,6 +134,7 @@ export function HeroVisualCollage({
className="object-cover" className="object-cover"
sizes="(max-width: 640px) 180px, 220px" sizes="(max-width: 640px) 180px, 220px"
priority priority
fetchPriority="high"
/> />
</div> </div>
) : ( ) : (
@@ -145,6 +146,7 @@ export function HeroVisualCollage({
height={160} height={160}
className="object-contain max-h-[140px] w-auto motion-safe:animate-[pulse_6s_infinite]" className="object-contain max-h-[140px] w-auto motion-safe:animate-[pulse_6s_infinite]"
priority priority
fetchPriority="high"
/> />
</div> </div>
)} )}

View File

@@ -2,11 +2,9 @@
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import React from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { FaFacebookF, FaInstagram, FaLinkedinIn } from "react-icons/fa";
import { MaterialIcon } from "@/components/ui/MaterialIcon"; import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, fadeIn, staggerContainer, staggerSlow, viewport } from "@/lib/animations"; import { fadeUp, fadeIn, staggerSlow, 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];
@@ -31,6 +29,30 @@ const LEGAL = [
const socialButtonClass = const socialButtonClass =
"flex h-10 w-10 items-center justify-center rounded-full border border-white/10 bg-white/5 text-white transition-[background-color] duration-300 xl:h-[54px] xl:w-[54px]"; "flex h-10 w-10 items-center justify-center rounded-full border border-white/10 bg-white/5 text-white transition-[background-color] duration-300 xl:h-[54px] xl:w-[54px]";
function FacebookIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 320 512" aria-hidden="true" fill="currentColor">
<path d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0C152.13 0 104.2 44.38 104.2 124.72v70.62H22.89V288h81.31v224h100.25V288z" />
</svg>
);
}
function InstagramIcon({ size = 21 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 448 512" aria-hidden="true" fill="currentColor">
<path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9S160.5 370.8 224.1 370.8 339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9S352.3 35.1 316.4 33.4c-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1S3.2 127.5 1.5 163.4c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.5 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2s34.5-58 36.2-93.9c2.1-37 2.1-147.8 0-184.8z" />
</svg>
);
}
function LinkedInIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 448 512" aria-hidden="true" fill="currentColor">
<path d="M100.28 448H7.4V148.9h92.88zM53.79 108.1C24.09 108.1 0 83.5 0 53.8a53.79 53.79 0 0 1 107.58 0c0 29.7-24.1 54.3-53.79 54.3zM447.9 448h-92.68V302.4c0-34.7-.7-79.2-48.29-79.2-48.29 0-55.69 37.7-55.69 76.7V448h-92.78V148.9h89.08v40.8h1.3c12.4-23.5 42.69-48.3 87.88-48.3 94 0 111.28 61.9 111.28 142.3V448z" />
</svg>
);
}
const linkColumnVariant = { const linkColumnVariant = {
hidden: { opacity: 0, y: 20 }, hidden: { opacity: 0, y: 20 },
visible: { visible: {
@@ -128,7 +150,7 @@ export function PremiumFooter() {
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }} transition={{ type: "spring", stiffness: 400, damping: 17 }}
> >
<FaFacebookF size={20} /> <FacebookIcon size={20} />
</motion.a> </motion.a>
<motion.a <motion.a
@@ -141,7 +163,7 @@ export function PremiumFooter() {
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }} transition={{ type: "spring", stiffness: 400, damping: 17 }}
> >
<FaInstagram size={21} /> <InstagramIcon size={21} />
</motion.a> </motion.a>
<motion.a <motion.a
@@ -154,32 +176,32 @@ export function PremiumFooter() {
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }} transition={{ type: "spring", stiffness: 400, damping: 17 }}
> >
<FaLinkedinIn size={20} /> <LinkedInIcon size={20} />
</motion.a> </motion.a>
</div> </div>
</motion.div> </motion.div>
{/* Company links */} {/* Company links */}
<motion.div variants={linkColumnVariant} className="flex flex-col"> <motion.div variants={linkColumnVariant} className="flex flex-col">
<h5 className="mb-5 text-xs font-extrabold uppercase tracking-wider text-white/40 md:text-sm xl:mb-7 xl:text-[17px]"> <div className="mb-5 text-xs font-extrabold uppercase tracking-wider text-white/40 md:text-sm xl:mb-7 xl:text-[17px]">
Company Company
</h5> </div>
<FooterLinkList links={COMPANY} /> <FooterLinkList links={COMPANY} />
</motion.div> </motion.div>
{/* Nearle links */} {/* Nearle links */}
<motion.div variants={linkColumnVariant} className="flex flex-col"> <motion.div variants={linkColumnVariant} className="flex flex-col">
<h5 className="mb-5 text-xs font-extrabold uppercase tracking-wider text-white/40 md:text-sm xl:mb-7 xl:text-[17px]"> <div className="mb-5 text-xs font-extrabold uppercase tracking-wider text-white/40 md:text-sm xl:mb-7 xl:text-[17px]">
Nearle Nearle
</h5> </div>
<FooterLinkList links={PARTNERSHIPS} /> <FooterLinkList links={PARTNERSHIPS} />
</motion.div> </motion.div>
{/* Support links */} {/* Support links */}
<motion.div variants={linkColumnVariant} className="flex flex-col"> <motion.div variants={linkColumnVariant} className="flex flex-col">
<h5 className="mb-5 text-xs font-extrabold uppercase tracking-wider text-white/40 md:text-sm xl:mb-7 xl:text-[17px]"> <div className="mb-5 text-xs font-extrabold uppercase tracking-wider text-white/40 md:text-sm xl:mb-7 xl:text-[17px]">
Support Support
</h5> </div>
<FooterLinkList links={LEGAL} /> <FooterLinkList links={LEGAL} />
</motion.div> </motion.div>
</motion.div> </motion.div>
@@ -197,9 +219,9 @@ export function PremiumFooter() {
</div> </div>
<div className="text-left"> <div className="text-left">
<h6 className="text-sm font-black text-white md:text-base xl:text-lg"> <div className="text-sm font-black text-white md:text-base xl:text-lg">
Nearle Nearle
</h6> </div>
<p className="mt-0.5 text-xs leading-normal text-white/60 md:text-sm xl:mt-1 xl:text-base"> <p className="mt-0.5 text-xs leading-normal text-white/60 md:text-sm xl:mt-1 xl:text-base">
424, Diwan Bahadur Rd, Opp. Sri Krishna Sweets, R.S. Puram, 424, Diwan Bahadur Rd, Opp. Sri Krishna Sweets, R.S. Puram,
Coimbatore, TN 641002 Coimbatore, TN 641002

View File

@@ -2,7 +2,7 @@
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { useState, useEffect } from "react"; import { useState } from "react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { import {
motion, motion,
@@ -56,11 +56,6 @@ export function PremiumNavbar() {
setScrolled(y > 20); setScrolled(y > 20);
}); });
// Close mobile menu whenever the route changes (navbar no longer remounts on navigation)
useEffect(() => {
setMobileOpen(false);
}, [pathname]);
const isActiveLink = (href: string) => { const isActiveLink = (href: string) => {
if (href === "/") return pathname === "/"; if (href === "/") return pathname === "/";
return pathname.startsWith(href); return pathname.startsWith(href);

View File

@@ -3,7 +3,7 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { fadeUp, staggerContainer, viewport } from "@/lib/animations"; import { fadeUp, staggerContainer, viewport } from "@/lib/animations";
import type { Variants } from "framer-motion"; import type { TargetAndTransition, Variants } from "framer-motion";
const VARIANTS: Record<string, Variants> = { const VARIANTS: Record<string, Variants> = {
fadeUp, fadeUp,
@@ -24,13 +24,17 @@ export function AnimatedSection({
delay = 0, delay = 0,
}: AnimatedSectionProps) { }: AnimatedSectionProps) {
const chosen = VARIANTS[variant] ?? fadeUp; const chosen = VARIANTS[variant] ?? fadeUp;
// eslint-disable-next-line @typescript-eslint/no-explicit-any const visible = chosen.visible;
const visibleState: TargetAndTransition =
typeof visible === "object" && visible !== null && !Array.isArray(visible)
? visible
: {};
const withDelay: Variants = { const withDelay: Variants = {
hidden: chosen.hidden as any, hidden: chosen.hidden,
visible: { visible: {
...(chosen.visible as any), ...visibleState,
transition: { transition: {
...((chosen.visible as any)?.transition ?? {}), ...(visibleState.transition ?? {}),
delay, delay,
}, },
}, },

View File

@@ -37,9 +37,9 @@ export function FeatureStatsRow({ items, className = "" }: FeatureStatsRowProps)
<MaterialIcon icon={item.icon} size={28} /> <MaterialIcon icon={item.icon} size={28} />
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<h4 className="text-lg lg:text-xl xl:text-2xl font-black text-[#17051F] leading-tight transition-colors duration-[250ms] group-hover:text-[#6330D6]"> <div className="text-lg lg:text-xl xl:text-2xl font-black text-[#17051F] leading-tight transition-colors duration-[250ms] group-hover:text-[#6330D6]">
{item.title} {item.title}
</h4> </div>
{item.desc && ( {item.desc && (
<p className="text-sm lg:text-[15px] font-medium text-[#6B7280] mt-1 leading-snug"> <p className="text-sm lg:text-[15px] font-medium text-[#6B7280] mt-1 leading-snug">
{item.desc} {item.desc}

View File

@@ -1,3 +1,21 @@
import {
Target,
Eye,
CircleHelp,
Users,
Store,
Building2,
Zap,
Download,
Compass,
Smile,
X,
Menu,
MapPin,
Phone,
type LucideIcon,
} from "lucide-react";
type MaterialIconProps = { type MaterialIconProps = {
icon: string; icon: string;
size?: number; size?: number;
@@ -5,15 +23,51 @@ type MaterialIconProps = {
className?: string; className?: string;
}; };
// Small, frequently-used subset of Material Symbols rendered as inline SVG
// (via lucide-react) so these pages don't need to fetch the full icon font.
// Any icon name not listed here falls back to the material-symbols-outlined
// font, which other pages still rely on.
const LUCIDE_ICON_MAP: Record<string, LucideIcon> = {
target: Target,
visibility: Eye,
help_outline: CircleHelp,
people: Users,
store: Store,
location_city: Building2,
bolt: Zap,
download: Download,
explore: Compass,
mood: Smile,
close: X,
menu: Menu,
location_on: MapPin,
phone: Phone,
};
export function MaterialIcon({ export function MaterialIcon({
icon, icon,
size = 24, size = 24,
fill = false, fill = false,
className = "", className = "",
}: MaterialIconProps) { }: MaterialIconProps) {
const LucideComponent = LUCIDE_ICON_MAP[icon];
if (LucideComponent) {
return (
<LucideComponent
size={size}
strokeWidth={2}
fill={fill ? "currentColor" : "none"}
className={className}
aria-hidden="true"
/>
);
}
return ( return (
<span <span
className={`material-symbols-outlined ${className}`} className={`material-symbols-outlined ${className}`}
aria-hidden="true"
style={{ style={{
fontSize: size, fontSize: size,
fontVariationSettings: `'FILL' ${fill ? 1 : 0}, 'wght' 400, 'GRAD' 0, 'opsz' ${size}`, fontVariationSettings: `'FILL' ${fill ? 1 : 0}, 'wght' 400, 'GRAD' 0, 'opsz' ${size}`,