remove unused files
This commit is contained in:
42
eslint.config.js
Normal file
42
eslint.config.js
Normal 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
3
next-env.d.ts
vendored
@@ -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.
|
||||||
|
|||||||
@@ -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
2366
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -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
9
public/apple-icon.png
Normal 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
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
11
src/app/robots.ts
Normal 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
12
src/app/sitemap.ts
Normal 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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -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'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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 India’s 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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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="🏠" />
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user