remove unused files

This commit is contained in:
2026-06-19 12:15:50 +05:30
parent 6958805396
commit 53a1968350
36 changed files with 928 additions and 4644 deletions

42
eslint.config.js Normal file
View File

@@ -0,0 +1,42 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{
ignores: [
'.next/**',
'node_modules/**',
'next-env.d.ts',
'next.config.mjs',
'postcss.config.js',
'tailwind.config.ts',
],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['src/**/*.{ts,tsx}'],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
plugins: {
'react-hooks': reactHooks,
},
rules: {
...reactHooks.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-undef': 'off',
},
}
)

3
next-env.d.ts vendored
View File

@@ -1,5 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -1,6 +1,22 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
]
},
}; };
export default nextConfig; export default nextConfig;

2366
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,37 +7,34 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "eslint src --max-warnings=0"
}, },
"dependencies": { "dependencies": {
"@react-three/drei": "^9.114.0", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^8.17.10", "@react-three/fiber": "^9.6.1",
"framer-motion": "^12.40.0", "framer-motion": "^12.40.0",
"gsap": "^3.15.0", "gsap": "^3.15.0",
"lenis": "^1.3.23", "lenis": "^1.3.23",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "^14.2.35", "next": "^16.2.9",
"react": "^18.3.1", "react": "^19.2.1",
"react-dom": "^18.3.1", "react-dom": "^19.2.1",
"three": "^0.170.0" "three": "^0.170.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.13.0", "@eslint/js": "^9.13.0",
"@tailwindcss/postcss": "^4.3.0", "@tailwindcss/postcss": "^4.3.0",
"@types/node": "^25.9.1", "@types/node": "^25.9.1",
"@types/react": "^18.3.29", "@types/react": "^19.2.17",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^19.2.3",
"@types/three": "^0.184.1", "@types/three": "^0.184.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.5.0", "autoprefixer": "^10.5.0",
"eslint": "^9.13.0", "eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0", "globals": "^15.11.0",
"postcss": "^8.5.15", "postcss": "^8.5.15",
"tailwindcss": "^3.4.19", "tailwindcss": "^3.4.19",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"typescript-eslint": "^8.11.0", "typescript-eslint": "^8.11.0"
"vite": "^5.4.10"
} }
} }

9
public/apple-icon.png Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.62 (Debian) Server at nearle.in Port 80</address>
</body></html>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,21 @@
import type { Metadata } from 'next'
import { SectionHeader } from '@/components/ui/SectionHeader' import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard' import { GlassCard } from '@/components/ui/GlassCard'
import { AnimatedCounter } from '@/components/ui/AnimatedCounter' import { AnimatedCounter } from '@/components/ui/AnimatedCounter'
export const metadata: Metadata = {
title: 'About Us',
description:
"Learn about Nearle's mission to build neighbourhood living platforms that connect residents, local stores, and trusted businesses.",
alternates: { canonical: '/about' },
openGraph: {
title: 'About Nearle',
description:
"Nearle builds hyperlocal neighbourhood commerce for residents and local businesses.",
url: '/about',
},
}
export default function AboutPage() { export default function AboutPage() {
return ( return (
<div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-white relative overflow-hidden"> <div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-white relative overflow-hidden">
@@ -22,6 +36,7 @@ export default function AboutPage() {
sub="Nearle is India's first hyper-local managed 'Neighbourhood living platform' which brings all the amenities and requirements of residents to their fingertips. We are redefining modern urban living by providing never before convenience." sub="Nearle is India's first hyper-local managed 'Neighbourhood living platform' which brings all the amenities and requirements of residents to their fingertips. We are redefining modern urban living by providing never before convenience."
align="center" align="center"
className="mb-16" className="mb-16"
headingAs="h1"
/> />
<div className="grid md:grid-cols-2 gap-8 mb-20"> <div className="grid md:grid-cols-2 gap-8 mb-20">

View File

@@ -1,6 +1,20 @@
import type { Metadata } from 'next'
import { SectionHeader } from '@/components/ui/SectionHeader' import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard' import { GlassCard } from '@/components/ui/GlassCard'
export const metadata: Metadata = {
title: 'Nearle for Business',
description:
'Partner with Nearle to reach nearby residents, manage local demand, and grow your neighbourhood business online.',
alternates: { canonical: '/businesses' },
openGraph: {
title: 'Nearle for Business',
description:
'Reach nearby residents and build your local brand with Nearle.',
url: '/businesses',
},
}
export default function BusinessesPage() { export default function BusinessesPage() {
return ( return (
<div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-nearle-bgsoft relative overflow-hidden"> <div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-nearle-bgsoft relative overflow-hidden">
@@ -19,6 +33,7 @@ export default function BusinessesPage() {
sub="Join the futuristic community lifestyle living. Nearle is built to help businesses like Yours. Get set in minutes and delight the Neighbourhood with a personalized online experience." sub="Join the futuristic community lifestyle living. Nearle is built to help businesses like Yours. Get set in minutes and delight the Neighbourhood with a personalized online experience."
align="center" align="center"
className="mb-16" className="mb-16"
headingAs="h1"
/> />
<div className="bg-white rounded-3xl p-8 sm:p-12 shadow-nearle-md border border-purple-lavender/30 mb-20 text-center"> <div className="bg-white rounded-3xl p-8 sm:p-12 shadow-nearle-md border border-purple-lavender/30 mb-20 text-center">

View File

@@ -1,7 +1,21 @@
import type { Metadata } from 'next'
import { SectionHeader } from '@/components/ui/SectionHeader' import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard' import { GlassCard } from '@/components/ui/GlassCard'
import { AnimatedCounter } from '@/components/ui/AnimatedCounter' import { AnimatedCounter } from '@/components/ui/AnimatedCounter'
export const metadata: Metadata = {
title: 'Nearle for People',
description:
'Use Nearle to discover local groceries, restaurants, offers, and home services from trusted businesses in your neighbourhood.',
alternates: { canonical: '/communities' },
openGraph: {
title: 'Nearle for People',
description:
'Discover daily essentials, food, offers, and services from your neighbourhood.',
url: '/communities',
},
}
export default function CommunitiesPage() { export default function CommunitiesPage() {
return ( return (
<div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-white relative overflow-hidden"> <div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-white relative overflow-hidden">
@@ -22,6 +36,7 @@ export default function CommunitiesPage() {
sub="Join the futuristic community lifestyle living. Be a part of a neighbourhood of like-minded people. Enjoy the comfort of elegant living with Nearle." sub="Join the futuristic community lifestyle living. Be a part of a neighbourhood of like-minded people. Enjoy the comfort of elegant living with Nearle."
align="center" align="center"
className="mb-16" className="mb-16"
headingAs="h1"
/> />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-24"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-24">

View File

@@ -1,10 +1,24 @@
import type { Metadata } from 'next'
import { SectionHeader } from '@/components/ui/SectionHeader' import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard' import { GlassCard } from '@/components/ui/GlassCard'
import { Mail, Phone, MapPin, Clock } from 'lucide-react' import { Mail, MapPin, Clock } from 'lucide-react'
export const metadata: Metadata = {
title: 'Contact Us',
description:
'Contact Nearle Technology Private Limited for neighbourhood onboarding, merchant partnerships, and support in Coimbatore.',
alternates: { canonical: '/contact' },
openGraph: {
title: 'Contact Nearle',
description:
'Reach Nearle for neighbourhood onboarding, merchant partnerships, and support.',
url: '/contact',
},
}
export default function ContactPage() { export default function ContactPage() {
return ( return (
<div className="pt-32 pb-24 sm:pt-40 sm:pb-32 bg-nearle-bgsoft relative overflow-hidden"> <div className="pt-28 pb-20 sm:pt-40 sm:pb-32 bg-nearle-bgsoft relative overflow-hidden">
<div className="max-w-7xl mx-auto px-5 sm:px-8"> <div className="max-w-7xl mx-auto px-5 sm:px-8">
<SectionHeader <SectionHeader
label="Contact Us" label="Contact Us"
@@ -13,19 +27,23 @@ export default function ContactPage() {
Get in touch Get in touch
<br /> <br />
<span className="bg-gradient-purple bg-clip-text text-transparent"> <span className="bg-gradient-purple bg-clip-text text-transparent">
Feel free to ask for details Feel free to ask for
<br className="sm:hidden" />
<span className="hidden sm:inline"> </span>
details
</span> </span>
</> </>
} }
sub="Don't save any questions! We are here to help you build your neighbourhood." sub="Don't save any questions! We are here to help you build your neighbourhood."
align="center" align="center"
className="mb-16" className="mb-10 sm:mb-16"
headingAs="h1"
/> />
<div className="grid lg:grid-cols-2 gap-10 items-start"> <div className="grid lg:grid-cols-2 gap-8 lg:gap-10 items-start">
{/* Form Side */} {/* Form Side */}
<GlassCard className="p-8 border-purple-lavender/50 shadow-nearle-md"> <GlassCard className="p-5 sm:p-8 border-purple-lavender/50 shadow-nearle-md">
<form className="flex flex-col gap-5"> <form className="flex flex-col gap-4 sm:gap-5">
<div> <div>
<label className="block text-sm font-bold text-nearle-dark mb-2">Full Name</label> <label className="block text-sm font-bold text-nearle-dark mb-2">Full Name</label>
<input <input
@@ -60,7 +78,7 @@ export default function ContactPage() {
</div> </div>
<button <button
type="button" type="button"
className="mt-2 bg-purple-deep text-white font-bold py-4 rounded-xl shadow-cta hover:bg-purple-primary transition-colors" className="mt-2 bg-purple-deep text-white font-bold py-3.5 sm:py-4 rounded-xl shadow-cta hover:bg-purple-primary transition-colors"
> >
Send Message Send Message
</button> </button>
@@ -83,18 +101,6 @@ export default function ContactPage() {
</div> </div>
</GlassCard> </GlassCard>
<GlassCard className="flex items-start gap-4 border-purple-lavender/30">
<div className="w-12 h-12 rounded-full bg-purple-soft text-purple-primary flex items-center justify-center shrink-0">
<Phone size={24} />
</div>
<div>
<h4 className="font-display font-bold text-lg text-nearle-dark mb-1">Phone</h4>
<p className="font-body text-nearle-mid text-sm leading-relaxed">
+91 96... (Contact Number)
</p>
</div>
</GlassCard>
<GlassCard className="flex items-start gap-4 border-purple-lavender/30"> <GlassCard className="flex items-start gap-4 border-purple-lavender/30">
<div className="w-12 h-12 rounded-full bg-purple-soft text-purple-primary flex items-center justify-center shrink-0"> <div className="w-12 h-12 rounded-full bg-purple-soft text-purple-primary flex items-center justify-center shrink-0">
<Mail size={24} /> <Mail size={24} />

View File

@@ -33,6 +33,13 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
@media (max-width: 639px) {
html,
body {
overflow-x: hidden;
}
}
::selection { ::selection {
background: var(--purple-deep); background: var(--purple-deep);
color: #fff; color: #fff;

View File

@@ -48,6 +48,26 @@ export const metadata: Metadata = {
siteName: 'Nearle', siteName: 'Nearle',
locale: 'en_IN', locale: 'en_IN',
type: 'website', type: 'website',
images: [
{
url: '/logo.png',
width: 512,
height: 512,
alt: 'Nearle logo',
},
],
},
twitter: {
card: 'summary',
title: 'Nearle — Your Neighbourhood, Your World',
description: "India's hyperlocal neighbourhood commerce platform",
images: ['/logo.png'],
},
alternates: { canonical: '/' },
icons: {
icon: '/favicon.ico',
shortcut: '/favicon.ico',
apple: '/apple-icon.png',
}, },
robots: { index: true, follow: true }, robots: { index: true, follow: true },
} }

View File

@@ -1,9 +1,23 @@
import type { Metadata } from 'next'
import { Hero } from '@/components/home/Hero' import { Hero } from '@/components/home/Hero'
import { ValueEcosystem } from '@/components/home/ValueEcosystem' import { ValueEcosystem } from '@/components/home/ValueEcosystem'
import { NeighbourhoodMap } from '@/components/home/NeighbourhoodMap' import { NeighbourhoodMap } from '@/components/home/NeighbourhoodMap'
import { Testimonials } from '@/components/home/Testimonials' import { Testimonials } from '@/components/home/Testimonials'
import { Download } from '@/components/home/Download' import { Download } from '@/components/home/Download'
export const metadata: Metadata = {
title: 'Nearle — Your Neighbourhood, Your World',
description:
"Nearle connects residents with groceries, restaurants, home services, and trusted local businesses in one hyperlocal neighbourhood platform.",
alternates: { canonical: '/' },
openGraph: {
title: 'Nearle — Your Neighbourhood, Your World',
description:
"Connect with groceries, restaurants, home services, and trusted local businesses near you.",
url: '/',
},
}
export default function HomePage() { export default function HomePage() {
return ( return (
<> <>

11
src/app/robots.ts Normal file
View File

@@ -0,0 +1,11 @@
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: 'https://nearle.in/sitemap.xml',
}
}

12
src/app/sitemap.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { MetadataRoute } from 'next'
const routes = ['', '/about', '/businesses', '/communities', '/contact']
export default function sitemap(): MetadataRoute.Sitemap {
return routes.map((route) => ({
url: `https://nearle.in${route}`,
lastModified: new Date(),
changeFrequency: route === '' ? 'weekly' : 'monthly',
priority: route === '' ? 1 : 0.8,
}))
}

View File

@@ -1,375 +0,0 @@
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard'
import { Badge } from '@/components/ui/Badge'
import { fadeUp, staggerContainer, viewportConfig } from '@/lib/animations'
type AppDetails = {
id: string
name: string
purpose: string
playUrl: string
features: string[]
themeColor: string
mockScreen: React.ReactNode
}
export function AppEcosystem() {
const [activeApp, setActiveApp] = useState<string>('deal')
const APPS: AppDetails[] = [
{
id: 'deal',
name: 'Nearle Deal',
purpose: 'Customer Shopping & Hyperlocal Commerce App',
playUrl: 'https://play.google.com/store/apps/details?id=com.nearle.deal',
themeColor: 'bg-purple-deep',
features: [
'Grocery & vegetable shopping',
'Restaurant food delivery',
'Exclusive hyperlocal local society discounts',
'Smart neighborhood deals',
],
mockScreen: (
<div className="w-full h-full bg-[#FAF8FB] p-5 flex flex-col justify-between font-body text-nearle-dark select-none">
{/* Header */}
<div className="flex items-center justify-between pb-3 border-b border-purple-lavender/30">
<span className="font-display font-extrabold text-sm text-purple-deep">Nearle Deal</span>
<span className="w-2.5 h-2.5 rounded-full bg-green-500 animate-pulse" />
</div>
{/* Body */}
<div className="flex-1 flex flex-col justify-center py-4">
<p className="text-[10px] uppercase font-mono tracking-wider text-purple-primary">Best Selling Near You</p>
<h4 className="font-display font-bold text-base mt-1">RS Puram Organic Mart</h4>
<div className="mt-3 bg-white p-3 rounded-2xl border border-nearle-border shadow-nearle-sm flex items-center gap-3">
<span className="text-2xl">🥬</span>
<div className="flex-1">
<p className="font-display font-bold text-xs">Fresh Farm Spinach</p>
<p className="text-[10px] text-nearle-mid mt-0.5">Qty: 500g · Fast Delivery</p>
</div>
<span className="font-mono text-xs font-bold text-purple-deep">28</span>
</div>
<div className="mt-3 bg-purple-soft/60 border border-purple-lavender/30 p-3 rounded-2xl flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xs">🛵</span>
<span className="text-[10px] font-bold text-purple-deep">Delivery in 14 Mins</span>
</div>
<span className="text-[9px] uppercase font-mono tracking-widest text-purple-primary font-bold">Track</span>
</div>
</div>
{/* Footer Checkout */}
<button className="w-full py-2.5 bg-purple-deep text-white rounded-full font-bold text-xs shadow-cta hover:bg-purple-primary transition">
Proceed to Checkout
</button>
</div>
),
},
{
id: 'partner',
name: 'Nearle Partner',
purpose: 'Merchant & Local Shop Management Platform',
playUrl: 'https://play.google.com/store/apps/details?id=com.nearle.partner',
themeColor: 'bg-purple-primary',
features: [
'Instant order notification alerts',
'Intuitive inventory catalog editor',
'Sales analysis and growth dashboards',
'Fast direct bank payout settlements',
],
mockScreen: (
<div className="w-full h-full bg-[#FCFAFD] p-5 flex flex-col justify-between font-body text-nearle-dark select-none">
{/* Header */}
<div className="flex items-center justify-between pb-3 border-b border-purple-lavender/30">
<span className="font-display font-extrabold text-sm text-purple-deep">Nearle Partner</span>
<span className="text-[10px] font-mono bg-purple-lavender px-2 py-0.5 rounded-full text-purple-deep font-bold">Active</span>
</div>
{/* Body */}
<div className="flex-1 flex flex-col justify-center py-4">
<p className="text-[10px] uppercase font-mono tracking-wider text-purple-primary">Business Analytics</p>
<h4 className="font-display font-bold text-base mt-1">Today&apos;s Revenue</h4>
<div className="mt-3 bg-white p-4 rounded-2xl border border-nearle-border shadow-nearle-sm">
<p className="text-2xl font-display font-extrabold text-nearle-dark">12,480</p>
<div className="flex items-center gap-1.5 text-green-600 text-xs mt-1 font-bold">
<span> 18.5%</span>
<span className="text-nearle-light font-normal text-[10px]">vs yesterday</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-2 text-center">
<div className="bg-purple-soft/40 p-2.5 rounded-xl border border-purple-lavender/10">
<p className="font-mono text-xs font-bold text-purple-deep">38</p>
<p className="text-[9px] text-nearle-light mt-0.5">Orders Accepted</p>
</div>
<div className="bg-purple-soft/40 p-2.5 rounded-xl border border-purple-lavender/10">
<p className="font-mono text-xs font-bold text-purple-deep">99.2%</p>
<p className="text-[9px] text-nearle-light mt-0.5">Accuracy Rate</p>
</div>
</div>
</div>
{/* Footer Active Alert */}
<div className="bg-emerald-500 text-white rounded-full p-2 text-center text-[10px] font-bold tracking-wider animate-pulse uppercase">
New Order Received
</div>
</div>
),
},
{
id: 'gear',
name: 'Nearle Gear',
purpose: 'Delivery Partner Fleet Optimization App',
playUrl: 'https://play.google.com/store/apps/details?id=com.nearle.gear',
themeColor: 'bg-purple-accent',
features: [
'Smart real-time route path optimization',
'Direct rider earnings and payout log',
'Instant pickup/delivery verification alerts',
'Flexible custom slots for riders',
],
mockScreen: (
<div className="w-full h-full bg-[#FAF8FB] p-5 flex flex-col justify-between font-body text-nearle-dark select-none">
{/* Header */}
<div className="flex items-center justify-between pb-3 border-b border-purple-lavender/30">
<span className="font-display font-extrabold text-sm text-purple-deep">Nearle Gear</span>
<span className="text-[10px] font-mono bg-purple-soft text-purple-deep px-2 py-0.5 rounded-full border border-purple-lavender/30 font-bold">On Duty</span>
</div>
{/* Body */}
<div className="flex-1 flex flex-col justify-center py-4">
<p className="text-[10px] uppercase font-mono tracking-wider text-purple-primary">Optimal Delivery Route</p>
<h4 className="font-display font-bold text-base mt-1">Route #4902</h4>
<div className="mt-3 bg-white p-3 rounded-2xl border border-nearle-border shadow-nearle-sm flex flex-col gap-3 font-body">
<div className="flex gap-2">
<span className="text-purple-primary font-mono text-xs">A</span>
<span className="text-xs text-nearle-dark font-bold">Kalyan Grocer (Pickup)</span>
</div>
<div className="flex gap-2 border-t border-nearle-border/40 pt-2">
<span className="text-purple-deep font-mono text-xs">B</span>
<span className="text-xs text-nearle-dark font-bold">Siddharth Apts (Drop)</span>
</div>
</div>
<div className="mt-3 bg-purple-soft/60 border border-purple-lavender/30 p-2.5 rounded-xl flex justify-between items-center">
<span className="text-[10px] text-nearle-mid font-semibold">Active Slot Earnings</span>
<span className="font-mono text-xs font-bold text-purple-deep">480</span>
</div>
</div>
{/* Footer Action */}
<button className="w-full py-2.5 bg-purple-deep text-white rounded-full font-bold text-xs shadow-cta hover:bg-purple-primary transition">
Swipe to Complete Drop
</button>
</div>
),
},
{
id: 'admin',
name: 'Nearle Admin',
purpose: 'Operations Control & Analytics Platform',
playUrl: 'https://play.google.com/store/apps/details?id=com.nearle.admin',
themeColor: 'bg-purple-deep',
features: [
'Real-time society network dashboard',
'Automatic rider assigning algorithms',
'Comprehensive user feedback tracker',
'Escrow and merchant settlement monitoring',
],
mockScreen: (
<div className="w-full h-full bg-[#FAF8FB] p-5 flex flex-col justify-between font-body text-nearle-dark select-none">
{/* Header */}
<div className="flex items-center justify-between pb-3 border-b border-purple-lavender/30">
<span className="font-display font-extrabold text-sm text-purple-deep">Nearle Admin</span>
<span className="text-[10px] font-mono bg-purple-deep text-white px-2 py-0.5 rounded-full font-bold">HQ</span>
</div>
{/* Body */}
<div className="flex-1 flex flex-col justify-center py-4">
<p className="text-[10px] uppercase font-mono tracking-wider text-purple-primary">Operations Center</p>
<h4 className="font-display font-bold text-base mt-1">Platform Diagnostics</h4>
<div className="mt-3 bg-white p-3 rounded-2xl border border-nearle-border shadow-nearle-sm flex flex-col gap-2">
<div className="flex justify-between items-center">
<span className="text-xs text-nearle-mid">Active Society Hubs</span>
<span className="font-mono text-xs font-bold text-purple-deep">24 / 25</span>
</div>
<div className="flex justify-between items-center border-t border-nearle-border/40 pt-2">
<span className="text-xs text-nearle-mid">Fleet Operational</span>
<span className="font-mono text-xs font-bold text-purple-deep">98.4%</span>
</div>
</div>
<div className="mt-3 bg-green-50 border border-green-200 p-2.5 rounded-xl text-center">
<span className="text-[10px] text-green-700 font-bold"> All services operational</span>
</div>
</div>
{/* Footer Option */}
<button className="w-full py-2.5 bg-nearle-dark text-white rounded-full font-bold text-xs hover:bg-nearle-mid transition">
Launch Admin Center
</button>
</div>
),
},
]
const activeAppDetails = APPS.find((app) => app.id === activeApp) || APPS[0]!
return (
<div className="py-24 sm:py-32 bg-nearle-bgsoft px-5 sm:px-8 relative overflow-hidden">
{/* Decorative Blur Backgrounds */}
<div className="absolute top-1/4 left-1/4 w-[500px] h-[500px] rounded-full bg-purple-soft/50 blur-3xl -z-10 pointer-events-none" />
<div className="absolute bottom-1/4 right-1/4 w-[500px] h-[500px] rounded-full bg-purple-lavender/30 blur-3xl -z-10 pointer-events-none" />
<div className="max-w-7xl mx-auto">
<SectionHeader
label="App Ecosystem"
heading={
<>
Unified Hyperlocal
<br />
<span className="bg-gradient-purple bg-clip-text text-transparent">
Mobile Suite
</span>
</>
}
sub="Nearle powers four custom-tailored mobile applications designed for the distinct members of our active community."
align="center"
className="mb-16 sm:mb-20"
/>
<div className="grid lg:grid-cols-12 gap-12 items-center">
{/* Mockup Showcase Column (5 cols) */}
<div className="lg:col-span-5 flex justify-center">
<div className="relative">
{/* Floating notification decor */}
<motion.div
animate={{ y: [0, -10, 0] }}
transition={{ duration: 5, repeat: Infinity, ease: 'easeInOut' }}
className="absolute top-12 -left-10 z-20 w-[160px] bg-white border border-nearle-border shadow-nearle-md p-3 rounded-2xl flex items-center gap-2"
>
<div className="w-6 h-6 rounded-full bg-purple-lavender flex items-center justify-center text-xs">💜</div>
<div className="flex-1">
<p className="font-display font-bold text-[10px] text-nearle-dark">Delivery Alert</p>
<p className="font-body text-[8px] text-nearle-mid">Rider dispatched</p>
</div>
</motion.div>
<motion.div
animate={{ y: [0, 8, 0] }}
transition={{ duration: 6, repeat: Infinity, ease: 'easeInOut', delay: 1 }}
className="absolute bottom-16 -right-12 z-20 w-[140px] bg-white border border-nearle-border shadow-nearle-md p-3 rounded-2xl flex items-center gap-2"
>
<div className="w-6 h-6 rounded-full bg-green-100 flex items-center justify-center text-xs"></div>
<div className="flex-1">
<p className="font-display font-bold text-[10px] text-nearle-dark">Payout Sent</p>
<p className="font-body text-[8px] text-green-600 font-bold">+1,250</p>
</div>
</motion.div>
{/* Smartphone Outer Shell */}
<div className="relative w-[280px] h-[570px] rounded-[48px] border-[12px] border-nearle-dark bg-nearle-dark shadow-nearle-lg overflow-hidden flex flex-col">
{/* Dynamic Screen Container */}
<div className="flex-1 rounded-[36px] overflow-hidden relative bg-white">
{/* Phone Notch */}
<div className="absolute top-0 inset-x-0 h-5 bg-nearle-dark flex justify-center items-center z-30">
<div className="w-16 h-4 bg-nearle-dark rounded-b-xl" />
</div>
{/* Screen Content */}
<div className="w-full h-full pt-5">
<AnimatePresence mode="wait">
<motion.div
key={activeAppDetails.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="w-full h-full"
>
{activeAppDetails.mockScreen}
</motion.div>
</AnimatePresence>
</div>
</div>
</div>
</div>
</div>
{/* Description & Selection Tabs Column (7 cols) */}
<div className="lg:col-span-7 flex flex-col gap-8">
<div className="grid grid-cols-2 gap-4">
{APPS.map((app) => {
const isActive = app.id === activeApp
return (
<button
key={app.id}
onClick={() => setActiveApp(app.id)}
className={`text-left p-5 rounded-3xl border transition-all duration-300 focus:outline-none ${
isActive
? 'bg-white border-purple-deep shadow-nearle-md'
: 'bg-white/40 border-nearle-border hover:border-purple-primary'
}`}
>
<span className="font-mono text-[9px] uppercase tracking-widest text-purple-primary">
Official Application
</span>
<h3 className="font-display font-bold text-lg text-nearle-dark mt-1">
{app.name}
</h3>
</button>
)
})}
</div>
{/* Selected App Details Panel */}
<GlassCard className="border-purple-lavender/50 hover:border-purple-primary shadow-nearle-md p-8">
<span className={`inline-block px-3 py-1 rounded-full text-white text-xs font-mono font-bold uppercase tracking-wider ${activeAppDetails.themeColor} mb-4`}>
{activeAppDetails.name}
</span>
<h3 className="h3-display text-nearle-dark">
{activeAppDetails.purpose}
</h3>
<p className="font-body text-nearle-mid mt-4 leading-relaxed">
Specifically built with GPU-optimised rendering, secure escrowed payments, and smart push alert integrations to empower this segment.
</p>
<ul className="mt-6 grid sm:grid-cols-2 gap-4">
{activeAppDetails.features.map((feat) => (
<li key={feat} className="flex items-start gap-3">
<span className="w-5 h-5 rounded-full bg-purple-soft flex items-center justify-center text-xs text-purple-deep font-bold mt-0.5">
</span>
<span className="font-body text-sm text-nearle-dark">
{feat}
</span>
</li>
))}
</ul>
<div className="mt-8 pt-6 border-t border-nearle-border flex flex-wrap gap-4 items-center justify-between">
<a
href={activeAppDetails.playUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2.5 px-6 py-2.5 bg-[#111827] text-white rounded-full font-bold text-sm shadow-cta hover:bg-purple-deep transition active:scale-95"
>
<span className="text-lg">🤖</span>
<span>Get on Google Play</span>
</a>
</div>
</GlassCard>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,175 +0,0 @@
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard'
import { staggerContainer, fadeUp, scaleUp, viewportConfig } from '@/lib/animations'
type CategoryItem = {
id: string
name: string
icon: string
subItems: string[]
accentColor: string
}
const CATEGORIES: CategoryItem[] = [
{
id: 'groceries',
name: 'Fresh Groceries',
icon: '🥬',
subItems: ['Fruits & Greens', 'Local Dairy & Farm Milk', 'Snacks & Beverages', 'Spices & Staples'],
accentColor: 'border-green-300 bg-green-500/5 text-green-600',
},
{
id: 'food',
name: 'Food & Dining',
icon: '🍜',
subItems: ['South Indian Special', 'Fine Dine Curries', 'Organic Salads', 'Street Side Sweets'],
accentColor: 'border-orange-300 bg-orange-500/5 text-orange-600',
},
{
id: 'services',
name: 'Home Services',
icon: '🧹',
subItems: ['Appliance Repair', 'Premium Deep Cleaning', 'Plumbing & Wiring', 'Home Painting'],
accentColor: 'border-blue-300 bg-blue-500/5 text-blue-600',
},
]
export function Categories() {
const [active, setActive] = useState<string>('groceries')
const activeCategory = CATEGORIES.find((cat) => cat.id === active) || CATEGORIES[0]!
return (
<div className="py-24 sm:py-32 bg-white px-5 sm:px-8 relative overflow-hidden">
<div className="absolute top-1/2 left-0 w-[400px] h-[400px] rounded-full bg-purple-soft/30 blur-3xl -z-10 pointer-events-none" />
<div className="max-w-7xl mx-auto">
<SectionHeader
label="Smarter Taxonomy"
heading={
<>
Explore our diverse
<br />
<span className="bg-gradient-purple bg-clip-text text-transparent">
Platform Categories
</span>
</>
}
sub="Everything categorised precisely. Whether you need grocery supplies, a hot meal, or a trusted electrician, find it instantly."
align="center"
className="mb-16 sm:mb-20"
/>
<div className="grid lg:grid-cols-12 gap-8 items-start">
{/* Category Tabs (5 cols) */}
<motion.div
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewportConfig}
className="lg:col-span-5 flex flex-col gap-4"
>
{CATEGORIES.map((cat) => {
const isActive = cat.id === active
return (
<motion.button
key={cat.id}
variants={fadeUp}
onClick={() => setActive(cat.id)}
className={`w-full text-left p-5 rounded-2xl border transition-all duration-300 focus:outline-none ${
isActive
? 'bg-purple-deep text-white border-purple-deep shadow-nearle-md'
: 'bg-white border-nearle-border hover:border-purple-primary text-nearle-dark'
}`}
>
<div className="flex items-center gap-4">
<div
className={`w-12 h-12 rounded-xl flex items-center justify-center text-2xl transition-colors duration-300 ${
isActive ? 'bg-white/10 text-white' : 'bg-purple-soft text-purple-deep'
}`}
>
{cat.icon}
</div>
<div className="flex-1">
<h3 className="font-display font-bold text-base leading-tight">
{cat.name}
</h3>
<p
className={`text-xs mt-1 font-body transition-colors duration-300 ${
isActive ? 'text-purple-lavender' : 'text-nearle-mid'
}`}
>
{cat.subItems.length} core segments available
</p>
</div>
</div>
</motion.button>
)
})}
</motion.div>
{/* Sub-Category Preview Panel (7 cols) */}
<div className="lg:col-span-7 h-full">
<AnimatePresence mode="wait">
<motion.div
key={activeCategory.id}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -15 }}
transition={{ duration: 0.35 }}
>
<GlassCard className="border-purple-lavender/50 hover:border-purple-primary p-8 min-h-[380px] flex flex-col justify-between shadow-nearle-md">
<div>
<div className="flex items-center gap-3 mb-6">
<span className="text-4xl">{activeCategory.icon}</span>
<div>
<span className={`inline-block px-2.5 py-0.5 rounded-full border text-[10px] font-mono font-bold uppercase tracking-wider ${activeCategory.accentColor}`}>
Ecosystem Partner Group
</span>
<h3 className="font-display font-extrabold text-2xl text-nearle-dark mt-1">
{activeCategory.name}
</h3>
</div>
</div>
<p className="font-body text-nearle-mid text-base leading-relaxed mb-8">
We curate verified local operators under this segment, providing them with robust tools, escrowed payouts, and real-time live navigation.
</p>
<div className="grid sm:grid-cols-2 gap-4">
{activeCategory.subItems.map((sub, idx) => (
<motion.div
key={sub}
variants={scaleUp}
initial="hidden"
animate="visible"
transition={{ delay: idx * 0.08 }}
className="flex items-center gap-3 p-4 bg-purple-soft/40 border border-purple-lavender/20 rounded-xl"
>
<span className="w-1.5 h-1.5 rounded-full bg-purple-primary" />
<span className="font-display font-bold text-sm text-nearle-dark">
{sub}
</span>
</motion.div>
))}
</div>
</div>
<div className="mt-8 pt-6 border-t border-nearle-border flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="font-body text-xs text-nearle-light text-center sm:text-left">
Interested in joining as a partner? Click become a partner in the hero section or visit our businesses page.
</p>
</div>
</GlassCard>
</motion.div>
</AnimatePresence>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,102 +0,0 @@
'use client'
import dynamic from 'next/dynamic'
import { motion } from 'framer-motion'
import { fadeUp, viewportConfig } from '@/lib/animations'
const CityRider3D = dynamic(() => import('@/components/three/CityRider3D'), {
ssr: false,
loading: () => (
<div className="w-full h-full flex items-center justify-center bg-purple-soft/60 rounded-3xl">
<span className="font-mono text-xs text-purple-deep tracking-widest uppercase">
Loading scene
</span>
</div>
),
})
export function ConnectLocally() {
return (
<div className="relative py-20 sm:py-28 px-5 sm:px-8 bg-nearle-bgsoft overflow-hidden">
<div className="max-w-7xl mx-auto">
<div className="relative rounded-3xl overflow-hidden border border-nearle-border shadow-nearle-md bg-white">
{/* 3D scene */}
<div className="relative h-[480px] sm:h-[560px] lg:h-[620px]">
<CityRider3D />
{/* Top-left info card */}
<motion.div
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewportConfig}
className="absolute top-5 left-5 sm:top-7 sm:left-7 max-w-[300px] bg-white border border-nearle-border rounded-2xl px-4 py-3 shadow-nearle-md flex items-start gap-3"
>
<div className="w-9 h-9 rounded-xl bg-purple-soft flex items-center justify-center text-lg shrink-0">
🚚
</div>
<div>
<div className="label-mono text-purple-primary">Nearle Commerce</div>
<div className="font-display font-extrabold text-nearle-dark text-base leading-tight mt-0.5">
Connect Locally
</div>
<p className="font-body text-xs text-nearle-mid mt-1.5 leading-snug">
Households nearby discover your store and order in a few taps.
</p>
</div>
</motion.div>
{/* Top-right metrics card */}
<motion.div
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={viewportConfig}
className="absolute top-5 right-5 sm:top-7 sm:right-7 w-[260px] bg-white border border-nearle-border rounded-2xl p-4 shadow-nearle-md"
>
<div className="label-mono text-nearle-light">Neighbourhood Metrics</div>
<div className="mt-3 flex items-center justify-between">
<span className="font-body text-sm text-nearle-mid">Community Progress</span>
<span className="font-mono text-sm font-bold text-purple-primary">35%</span>
</div>
<div className="mt-1.5 h-1.5 rounded-full bg-purple-soft overflow-hidden">
<motion.div
className="h-full bg-gradient-purple"
initial={{ width: 0 }}
whileInView={{ width: '35%' }}
viewport={{ once: true }}
transition={{ duration: 1.2, ease: 'easeOut' }}
/>
</div>
<div className="mt-4 space-y-1.5 text-sm">
<div className="flex justify-between">
<span className="text-nearle-mid">Status</span>
<span className="font-bold text-nearle-dark">Building</span>
</div>
<div className="flex justify-between">
<span className="text-nearle-mid">Reach</span>
<span className="font-bold text-nearle-dark">Neighbourhood</span>
</div>
<div className="flex justify-between">
<span className="text-nearle-mid">Model</span>
<span className="font-bold text-nearle-dark">Hyper-local</span>
</div>
</div>
</motion.div>
{/* Bottom hint */}
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
className="absolute bottom-5 right-5 sm:bottom-7 sm:right-7 bg-white border border-nearle-border rounded-full px-4 py-2 shadow-nearle-sm font-body text-xs text-nearle-mid"
>
Scroll down to explore Nearle commerce
</motion.div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,146 +0,0 @@
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard'
import { Button } from '@/components/ui/Button'
export function Contact() {
const [email, setEmail] = useState('')
const [society, setSociety] = useState('')
const [submitted, setSubmitted] = useState(false)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (email) {
setSubmitted(true)
setTimeout(() => {
setSubmitted(false)
setEmail('')
setSociety('')
}, 4000)
}
}
return (
<div className="py-24 sm:py-32 bg-nearle-bgsoft px-5 sm:px-8 relative overflow-hidden">
{/* Decorative Blur Backgrounds */}
<div className="absolute top-1/4 left-1/4 w-[400px] h-[400px] rounded-full bg-purple-soft/30 blur-3xl -z-10 pointer-events-none" />
<div className="max-w-6xl mx-auto">
<div className="grid lg:grid-cols-12 gap-12 items-center">
{/* Left Column Text (5 cols) */}
<div className="lg:col-span-5 flex flex-col justify-center">
<SectionHeader
label="Join the Network"
heading={
<>
Request Nearle in
<br />
<span className="bg-gradient-purple bg-clip-text text-transparent">
Your Neighbourhood
</span>
</>
}
sub="Are you a society coordinator or a local merchant who wants to activate cooperative living? Reach out and we will geofence your area."
align="left"
className="mb-8"
/>
<div className="flex flex-col gap-5 font-body text-nearle-mid text-sm">
<div className="flex items-center gap-3">
<span className="w-10 h-10 rounded-xl bg-purple-soft text-purple-deep flex items-center justify-center text-lg">
📍
</span>
<div>
<p className="font-bold text-nearle-dark">HQ Location</p>
<p>RS Puram, Coimbatore, Tamil Nadu, India</p>
</div>
</div>
<div className="flex items-center gap-3">
<span className="w-10 h-10 rounded-xl bg-purple-soft text-purple-deep flex items-center justify-center text-lg">
</span>
<div>
<p className="font-bold text-nearle-dark">Official Email</p>
<p>partners@nearle.in · operations@nearle.in</p>
</div>
</div>
</div>
</div>
{/* Right Column Form (7 cols) */}
<div className="lg:col-span-7">
<GlassCard className="border-purple-lavender/50 hover:border-purple-primary shadow-nearle-md p-8 sm:p-10 relative overflow-hidden">
{submitted ? (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center py-12 flex flex-col items-center"
>
<span className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center text-3xl mb-6 shadow-sm">
</span>
<h3 className="font-display font-extrabold text-2xl text-nearle-dark">
Request Received!
</h3>
<p className="font-body text-nearle-mid text-sm leading-relaxed mt-4 max-w-sm">
Thank you! Our geofencing calibration team will contact you or your society management shortly to initiate the verification process.
</p>
</motion.div>
) : (
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
<div>
<label
htmlFor="email-address"
className="block font-display font-bold text-xs uppercase tracking-wider text-purple-deep mb-2"
>
Email Address
</label>
<input
id="email-address"
type="email"
required
placeholder="e.g. coordinator@society.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full bg-white border border-nearle-border rounded-xl px-4 py-3.5 text-sm focus:outline-none focus:border-purple-primary focus:ring-1 focus:ring-purple-primary transition-all duration-300"
/>
</div>
<div>
<label
htmlFor="society-name"
className="block font-display font-bold text-xs uppercase tracking-wider text-purple-deep mb-2"
>
Society or Complex Name
</label>
<input
id="society-name"
type="text"
placeholder="e.g. Sree Vatsa Residency, RS Puram"
value={society}
onChange={(e) => setSociety(e.target.value)}
className="w-full bg-white border border-nearle-border rounded-xl px-4 py-3.5 text-sm focus:outline-none focus:border-purple-primary focus:ring-1 focus:ring-purple-primary transition-all duration-300"
/>
</div>
<Button
type="submit"
variant="primary"
size="md"
className="w-full justify-center mt-2 shadow-cta hover:shadow-cta-hover transition-all"
>
Send Society Request
</Button>
</form>
)}
</GlassCard>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,8 +1,5 @@
'use client' 'use client'
import { motion } from 'framer-motion'
import { fadeUp, staggerContainer, viewportConfig } from '@/lib/animations'
export function Download() { export function Download() {
return ( return (
<section id="download" className="py-24 sm:py-32 bg-white relative overflow-hidden px-5 sm:px-8 border-t border-purple-soft"> <section id="download" className="py-24 sm:py-32 bg-white relative overflow-hidden px-5 sm:px-8 border-t border-purple-soft">

View File

@@ -1,107 +0,0 @@
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard'
type FAQItem = {
question: string
answer: string
}
const FAQS: FAQItem[] = [
{
question: 'What is Nearle?',
answer: 'Nearle is Indias first premium managed smart hyperlocal neighbourhood platform. We map out geofenced societies and connect residents directly to verified local merchants, service technicians, and private community deals in a unified digital ecosystem.',
},
{
question: 'How does hyperlocal delivery work?',
answer: 'Once an order is placed on the Nearle Deal app, nearby partner stores receive instant geofenced alerts. Dedicated Nearle Gear riders use optimised routing navigation to collect the packed package and complete the delivery in under 15-20 minutes.',
},
{
question: 'How can local businesses and shops join?',
answer: 'Local shops, supermarkets, restaurants, and freelance service providers can apply via the Nearle Partner app. After quick boundary verification and catalog sync, they go live in the local geofenced mesh.',
},
{
question: 'Which cooperative societies and communities are supported?',
answer: 'Currently we are actively activating major societies and gated complexes in Coimbatore. If your society is not geofenced yet, you can send an onboarding request via the coordinator hub inside the Admin application.',
},
{
question: 'Is delivery available 24/7?',
answer: 'Our smart network works matching store opening timings. Most essential grocery and food categories operate from 7:00 AM to 11:00 PM, while instant peer-to-peer courier options remain active in key zones 24/7.',
},
]
export function FAQ() {
const [openIdx, setOpenIdx] = useState<number | null>(0) // Default expand first question
const toggle = (idx: number) => {
setOpenIdx(openIdx === idx ? null : idx)
}
return (
<div className="py-24 sm:py-32 bg-white px-5 sm:px-8 relative overflow-hidden">
{/* Decorative Blur BG */}
<div className="absolute bottom-1/4 left-1/4 w-[500px] h-[500px] rounded-full bg-purple-soft/30 blur-3xl -z-10 pointer-events-none" />
<div className="max-w-4xl mx-auto">
<SectionHeader
label="Help Center"
heading={
<>
Frequently Asked
<br />
<span className="bg-gradient-purple bg-clip-text text-transparent">
Questions
</span>
</>
}
sub="Everything you need to know about the Nearle hyperlocal platform, society geofencing, and merchant tools."
align="center"
className="mb-16 sm:mb-20"
/>
<div className="flex flex-col gap-4">
{FAQS.map((faq, idx) => {
const isOpen = openIdx === idx
return (
<GlassCard
key={idx}
hover={false}
className="border-purple-lavender/50 hover:border-purple-primary transition-all duration-300 p-0 overflow-hidden shadow-nearle-sm"
>
<button
onClick={() => toggle(idx)}
className="w-full text-left p-6 sm:p-8 flex justify-between items-center gap-4 focus:outline-none select-none group"
>
<span className="font-display font-bold text-base sm:text-lg text-nearle-dark group-hover:text-purple-deep transition-colors duration-200">
{faq.question}
</span>
<span className={`w-8 h-8 rounded-full bg-purple-soft flex items-center justify-center text-purple-deep transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}>
</span>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
>
<div className="px-6 pb-6 sm:px-8 sm:pb-8 font-body text-nearle-mid text-sm sm:text-base leading-relaxed border-t border-nearle-border/40 pt-4 bg-[#FCFAFD]/50">
{faq.answer}
</div>
</motion.div>
)}
</AnimatePresence>
</GlassCard>
)
})}
</div>
</div>
</div>
)
}

View File

@@ -41,7 +41,8 @@ export function Hero() {
variants={fadeUp} variants={fadeUp}
className="h1-display text-nearle-dark mt-5" className="h1-display text-nearle-dark mt-5"
> >
Your Neighbourhood, <span className="block sm:inline">Your</span>
<span className="block sm:inline"> Neighbourhood,</span>
<br /> <br />
<span className="bg-gradient-purple bg-clip-text text-transparent"> <span className="bg-gradient-purple bg-clip-text text-transparent">
Your World Your World
@@ -76,7 +77,7 @@ export function Hero() {
</motion.div> </motion.div>
</motion.div> </motion.div>
<div className="lg:col-span-5 relative h-[440px] sm:h-[520px] lg:h-[560px]"> <div className="lg:col-span-5 relative h-[420px] sm:h-[520px] lg:h-[560px] overflow-hidden sm:overflow-visible">
<HeroPhones3D /> <HeroPhones3D />
</div> </div>
</div> </div>
@@ -105,4 +106,3 @@ function FloatingBlobs() {
</div> </div>
) )
} }

View File

@@ -124,7 +124,7 @@ export function NeighbourhoodMap() {
</div> </div>
</div> </div>
<div className="grid lg:grid-cols-12 gap-8 items-stretch"> <div className="grid lg:grid-cols-12 gap-6 lg:gap-8 items-stretch">
{/* DETAIL CARD (4 cols) */} {/* DETAIL CARD (4 cols) */}
<div className="lg:col-span-4"> <div className="lg:col-span-4">
<GlassCard className="h-full flex flex-col border-purple-lavender/50 shadow-nearle-md"> <GlassCard className="h-full flex flex-col border-purple-lavender/50 shadow-nearle-md">
@@ -197,9 +197,9 @@ export function NeighbourhoodMap() {
</div> </div>
{/* 3D MAP CANVAS (8 cols) */} {/* 3D MAP CANVAS (8 cols) */}
<div className="lg:col-span-8 relative aspect-[4/3] sm:aspect-[16/11] bg-white rounded-3xl border border-nearle-border shadow-nearle-md overflow-hidden"> <div className="lg:col-span-8 relative min-h-[360px] aspect-[10/11] sm:aspect-[16/11] bg-white rounded-3xl border border-nearle-border shadow-nearle-md overflow-hidden">
{/* Legend chips */} {/* Legend chips */}
<div className="absolute top-4 left-4 z-10 flex flex-col gap-2"> <div className="absolute top-3 left-3 right-3 sm:top-4 sm:left-4 sm:right-auto z-10 flex flex-wrap sm:flex-col gap-2">
<LegendChip color="#683285" label="Food Essentials / Shops" /> <LegendChip color="#683285" label="Food Essentials / Shops" />
<LegendChip color="#AE79BF" label="Active Restaurants" /> <LegendChip color="#AE79BF" label="Active Restaurants" />
<LegendChip color="#3B82F6" label="People / Neighbourhoods" emoji="🏠" /> <LegendChip color="#3B82F6" label="People / Neighbourhoods" emoji="🏠" />

View File

@@ -1,176 +0,0 @@
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard'
import { Badge } from '@/components/ui/Badge'
import { fadeUp, staggerContainer, viewportConfig } from '@/lib/animations'
type RoadmapStep = {
phase: string
title: string
desc: string
metrics: string[]
icon: string
status: 'completed' | 'current' | 'upcoming'
}
const ROADMAP: RoadmapStep[] = [
{
phase: 'Phase 01',
title: 'Society Geofencing',
desc: 'Cooperative societies sign partnership agreements. Geofences are digitally matching with strict boundary lines.',
metrics: ['99.8% Geofence Accuracy', 'Escrow Account Mappings', 'Resident Onboarding'],
icon: '🗺️',
status: 'completed',
},
{
phase: 'Phase 02',
title: 'Merchant Integration',
desc: 'Onboarding nearby grocery shops, premium restaurants, pharmacies, and trusted home service professionals.',
metrics: ['Catalog Editor Sync', 'Fast Merchant Approvals', 'API Device Activation'],
icon: '🏪',
status: 'current',
},
{
phase: 'Phase 03',
title: 'Fleet Calibration',
desc: 'Activating Nearle Gear delivery riders. Dispatch rules are optimized with real-time navigation tools.',
metrics: ['Optimal Path Training', 'Equipment Issuance', 'Payout System Verified'],
icon: '🛵',
status: 'upcoming',
},
{
phase: 'Phase 04',
title: 'Go-Live society Launch',
desc: 'Platform soft launch within the geofenced society limits accompanied by exclusive society discounts.',
metrics: ['Society Launch Event', 'Support Centers Active', '12-Minute Deliveries'],
icon: '🔮',
status: 'upcoming',
},
]
export function OperationalPlan() {
const [activeStep, setActiveStep] = useState<number>(1) // Default to active step (Phase 02)
return (
<div className="py-24 sm:py-32 bg-nearle-bgsoft px-5 sm:px-8 relative overflow-hidden">
{/* Decorative vectors */}
<div className="absolute inset-0 bg-grid-soft opacity-30 pointer-events-none" />
<div className="max-w-7xl mx-auto">
<SectionHeader
label="Execution Blueprint"
heading={
<>
Our Operational
<br />
<span className="bg-gradient-purple bg-clip-text text-transparent">
Launch Roadmap
</span>
</>
}
sub="Watch how Nearle systematically activates cooperative societies and curates merchant networks."
align="center"
className="mb-16 sm:mb-20"
/>
<div className="grid lg:grid-cols-12 gap-8 items-stretch">
{/* Timeline side controls (5 cols) */}
<div className="lg:col-span-5 flex flex-col gap-4">
{ROADMAP.map((step, idx) => {
const isActive = activeStep === idx
return (
<button
key={step.phase}
onClick={() => setActiveStep(idx)}
className={`text-left p-5 rounded-2xl border transition-all duration-300 focus:outline-none flex items-center justify-between ${
isActive
? 'bg-purple-deep text-white border-purple-deep shadow-nearle-md'
: 'bg-white border-nearle-border hover:border-purple-primary text-nearle-dark'
}`}
>
<div className="flex items-center gap-4">
<span className="text-2xl">{step.icon}</span>
<div>
<span className={`text-[10px] font-mono font-bold tracking-widest ${isActive ? 'text-purple-lavender' : 'text-purple-primary'}`}>
{step.phase}
</span>
<h4 className="font-display font-bold text-base mt-0.5">
{step.title}
</h4>
</div>
</div>
{step.status === 'completed' && (
<span className={`text-[10px] font-mono uppercase font-bold tracking-wider px-2 py-0.5 rounded-full ${isActive ? 'bg-white/20 text-white' : 'bg-green-100 text-green-700'}`}>
Completed
</span>
)}
{step.status === 'current' && (
<span className={`text-[10px] font-mono uppercase font-bold tracking-wider px-2 py-0.5 rounded-full animate-pulse ${isActive ? 'bg-white/20 text-white' : 'bg-purple-lavender text-purple-deep'}`}>
Active
</span>
)}
</button>
)
})}
</div>
{/* Timeline detail container (7 cols) */}
<div className="lg:col-span-7">
<AnimatePresence mode="wait">
<motion.div
key={activeStep}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
className="h-full"
>
<GlassCard className="h-full p-8 flex flex-col justify-between border-purple-lavender/50 hover:border-purple-primary shadow-nearle-md">
<div>
<span className="text-xs font-mono font-bold text-purple-primary uppercase tracking-widest block mb-2">
Active Operational phase
</span>
<h3 className="h3-display text-nearle-dark mb-4">
{ROADMAP[activeStep]!.title}
</h3>
<p className="font-body text-nearle-mid text-sm leading-relaxed mb-8">
{ROADMAP[activeStep]!.desc}
</p>
<h4 className="font-display font-bold text-xs uppercase text-purple-deep mb-4 tracking-wider">
Key Deliverables & Metrics
</h4>
<div className="grid sm:grid-cols-2 gap-4">
{ROADMAP[activeStep]!.metrics.map((metric) => (
<div
key={metric}
className="flex items-center gap-3 p-4 bg-purple-soft/40 border border-purple-lavender/20 rounded-xl"
>
<span className="w-1.5 h-1.5 rounded-full bg-purple-primary" />
<span className="font-display font-bold text-xs text-nearle-dark">
{metric}
</span>
</div>
))}
</div>
</div>
<div className="mt-8 pt-6 border-t border-nearle-border flex justify-between items-center flex-wrap gap-4">
<p className="font-body text-xs text-nearle-light">
* Launch timings subject to merchant onboarding rate and society density geofencing tests.
</p>
</div>
</GlassCard>
</motion.div>
</AnimatePresence>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,358 +0,0 @@
'use client'
import { useEffect, useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
type Node = {
icon: string
title: string
desc: string
metric: string
log: string
}
const NODES: Node[] = [
{
icon: '📱',
title: 'Customer Books',
desc: 'Resident places order via Nearle Deal app',
metric: 'Active Status',
log: '// Geofence matched. Nearest 3 stores pinged...',
},
{
icon: '🏪',
title: 'Store Receives',
desc: 'Nearest verified store gets instant notification',
metric: 'Verified Store',
log: '// Push notification delivered. ETA: 45s...',
},
{
icon: '✔️',
title: 'Store Confirms',
desc: 'Store accepts and begins packing the order',
metric: 'Live Packing',
log: '// Inventory verified. Packing initiated...',
},
{
icon: '💳',
title: 'Payment Processed',
desc: 'Secure digital settlement: 95% merchant, 5% fleet',
metric: 'Direct Settlement',
log: '// Escrow verified. Payout queued...',
},
{
icon: '🏍️',
title: 'Rider Picks Up',
desc: 'Gear rider collects and begins last-mile delivery',
metric: 'Route Optimized',
log: '// Route optimized. Rider en route...',
},
{
icon: '🏠',
title: 'Delivered',
desc: "Order reaches resident's door. Trust confirmed.",
metric: '< 30 min',
log: '// Delivery confirmed. Rating prompt sent...',
},
]
function TypewriterLog({ text }: { text: string }) {
const [displayed, setDisplayed] = useState('')
useEffect(() => {
setDisplayed('')
let idx = 0
const interval = setInterval(() => {
setDisplayed((d) => d + text.charAt(idx))
idx++
if (idx >= text.length) {
clearInterval(interval)
}
}, 20)
return () => clearInterval(interval)
}, [text])
return (
<span className="font-mono text-[11px] sm:text-xs flex items-center gap-1.5 text-emerald-400">
<span>{displayed}</span>
<span className="w-1.5 h-3.5 bg-emerald-400 inline-block shrink-0 animate-[blink_1s_infinite]" />
<style dangerouslySetInnerHTML={{ __html: `
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
`}} />
</span>
)
}
function DetailsCard({ activeNode, activeIndex }: { activeNode: Node; activeIndex: number }) {
const [rotate, setRotate] = useState({ x: 0, y: 0 })
const [shine, setShine] = useState({ x: 0, y: 0, opacity: 0 })
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
const el = e.currentTarget
const r = el.getBoundingClientRect()
const x = e.clientX - r.left
const y = e.clientY - r.top
// Smooth angle calculations (tilt range -6 to 6 deg)
const rx = -((y / r.height) - 0.5) * 12
const ry = ((x / r.width) - 0.5) * 12
setRotate({ x: rx, y: ry })
setShine({ x, y, opacity: 0.15 })
}
function handleMouseLeave() {
setRotate({ x: 0, y: 0 })
setShine({ x: 0, y: 0, opacity: 0 })
}
return (
<motion.div
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{
transformStyle: 'preserve-3d',
transform: `perspective(1000px) rotateX(${rotate.x}deg) rotateY(${rotate.y}deg)`,
transition: 'transform 0.1s ease-out',
}}
className="relative bg-white rounded-3xl border border-nearle-border shadow-nearle-sm p-8 overflow-hidden select-none"
>
{/* Light shine overlay */}
<div
className="absolute pointer-events-none rounded-full"
style={{
width: '320px',
height: '320px',
background: 'radial-gradient(circle, rgba(164,103,183,0.15) 0%, rgba(255,255,255,0) 70%)',
left: `${shine.x - 160}px`,
top: `${shine.y - 160}px`,
opacity: shine.opacity,
transition: 'opacity 0.2s ease',
}}
/>
<div className="flex items-center justify-between flex-wrap gap-3" style={{ transform: 'translateZ(10px)' }}>
<span className="font-mono text-xs tracking-widest uppercase text-purple-primary">
Step 0{activeIndex + 1} of 06
</span>
<span className="bg-purple-soft text-purple-deep border border-purple-lavender/30 rounded-full px-3 py-1 font-mono text-[11px] tracking-widest uppercase shadow-nearle-xs">
{activeNode.metric}
</span>
</div>
<h3 className="h3-display text-nearle-dark mt-4" style={{ transform: 'translateZ(20px)' }}>
{activeNode.title}
</h3>
<p className="font-body text-nearle-mid mt-3 leading-relaxed" style={{ transform: 'translateZ(15px)' }}>
{activeNode.desc}
</p>
<div
className="mt-6 inline-flex items-center bg-[#0C0614] text-emerald-300 rounded-full px-5 py-2.5 font-mono text-xs shadow-nearle-sm border border-purple-deep/10"
style={{ transform: 'translateZ(25px)' }}
>
<TypewriterLog text={activeNode.log} />
</div>
</motion.div>
)
}
export function OrderLifecycle() {
const [active, setActive] = useState(0)
const [prevActive, setPrevActive] = useState(0)
const [tilt, setTilt] = useState(0)
useEffect(() => {
const id = setInterval(() => {
setActive((a) => (a + 1) % NODES.length)
}, 4500)
return () => clearInterval(id)
}, [])
useEffect(() => {
if (active > prevActive) {
setTilt(12) // Lean forward
const t = setTimeout(() => setTilt(0), 650)
return () => clearTimeout(t)
} else if (active < prevActive) {
setTilt(-12) // Lean backward
const t = setTimeout(() => setTilt(0), 650)
return () => clearTimeout(t)
}
}, [active, prevActive])
// Track previous step to identify travel direction
useEffect(() => {
setPrevActive(active)
}, [active])
return (
<div className="bg-nearle-bgsoft py-24 sm:py-32 px-5 sm:px-8 relative overflow-hidden">
<div className="absolute inset-0 bg-grid-soft opacity-30 pointer-events-none" />
<div className="max-w-7xl mx-auto relative z-10">
<SectionHeader
label="Order Lifecycle"
heading="How your order travels"
sub="Watch the real order journey — the Nearle delivery chain"
align="center"
/>
<div className="mt-20 relative">
{/* Animated 3D/gliding road track for desktop views */}
<div className="hidden md:block absolute top-[36px] left-[8.33%] right-[8.33%] h-2 -translate-y-1/2">
<svg width="100%" height="8" className="overflow-visible">
<defs>
<linearGradient id="road-progress-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#683285" />
<stop offset="100%" stopColor="#A467B7" />
</linearGradient>
</defs>
{/* Ground Road Track */}
<line
x1="0"
y1="4"
x2="100%"
y2="4"
stroke="#F1EEF4"
strokeWidth="6"
strokeLinecap="round"
/>
{/* Dashlane inside road */}
<line
x1="0"
y1="4"
x2="100%"
y2="4"
stroke="#E2D6EC"
strokeWidth="2"
strokeDasharray="4 6"
/>
{/* Dynamic Fills Glowing Progress */}
<motion.line
x1="0"
y1="4"
x2={`${(active / 5) * 100}%`}
y2="4"
stroke="url(#road-progress-grad)"
strokeWidth="6"
strokeLinecap="round"
animate={{ x2: `${(active / 5) * 100}%` }}
transition={{ type: 'spring', stiffness: 90, damping: 16 }}
/>
</svg>
{/* Gliding scooter rider on road */}
<motion.div
className="absolute top-1/2 w-10 h-10 rounded-full bg-white border-2 border-purple-primary shadow-[0_4px_16px_rgba(104,50,133,0.22)] flex items-center justify-center text-xl z-20 overflow-hidden"
style={{
y: '-50%',
x: '-50%',
}}
animate={{
left: `${(active / 5) * 100}%`,
rotate: tilt,
}}
transition={{
left: { type: 'spring', stiffness: 90, damping: 16 },
rotate: { type: 'spring', stiffness: 100, damping: 10 },
}}
>
<AnimatePresence mode="wait">
<motion.span
key={active}
initial={{ scale: 0, rotate: -45, opacity: 0 }}
animate={{ scale: 1, rotate: 0, opacity: 1 }}
exit={{ scale: 0, rotate: 45, opacity: 0 }}
transition={{ duration: 0.22, ease: 'easeOut' }}
className="inline-block"
>
{active === 0 && '📱'}
{active === 1 && '🏪'}
{active === 2 && '📦'}
{active === 3 && '💳'}
{active === 4 && '🛵'}
{active === 5 && '🛵'}
</motion.span>
</AnimatePresence>
</motion.div>
</div>
<ol className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-y-10 gap-x-4 relative">
{NODES.map((node, i) => (
<li
key={node.title}
className="flex flex-col items-center text-center cursor-pointer group"
onClick={() => {
setPrevActive(active)
setActive(i)
}}
>
<div className="relative">
<motion.div
animate={{
scale: i === active ? 1.08 : 1,
y: i === active ? [0, -3, 0] : 0,
}}
transition={{
scale: { duration: 0.35 },
y: i === active ? { duration: 3, repeat: Infinity, ease: 'easeInOut' } : { duration: 0.3 },
}}
className={`relative z-10 w-18 h-18 rounded-full flex items-center justify-center text-2xl border-2 transition-all ${
i === active
? 'bg-purple-deep text-white border-purple-deep shadow-[0_8px_20px_rgba(104,50,133,0.25)]'
: i < active
? 'bg-purple-soft/90 text-purple-deep border-purple-primary/40 shadow-nearle-xs'
: 'bg-white/70 backdrop-blur-sm text-nearle-mid border-nearle-border hover:border-purple-primary hover:bg-white'
}`}
>
<span>{node.icon}</span>
</motion.div>
{i === active && (
<>
<motion.span
className="absolute inset-0 rounded-full border-2 border-purple-primary"
animate={{ scale: [1, 1.35], opacity: [0.6, 0] }}
transition={{ duration: 1.8, repeat: Infinity, ease: 'easeOut' }}
/>
<motion.span
className="absolute inset-0 rounded-full border-2 border-purple-primary"
animate={{ scale: [1, 1.6], opacity: [0.35, 0] }}
transition={{ duration: 1.8, repeat: Infinity, ease: 'easeOut', delay: 0.5 }}
/>
</>
)}
</div>
<span className="font-mono text-[9px] text-purple-primary tracking-widest uppercase mt-4 font-bold">
Step 0{i + 1}
</span>
<span className="font-display font-extrabold text-sm text-nearle-dark mt-1 group-hover:text-purple-primary transition-colors">
{node.title}
</span>
</li>
))}
</ol>
</div>
<div className="mt-16 max-w-3xl mx-auto">
<AnimatePresence mode="wait">
<motion.div
key={active}
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
transition={{ duration: 0.3 }}
>
<DetailsCard activeNode={NODES[active]!} activeIndex={active} />
</motion.div>
</AnimatePresence>
</div>
</div>
</div>
)
}

View File

@@ -1,94 +0,0 @@
'use client'
import { motion } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard'
import { fadeUp, staggerContainer, viewportConfig } from '@/lib/animations'
type StatItem = {
value: string
label: string
desc: string
icon: string
}
const STATS: StatItem[] = [
{
value: '10,000+',
label: 'Active Residents',
desc: 'Empowering local communities with seamless smart living amenities.',
icon: '👥',
},
{
value: '5,000+',
label: 'Completed Deliveries',
desc: 'Completed in record time across Coimbatore societies.',
icon: '🛵',
},
{
value: '1,000+',
label: 'Verified Businesses',
desc: 'Directly supporting neighborhood shops and service providers.',
icon: '🏪',
},
{
value: '25+',
label: 'Connected Communities',
desc: 'Integrating diverse cooperative societies in Coimbatore.',
icon: '🏡',
},
]
export function Stats() {
return (
<div className="py-24 sm:py-32 bg-white px-5 sm:px-8 relative overflow-hidden">
{/* Decorative Blob */}
<div className="absolute top-1/4 right-0 w-[500px] h-[500px] rounded-full bg-purple-soft/40 blur-3xl -z-10 pointer-events-none" />
<div className="max-w-7xl mx-auto">
<SectionHeader
label="Proven Scale"
heading={
<>
Powering Cooperative
<br />
<span className="bg-gradient-purple bg-clip-text text-transparent">
Living at Scale
</span>
</>
}
sub="Our growing network makes sure that residents get access to everything they need quickly and reliably."
align="center"
className="mb-16 sm:mb-20"
/>
<motion.div
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewportConfig}
className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8"
>
{STATS.map((stat) => (
<motion.div key={stat.label} variants={fadeUp} className="group">
<GlassCard className="h-full flex flex-col justify-between border-purple-lavender/50 hover:border-purple-primary shadow-nearle-sm group-hover:shadow-nearle-md group-hover:scale-[1.02] p-8">
<div>
<span className="text-3xl block mb-4">{stat.icon}</span>
<p className="font-display font-extrabold text-4xl sm:text-5xl text-purple-deep tracking-tight bg-gradient-purple bg-clip-text text-transparent">
{stat.value}
</p>
<h4 className="font-display font-bold text-base text-nearle-dark mt-3">
{stat.label}
</h4>
</div>
<p className="font-body text-nearle-mid text-xs leading-relaxed mt-4 pt-4 border-t border-nearle-border/40">
{stat.desc}
</p>
</GlassCard>
</motion.div>
))}
</motion.div>
</div>
</div>
)
}

View File

@@ -4,7 +4,6 @@ import { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { SectionHeader } from '@/components/ui/SectionHeader' import { SectionHeader } from '@/components/ui/SectionHeader'
import { GlassCard } from '@/components/ui/GlassCard' import { GlassCard } from '@/components/ui/GlassCard'
import { fadeUp, scaleUp, viewportConfig } from '@/lib/animations'
type Testimonial = { type Testimonial = {
quote: string quote: string

View File

@@ -1,19 +1,14 @@
import Link from 'next/link' import Link from 'next/link'
import { Instagram, Twitter, Linkedin, Youtube } from 'lucide-react'
import { NearleLogo } from '@/components/ui/NearleLogo' import { NearleLogo } from '@/components/ui/NearleLogo'
const company = [ const company = [
{ label: 'About Us', href: '/about' }, { label: 'About Us', href: '/about' },
{ label: 'Communities', href: '/communities' }, { label: 'Communities', href: '/communities' },
{ label: 'Blog', href: '#' }, { label: 'For Businesses', href: '/businesses' },
{ label: 'Careers', href: '#' },
] ]
const support = [ const support = [
{ label: 'FAQ', href: '/#faq' },
{ label: 'Contact Us', href: '/contact' }, { label: 'Contact Us', href: '/contact' },
{ label: 'Privacy Policy', href: '#' },
{ label: 'Terms & Conditions', href: '#' },
] ]
export function Footer() { export function Footer() {
@@ -27,20 +22,6 @@ export function Footer() {
<p className="font-body text-nearle-light mt-4 max-w-xs"> <p className="font-body text-nearle-light mt-4 max-w-xs">
Your Neighbourhood, Your World. Your Neighbourhood, Your World.
</p> </p>
<div className="flex items-center gap-4 mt-6 text-nearle-light">
<a aria-label="Instagram" href="#" className="hover:text-purple-primary transition">
<Instagram size={20} />
</a>
<a aria-label="Twitter / X" href="#" className="hover:text-purple-primary transition">
<Twitter size={20} />
</a>
<a aria-label="LinkedIn" href="#" className="hover:text-purple-primary transition">
<Linkedin size={20} />
</a>
<a aria-label="YouTube" href="#" className="hover:text-purple-primary transition">
<Youtube size={20} />
</a>
</div>
</div> </div>
<FooterCol title="Company" links={company} /> <FooterCol title="Company" links={company} />

File diff suppressed because it is too large Load Diff

View File

@@ -208,6 +208,7 @@ function Phone({
translateY, translateY,
translateZ, translateZ,
delay, delay,
className = '',
}: { }: {
variant: ScreenProps['variant'] variant: ScreenProps['variant']
rotateY: number rotateY: number
@@ -215,6 +216,7 @@ function Phone({
translateY: number translateY: number
translateZ: number translateZ: number
delay: number delay: number
className?: string
}) { }) {
return ( return (
<div <div
@@ -222,7 +224,7 @@ function Phone({
transformStyle: 'preserve-3d', transformStyle: 'preserve-3d',
transform: `translate3d(${translateX}px, ${translateY}px, ${translateZ}px) rotateY(${rotateY}deg) translate(-50%, -50%)`, transform: `translate3d(${translateX}px, ${translateY}px, ${translateZ}px) rotateY(${rotateY}deg) translate(-50%, -50%)`,
}} }}
className="absolute top-1/2 left-1/2" className={`absolute top-1/2 left-1/2 ${className}`}
> >
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
@@ -238,19 +240,17 @@ function Phone({
ease: 'easeInOut', ease: 'easeInOut',
delay, delay,
}} }}
className="relative" className="relative w-[198px] h-[396px] sm:w-[224px] sm:h-[448px]"
style={{ style={{
width: 198, filter: 'drop-shadow(0 30px 50px rgba(104,50,133,0.24)) contrast(1.06) saturate(1.08)',
height: 396,
filter: 'drop-shadow(0 30px 50px rgba(104,50,133,0.18))',
}} }}
> >
{/* Phone frame */} {/* Phone frame */}
<div <div
className="absolute inset-0 rounded-[34px] bg-white" className="absolute inset-0 rounded-[34px] bg-white"
style={{ style={{
border: '1.5px solid #DDD8E5', border: '1.5px solid #D7C6E0',
boxShadow: 'inset 0 0 0 6px #F5EEF8, 0 16px 40px -10px rgba(104,50,133,0.20)', boxShadow: 'inset 0 0 0 6px #F7EFFA, 0 18px 44px -10px rgba(104,50,133,0.26)',
}} }}
/> />
{/* Notch */} {/* Notch */}
@@ -301,7 +301,7 @@ export default function HeroPhones3D() {
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }} transition={{ duration: 0.5, delay: 0.2 }}
className="absolute top-2 left-2 z-20 flex items-center gap-2 bg-white border border-[#E7D3EF] rounded-full pl-2 pr-3 py-1.5 shadow-[0_4px_16px_rgba(164,103,183,0.10)]" className="absolute top-2 left-3 right-3 sm:left-2 sm:right-auto z-20 flex items-center gap-2 bg-white border border-[#E7D3EF] rounded-full pl-2 pr-3 py-1.5 shadow-[0_4px_16px_rgba(164,103,183,0.10)]"
> >
<span className="w-5 h-5 rounded-full bg-[#F5EEF8] flex items-center justify-center text-[10px]">🏅</span> <span className="w-5 h-5 rounded-full bg-[#F5EEF8] flex items-center justify-center text-[10px]">🏅</span>
<span className="font-display font-extrabold text-[#111827] text-[11px] leading-none"> <span className="font-display font-extrabold text-[#111827] text-[11px] leading-none">
@@ -318,9 +318,9 @@ export default function HeroPhones3D() {
transition: 'transform 0.25s ease-out', transition: 'transform 0.25s ease-out',
}} }}
> >
<Phone variant="map" rotateY={16} translateX={-220} translateY={10} translateZ={-50} delay={0.05} /> <Phone className="hidden sm:block" variant="map" rotateY={16} translateX={-220} translateY={10} translateZ={-50} delay={0.05} />
<Phone variant="home" rotateY={0} translateX={0} translateY={-10} translateZ={40} delay={0.18} /> <Phone variant="home" rotateY={0} translateX={0} translateY={-10} translateZ={40} delay={0.18} />
<Phone variant="chat" rotateY={-16} translateX={220} translateY={10} translateZ={-50} delay={0.3} /> <Phone className="hidden sm:block" variant="chat" rotateY={-16} translateX={220} translateY={10} translateZ={-50} delay={0.3} />
</div> </div>
</div> </div>
) )

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import { motion, AnimatePresence } from 'framer-motion' import { motion } from 'framer-motion'
type IsoNode = { type IsoNode = {
id: string id: string
@@ -44,7 +44,7 @@ export default function IsoMap3D({
const scooterTarget = NODES.find((n) => n.id === selectedId) const scooterTarget = NODES.find((n) => n.id === selectedId)
return ( return (
<div className="relative w-full h-full bg-[#FAF9FC] overflow-hidden select-none select-none"> <div className="relative w-full h-full bg-[#FAF9FC] overflow-hidden select-none">
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `
@keyframes flow { @keyframes flow {
to { to {
@@ -208,7 +208,7 @@ export default function IsoMap3D({
{/* Central Icon Circle */} {/* Central Icon Circle */}
<div <div
className={`relative w-12 h-12 rounded-2xl flex items-center justify-center text-2xl border transition-all duration-300 ${ className={`relative w-10 h-10 sm:w-12 sm:h-12 rounded-2xl flex items-center justify-center text-xl sm:text-2xl border transition-all duration-300 ${
isActive isActive
? 'bg-purple-deep border-purple-deep text-white shadow-[0_6px_20px_rgba(104,50,133,0.25)]' ? 'bg-purple-deep border-purple-deep text-white shadow-[0_6px_20px_rgba(104,50,133,0.25)]'
: 'bg-white border-[#E7D3EF]/85 text-nearle-dark group-hover:border-purple-primary/60 group-hover:shadow-nearle-sm' : 'bg-white border-[#E7D3EF]/85 text-nearle-dark group-hover:border-purple-primary/60 group-hover:shadow-nearle-sm'
@@ -220,7 +220,7 @@ export default function IsoMap3D({
{/* Label Badge underneath node */} {/* Label Badge underneath node */}
<div <div
className={`mt-2.5 whitespace-nowrap border rounded-full px-3 py-1 font-body text-[10px] font-extrabold shadow-nearle-xs transition-all duration-300 ${ className={`mt-2 hidden sm:block whitespace-nowrap border rounded-full px-3 py-1 font-body text-[10px] font-extrabold shadow-nearle-xs transition-all duration-300 ${
isActive isActive
? 'bg-purple-deep border-purple-deep text-white shadow-[0_4px_12px_rgba(104,50,133,0.18)]' ? 'bg-purple-deep border-purple-deep text-white shadow-[0_4px_12px_rgba(104,50,133,0.18)]'
: 'bg-white border-[#E7D3EF] text-nearle-dark group-hover:border-purple-primary group-hover:text-purple-primary font-bold' : 'bg-white border-[#E7D3EF] text-nearle-dark group-hover:border-purple-primary group-hover:text-purple-primary font-bold'
@@ -235,7 +235,7 @@ export default function IsoMap3D({
</div> </div>
{/* Helper hint badge */} {/* Helper hint badge */}
<div className="absolute bottom-4 right-4 flex items-center gap-2 bg-white border border-[#E7D3EF] rounded-full px-3.5 py-1.5 shadow-[0_4px_16px_rgba(164,103,183,0.08)] select-none z-30"> <div className="absolute bottom-3 left-1/2 sm:left-auto sm:bottom-4 sm:right-4 -translate-x-1/2 sm:translate-x-0 flex items-center gap-2 bg-white border border-[#E7D3EF] rounded-full px-3.5 py-1.5 shadow-[0_4px_16px_rgba(164,103,183,0.08)] select-none z-30">
<span className="text-[12px] animate-bounce">📍</span> <span className="text-[12px] animate-bounce">📍</span>
<span className="font-body text-[11px] font-bold text-nearle-dark tracking-wide uppercase"> <span className="font-body text-[11px] font-bold text-nearle-dark tracking-wide uppercase">
Neighbourhood Plan Neighbourhood Plan

View File

@@ -1,20 +0,0 @@
type Props = {
children: React.ReactNode
className?: string
tone?: 'lavender' | 'deep' | 'outline'
}
export function Badge({ children, className = '', tone = 'lavender' }: Props) {
const tones = {
lavender: 'bg-purple-lavender text-purple-deep',
deep: 'bg-purple-deep text-white',
outline: 'border border-purple-deep text-purple-deep',
}
return (
<span
className={`inline-flex items-center font-mono text-[11px] tracking-widest uppercase rounded-full px-3 py-1 ${tones[tone]} ${className}`}
>
{children}
</span>
)
}

View File

@@ -1,57 +0,0 @@
import { forwardRef } from 'react'
import type { ButtonHTMLAttributes, AnchorHTMLAttributes } from 'react'
type Variant = 'primary' | 'secondary' | 'ghost'
const base =
'inline-flex items-center justify-center gap-2 font-body font-semibold rounded-full transition-all duration-300 active:scale-95 disabled:opacity-60 disabled:cursor-not-allowed'
const sizes = {
sm: 'px-4 py-2 text-sm',
md: 'px-7 py-3 text-base',
lg: 'px-8 py-3.5 text-base',
}
const variants: Record<Variant, string> = {
primary:
'bg-purple-deep text-white shadow-cta hover:bg-purple-primary hover:shadow-cta-hover',
secondary:
'border-2 border-purple-deep text-purple-deep bg-transparent hover:bg-purple-deep hover:text-white',
ghost:
'text-purple-deep hover:bg-purple-soft',
}
type CommonProps = {
variant?: Variant
size?: keyof typeof sizes
}
export type ButtonProps = CommonProps &
ButtonHTMLAttributes<HTMLButtonElement> & { as?: 'button' }
export type AnchorProps = CommonProps &
AnchorHTMLAttributes<HTMLAnchorElement> & { as: 'a'; href: string }
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', className = '', ...rest }, ref) => (
<button
ref={ref}
className={`${base} ${sizes[size]} ${variants[variant]} ${className}`}
{...rest}
/>
)
)
Button.displayName = 'Button'
export function ButtonLink({
variant = 'primary',
size = 'md',
className = '',
...rest
}: AnchorProps) {
return (
<a
className={`${base} ${sizes[size]} ${variants[variant]} ${className}`}
{...rest}
/>
)
}

View File

@@ -4,6 +4,7 @@ type Props = {
sub?: React.ReactNode sub?: React.ReactNode
align?: 'left' | 'center' align?: 'left' | 'center'
className?: string className?: string
headingAs?: 'h1' | 'h2'
} }
export function SectionHeader({ export function SectionHeader({
@@ -12,9 +13,12 @@ export function SectionHeader({
sub, sub,
align = 'left', align = 'left',
className = '', className = '',
headingAs = 'h2',
}: Props) { }: Props) {
const alignment = const alignment =
align === 'center' ? 'items-center text-center mx-auto' : 'items-start text-left' align === 'center' ? 'items-center text-center mx-auto' : 'items-start text-left'
const HeadingTag = headingAs
return ( return (
<div className={`flex flex-col ${alignment} max-w-3xl ${className}`}> <div className={`flex flex-col ${alignment} max-w-3xl ${className}`}>
{label ? ( {label ? (
@@ -22,7 +26,7 @@ export function SectionHeader({
{label} {label}
</span> </span>
) : null} ) : null}
<h2 className="h2-display text-nearle-dark mt-3">{heading}</h2> <HeadingTag className="h2-display text-nearle-dark mt-3">{heading}</HeadingTag>
{sub ? ( {sub ? (
<p className="font-body text-nearle-mid text-lg leading-relaxed mt-4"> <p className="font-body text-nearle-mid text-lg leading-relaxed mt-4">
{sub} {sub}

View File

@@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@@ -11,14 +15,30 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react-jsx",
"incremental": true, "incremental": true,
"plugins": [{ "name": "next" }], "plugins": [
{
"name": "next"
}
],
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
]
} }
}, },
"include": ["next-env.d.ts", ".next/types/**/*.ts", "src/**/*.ts", "src/**/*.tsx"], "include": [
"exclude": ["node_modules", "nearle-next", "dist"] "next-env.d.ts",
".next/types/**/*.ts",
"src/**/*.ts",
"src/**/*.tsx",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules",
"nearle-next",
"dist"
]
} }