diff --git a/Apple download.svg b/Apple download.svg
deleted file mode 100644
index dc84313..0000000
--- a/Apple download.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/package.json b/package.json
index 9c21845..d549cae 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "NODE_OPTIONS=--max-old-space-size=6144 next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
diff --git a/platfrom.png b/platfrom.png
deleted file mode 100644
index d4b5e31..0000000
Binary files a/platfrom.png and /dev/null differ
diff --git a/public/Food.svg b/public/Food.svg
deleted file mode 100644
index 1849a6e..0000000
--- a/public/Food.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/apple-download.svg b/public/icons/apple-download.svg
similarity index 100%
rename from public/apple-download.svg
rename to public/icons/apple-download.svg
diff --git a/public/apple-icon.png b/public/icons/apple-icon.png
similarity index 100%
rename from public/apple-icon.png
rename to public/icons/apple-icon.png
diff --git a/public/play-download.svg b/public/icons/play-download.svg
similarity index 100%
rename from public/play-download.svg
rename to public/icons/play-download.svg
diff --git a/play 1.svg b/public/icons/play.svg
similarity index 100%
rename from play 1.svg
rename to public/icons/play.svg
diff --git a/public/about.webp b/public/images/hero/about.webp
similarity index 100%
rename from public/about.webp
rename to public/images/hero/about.webp
diff --git a/public/images/business1.webp b/public/images/hero/business1.webp
similarity index 100%
rename from public/images/business1.webp
rename to public/images/hero/business1.webp
diff --git a/public/contact-page1.webp b/public/images/hero/contact-page1.webp
similarity index 100%
rename from public/contact-page1.webp
rename to public/images/hero/contact-page1.webp
diff --git a/public/images/home1.webp b/public/images/hero/home1.webp
similarity index 100%
rename from public/images/home1.webp
rename to public/images/hero/home1.webp
diff --git a/public/images/hero/iphone-chrome.svg b/public/images/hero/iphone-chrome.svg
new file mode 100644
index 0000000..a5d9087
--- /dev/null
+++ b/public/images/hero/iphone-chrome.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/images/hero/iphone-frame.svg b/public/images/hero/iphone-frame.svg
new file mode 100644
index 0000000..70b767f
--- /dev/null
+++ b/public/images/hero/iphone-frame.svg
@@ -0,0 +1,304 @@
+
diff --git a/public/nearle-people.webp b/public/images/hero/nearle-people.webp
similarity index 100%
rename from public/nearle-people.webp
rename to public/images/hero/nearle-people.webp
diff --git a/public/images/people1.webp b/public/images/hero/people1.webp
similarity index 100%
rename from public/images/people1.webp
rename to public/images/hero/people1.webp
diff --git a/public/images/nearle-redesign/better-local-value.webp b/public/images/illustrations/better-local-value.webp
similarity index 100%
rename from public/images/nearle-redesign/better-local-value.webp
rename to public/images/illustrations/better-local-value.webp
diff --git a/public/brand part.png b/public/images/illustrations/brand-part.png
similarity index 100%
rename from public/brand part.png
rename to public/images/illustrations/brand-part.png
diff --git a/public/convenience.svg b/public/images/illustrations/convenience.svg
similarity index 100%
rename from public/convenience.svg
rename to public/images/illustrations/convenience.svg
diff --git a/customer.svg b/public/images/illustrations/customer.svg
similarity index 100%
rename from customer.svg
rename to public/images/illustrations/customer.svg
diff --git a/public/customers.svg b/public/images/illustrations/customers.svg
similarity index 100%
rename from public/customers.svg
rename to public/images/illustrations/customers.svg
diff --git a/public/d2c bg1.png b/public/images/illustrations/d2c-bg1.png
similarity index 100%
rename from public/d2c bg1.png
rename to public/images/illustrations/d2c-bg1.png
diff --git a/public/images/nearle-redesign/delivery-doorstep.webp b/public/images/illustrations/delivery-doorstep.webp
similarity index 100%
rename from public/images/nearle-redesign/delivery-doorstep.webp
rename to public/images/illustrations/delivery-doorstep.webp
diff --git a/public/images/nearle-redesign/easy-reordering.webp b/public/images/illustrations/easy-reordering.webp
similarity index 100%
rename from public/images/nearle-redesign/easy-reordering.webp
rename to public/images/illustrations/easy-reordering.webp
diff --git a/Food.svg b/public/images/illustrations/food.svg
similarity index 100%
rename from Food.svg
rename to public/images/illustrations/food.svg
diff --git a/public/Gemini_Generated_Image_lxh416lxh416lxh4.png b/public/images/illustrations/gemini-generated-image.png
similarity index 100%
rename from public/Gemini_Generated_Image_lxh416lxh416lxh4.png
rename to public/images/illustrations/gemini-generated-image.png
diff --git a/public/images/nearle-redesign/heading-shopper.webp b/public/images/illustrations/heading-shopper.webp
similarity index 100%
rename from public/images/nearle-redesign/heading-shopper.webp
rename to public/images/illustrations/heading-shopper.webp
diff --git a/public/images/nearle-redesign/heading-store.webp b/public/images/illustrations/heading-store.webp
similarity index 100%
rename from public/images/nearle-redesign/heading-store.webp
rename to public/images/illustrations/heading-store.webp
diff --git a/public/location.svg b/public/images/illustrations/location.svg
similarity index 100%
rename from public/location.svg
rename to public/images/illustrations/location.svg
diff --git a/public/nerale (1).png b/public/images/illustrations/nerale.png
similarity index 100%
rename from public/nerale (1).png
rename to public/images/illustrations/nerale.png
diff --git a/public/images/nearle-redesign/shops-you-know.webp b/public/images/illustrations/shops-you-know.webp
similarity index 100%
rename from public/images/nearle-redesign/shops-you-know.webp
rename to public/images/illustrations/shops-you-know.webp
diff --git a/public/super.png b/public/images/illustrations/super.png
similarity index 100%
rename from public/super.png
rename to public/images/illustrations/super.png
diff --git a/public/superhero.png b/public/images/illustrations/superhero.png
similarity index 100%
rename from public/superhero.png
rename to public/images/illustrations/superhero.png
diff --git a/public/images/nearle-redesign/support-local-business.webp b/public/images/illustrations/support-local-business.webp
similarity index 100%
rename from public/images/nearle-redesign/support-local-business.webp
rename to public/images/illustrations/support-local-business.webp
diff --git a/public/trust.svg b/public/images/illustrations/trust.svg
similarity index 100%
rename from public/trust.svg
rename to public/images/illustrations/trust.svg
diff --git a/public/vegetable.png b/public/images/illustrations/vegetable.png
similarity index 100%
rename from public/vegetable.png
rename to public/images/illustrations/vegetable.png
diff --git a/artificial intelligence booting.svg b/public/images/platform/ai-booting.svg
similarity index 100%
rename from artificial intelligence booting.svg
rename to public/images/platform/ai-booting.svg
diff --git a/public/images/app-mockup.webp b/public/images/platform/app-mockup.webp
similarity index 100%
rename from public/images/app-mockup.webp
rename to public/images/platform/app-mockup.webp
diff --git a/public/mobile-image.webp b/public/images/platform/mobile-image.webp
similarity index 100%
rename from public/mobile-image.webp
rename to public/images/platform/mobile-image.webp
diff --git a/public/platform.png b/public/images/platform/platform.png
similarity index 100%
rename from public/platform.png
rename to public/images/platform/platform.png
diff --git a/public/images/cat-daily-essentials.webp b/public/images/stores/cat-daily-essentials.webp
similarity index 100%
rename from public/images/cat-daily-essentials.webp
rename to public/images/stores/cat-daily-essentials.webp
diff --git a/public/images/cat-health-wellness.webp b/public/images/stores/cat-health-wellness.webp
similarity index 100%
rename from public/images/cat-health-wellness.webp
rename to public/images/stores/cat-health-wellness.webp
diff --git a/public/images/cat-home-services.webp b/public/images/stores/cat-home-services.webp
similarity index 100%
rename from public/images/cat-home-services.webp
rename to public/images/stores/cat-home-services.webp
diff --git a/public/images/cat-hot-food.webp b/public/images/stores/cat-hot-food.webp
similarity index 100%
rename from public/images/cat-hot-food.webp
rename to public/images/stores/cat-hot-food.webp
diff --git a/public/images/cat-pet-essentials.webp b/public/images/stores/cat-pet-essentials.webp
similarity index 100%
rename from public/images/cat-pet-essentials.webp
rename to public/images/stores/cat-pet-essentials.webp
diff --git a/public/images/nearle-merchant/catalogue-ui.webp b/public/images/stores/catalogue-ui.webp
similarity index 100%
rename from public/images/nearle-merchant/catalogue-ui.webp
rename to public/images/stores/catalogue-ui.webp
diff --git a/public/images/nearle-merchant/delivery-support.webp b/public/images/stores/delivery-support.webp
similarity index 100%
rename from public/images/nearle-merchant/delivery-support.webp
rename to public/images/stores/delivery-support.webp
diff --git a/public/images/nearle-merchant/local-map.webp b/public/images/stores/local-map.webp
similarity index 100%
rename from public/images/nearle-merchant/local-map.webp
rename to public/images/stores/local-map.webp
diff --git a/public/images/nearle-merchant/merchant-hero.webp b/public/images/stores/merchant-hero.webp
similarity index 100%
rename from public/images/nearle-merchant/merchant-hero.webp
rename to public/images/stores/merchant-hero.webp
diff --git a/public/merchant.png b/public/images/stores/merchant.png
similarity index 100%
rename from public/merchant.png
rename to public/images/stores/merchant.png
diff --git a/public/nearle-qr-code.png b/public/images/stores/nearle-qr-code.png
similarity index 100%
rename from public/nearle-qr-code.png
rename to public/images/stores/nearle-qr-code.png
diff --git a/public/images/nearle-merchant/qr-stand.webp b/public/images/stores/qr-stand.webp
similarity index 100%
rename from public/images/nearle-merchant/qr-stand.webp
rename to public/images/stores/qr-stand.webp
diff --git a/public/images/nearle-merchant/success-owner.webp b/public/images/stores/success-owner.webp
similarity index 100%
rename from public/images/nearle-merchant/success-owner.webp
rename to public/images/stores/success-owner.webp
diff --git a/public/why-nearle.png b/public/images/why-nearle/why-nearle.png
similarity index 100%
rename from public/why-nearle.png
rename to public/images/why-nearle/why-nearle.png
diff --git a/Dlivery Map.svg b/public/images/workflow/delivery-map.svg
similarity index 100%
rename from Dlivery Map.svg
rename to public/images/workflow/delivery-map.svg
diff --git a/public/delivery.png b/public/images/workflow/delivery.png
similarity index 100%
rename from public/delivery.png
rename to public/images/workflow/delivery.png
diff --git a/public/images/nearle-how/how-grocery-bag.webp b/public/images/workflow/how-grocery-bag.webp
similarity index 100%
rename from public/images/nearle-how/how-grocery-bag.webp
rename to public/images/workflow/how-grocery-bag.webp
diff --git a/public/images/nearle-how/how-storefront.webp b/public/images/workflow/how-storefront.webp
similarity index 100%
rename from public/images/nearle-how/how-storefront.webp
rename to public/images/workflow/how-storefront.webp
diff --git a/public/map1.webp b/public/images/workflow/map1.webp
similarity index 100%
rename from public/map1.webp
rename to public/images/workflow/map1.webp
diff --git a/public/logo-light.png b/public/logos/logo-light.png
similarity index 100%
rename from public/logo-light.png
rename to public/logos/logo-light.png
diff --git a/public/logo.png b/public/logos/logo.png
similarity index 100%
rename from public/logo.png
rename to public/logos/logo.png
diff --git a/take_screenshot.js b/scripts/take-screenshot.js
similarity index 100%
rename from take_screenshot.js
rename to scripts/take-screenshot.js
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index e629d06..80f36cd 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -2,10 +2,10 @@ import React from "react";
import Image from "next/image";
import Link from "next/link";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { AboutHero } from "@/components/heroes/AboutHero";
-import { AnimatedSection } from "@/components/motion/AnimatedSection";
-import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
+import { AnimatedSection } from "@/components/animations/AnimatedSection";
+import { AnimatedGrid, AnimatedCard } from "@/components/animations/AnimatedGrid";
import { FloatingDelivery } from "@/components/about/FloatingDelivery";
import type { Metadata } from "next";
@@ -20,7 +20,7 @@ const STRATEGY_CARDS = [
descr: "To help average stores perform like the great ones, not just by selling products, but by building a strong brand identity and real relationships with customers through Nearle.",
icon: "target",
iconClass: "bg-[#6330D6]/[0.07] text-[#6330D6] border-[#6330D6]/10",
- image: "/images/nearle-how/how-storefront.webp",
+ image: "/images/workflow/how-storefront.webp",
imageAlt: "3D local store storefront with purple awning",
imgFit: "object-contain object-bottom"
},
@@ -29,7 +29,7 @@ const STRATEGY_CARDS = [
descr: "Our vision is to make every neighbourhood more connected by helping people discover, support, and shop from the local businesses around them.",
icon: "visibility",
iconClass: "bg-secondary/[0.07] text-secondary border-secondary/10",
- image: "/images/nearle-merchant/local-map.webp",
+ image: "/images/stores/local-map.webp",
imageAlt: "Nearle local neighbourhood map",
imgFit: "object-cover object-center"
},
@@ -38,7 +38,7 @@ const STRATEGY_CARDS = [
descr: "Nearle is essential for a business to grow by leveraging the full potential of its neighbourhood. It is an indispensable tool for consumers to take advantage of what their neighbourhood has to offer.",
icon: "help_outline",
iconClass: "bg-[#6330D6]/10 text-[#6330D6] border-[#6330D6]/20",
- image: "/images/app-mockup.webp",
+ image: "/images/platform/app-mockup.webp",
imageAlt: "Nearle app on a smartphone",
imgFit: "object-contain object-bottom"
}
diff --git a/src/app/brands/page.tsx b/src/app/brands/page.tsx
index 5d738e3..ee03684 100644
--- a/src/app/brands/page.tsx
+++ b/src/app/brands/page.tsx
@@ -2,11 +2,11 @@ import React from "react";
import Image from "next/image";
import Link from "next/link";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { BrandsHero } from "@/components/heroes/BrandsHero";
import { FAQAccordion } from "@/components/ui/FAQAccordion";
-import { AnimatedSection } from "@/components/motion/AnimatedSection";
-import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
+import { AnimatedSection } from "@/components/animations/AnimatedSection";
+import { AnimatedGrid, AnimatedCard } from "@/components/animations/AnimatedGrid";
import { CTABanner } from "@/components/ui/CTABanner";
import { NetworkBackground } from "@/components/brands/NetworkBackground";
import { PartnershipConnector } from "@/components/brands/PartnershipConnector";
@@ -199,7 +199,7 @@ export default function BrandsPage() {
{/* ─── Industries ─── */}
);
diff --git a/src/app/businesses/page.tsx b/src/app/businesses/page.tsx
index 5cbe117..784fdd8 100644
--- a/src/app/businesses/page.tsx
+++ b/src/app/businesses/page.tsx
@@ -2,11 +2,11 @@ import React from "react";
import Image from "next/image";
import Link from "next/link";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { BusinessFeaturesGrid } from "@/components/businesses/BusinessFeaturesGrid";
import { BusinessHero } from "@/components/heroes/BusinessHero";
-import { AnimatedSection } from "@/components/motion/AnimatedSection";
-import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
+import { AnimatedSection } from "@/components/animations/AnimatedSection";
+import { AnimatedGrid, AnimatedCard } from "@/components/animations/AnimatedGrid";
import { CTABanner } from "@/components/ui/CTABanner";
import type { Metadata } from "next";
@@ -177,7 +177,7 @@ export default function BusinessesPage() {
.
+ This covers CSS-only motion it can't reach — Tailwind transitions/hover transforms
+ and the infinite keyframe loops (float, pulse-subtle, hero-bike-float, stroke-dash,
+ hero-map-drift). Near-instant rather than removed, so final states stay intact. */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 2c5971d..3d2cddc 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,9 +1,9 @@
import type { Metadata, Viewport } from "next";
import { Lato } from "next/font/google";
import "./globals.css";
-import { MotionProvider } from "@/components/layout/MotionProvider";
-import { PremiumNavbar } from "@/components/layout/PremiumNavbar";
-import { PremiumFooter } from "@/components/layout/PremiumFooter";
+import { MotionProvider } from "@/components/common/MotionProvider";
+import { PremiumNavbar } from "@/components/navbar/PremiumNavbar";
+import { PremiumFooter } from "@/components/footer/PremiumFooter";
const lato = Lato({
subsets: ["latin"],
@@ -41,7 +41,7 @@ export const metadata: Metadata = {
type: "website",
images: [
{
- url: "/logo.png",
+ url: "/logos/logo.png",
width: 512,
height: 512,
alt: "Nearle logo",
@@ -53,13 +53,13 @@ export const metadata: Metadata = {
title: "Nearle: Your Neighborhood, Delivered",
description:
"India's hyperlocal neighborhood commerce platform",
- images: ["/logo.png"],
+ images: ["/logos/logo.png"],
},
alternates: { canonical: "/" },
icons: {
icon: "/favicon.ico",
shortcut: "/favicon.ico",
- apple: "/apple-icon.png",
+ apple: "/icons/apple-icon.png",
},
robots: { index: true, follow: true },
};
@@ -78,7 +78,7 @@ const jsonLd = {
"@id": "https://nearle.in/#organization",
name: "Nearle",
url: "https://nearle.in",
- logo: "https://nearle.in/logo.png",
+ logo: "https://nearle.in/logos/logo.png",
description:
"Nearle is an AI Commerce platform that digitizes neighbourhood stores — with AI catalogs by ProductLLM, QR ordering, and local discovery — instead of replacing them.",
sameAs: [
@@ -119,7 +119,7 @@ export default function RootLayout({
-
+
{children}
diff --git a/src/app/merchants/page.tsx b/src/app/merchants/page.tsx
index c2e3179..44e1895 100644
--- a/src/app/merchants/page.tsx
+++ b/src/app/merchants/page.tsx
@@ -1,11 +1,11 @@
import React from "react";
import Link from "next/link";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { MerchantsHero } from "@/components/heroes/MerchantsHero";
import { FAQAccordion } from "@/components/ui/FAQAccordion";
-import { AnimatedSection } from "@/components/motion/AnimatedSection";
-import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
+import { AnimatedSection } from "@/components/animations/AnimatedSection";
+import { AnimatedGrid, AnimatedCard } from "@/components/animations/AnimatedGrid";
import { CTABanner } from "@/components/ui/CTABanner";
import type { Metadata } from "next";
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 5169fa1..8340932 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,18 +1,21 @@
import type { Metadata } from "next";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { Hero } from "@/components/home/Hero";
+import { VoiceCommerceExperience } from "@/components/home/VoiceCommerceExperience";
+import { StoreJourney } from "@/components/home/experience/StoreJourney";
import { ServiceCategories } from "@/components/home/ServiceCategories";
import { WhyNearle } from "@/components/home/WhyNearle";
import { HowItWorks } from "@/components/home/HowItWorks";
import { HyperlocalFeatures } from "@/components/home/HyperlocalFeatures";
import { BusinessSection } from "@/components/home/BusinessSection";
import { WhyAICommerceWins } from "@/components/home/WhyAICommerceWins";
+import { AICommerceTeaser } from "@/components/home/AICommerceTeaser";
import { StoreQRSection } from "@/components/home/StoreQRSection";
export const metadata: Metadata = {
title: "AI Commerce Platform for Neighbourhood Stores",
description:
- "Nearle is the AI Commerce platform that digitizes neighbourhood stores instead of replacing them. AI catalogs by ProductLLM, QR ordering, and local discovery — commerce that runs on your neighbourhood.",
+ "Nearle digitizes the neighbourhood stores you already trust. Shop from nearby merchants, let Nearle AI find products by natural language, and get orders packed by the shopkeeper and delivered by a Nearle rider.",
openGraph: {
title: "Nearle — AI Commerce Platform for Neighbourhood Stores",
description:
@@ -26,12 +29,15 @@ export default function HomePage() {
return (
+
+
+
);
diff --git a/src/app/people/page.tsx b/src/app/people/page.tsx
index 91711dc..e526381 100644
--- a/src/app/people/page.tsx
+++ b/src/app/people/page.tsx
@@ -1,12 +1,12 @@
import React from "react";
import Image from "next/image";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { PeopleHero } from "@/components/heroes/PeopleHero";
-import { AnimatedSection } from "@/components/motion/AnimatedSection";
-import { AnimatedGrid, AnimatedCard } from "@/components/motion/AnimatedGrid";
+import { AnimatedSection } from "@/components/animations/AnimatedSection";
+import { AnimatedGrid, AnimatedCard } from "@/components/animations/AnimatedGrid";
import { AnimatedTimeline } from "@/components/delivery/AnimatedTimeline";
-import { siteContent } from "@/config/site-content";
+import { siteContent } from "@/constants/site-content";
import type { Metadata } from "next";
export const metadata: Metadata = {
@@ -79,7 +79,7 @@ const OFFERINGS = [
descr: "Enjoy the comfort of elegant living. Nearle is India's first hyper-local managed platform bringing all amenities and requirements directly to your fingertips.",
icon: "local_florist",
color: "from-purple-500/10 to-indigo-500/10 text-primary border-purple-100",
- image: "/nearle-people.webp",
+ image: "/images/hero/nearle-people.webp",
isWide: false
},
{
@@ -88,7 +88,7 @@ const OFFERINGS = [
descr: "Pick hot deals within your neighbourhood. Stay updated on localized sales, discounts, and flash promotions happening today.",
icon: "local_activity",
color: "from-amber-500/10 to-orange-500/10 text-amber-700 border-amber-100",
- image: "/images/app-mockup.webp",
+ image: "/images/platform/app-mockup.webp",
isWide: false
},
{
@@ -97,7 +97,7 @@ const OFFERINGS = [
descr: "Enjoy the ease of the World Wide Web, fulfilled entirely by trusted local businesses. Avoid traffic lights, parking hassles, and pollution.",
icon: "travel_explore",
color: "from-green-500/10 to-emerald-500/10 text-secondary border-green-100",
- image: "/super.png",
+ image: "/images/illustrations/super.png",
isWide: false
},
{
@@ -106,7 +106,7 @@ const OFFERINGS = [
descr: "Enjoy safe, convenient shopping with custom pricing, resident promotions, and bulk order discounts negotiated for your community.",
icon: "shopping_bag",
color: "from-rose-500/10 to-pink-500/10 text-rose-700 border-rose-100",
- image: "/images/cat-pet-essentials.webp",
+ image: "/images/stores/cat-pet-essentials.webp",
isWide: false
},
{
@@ -115,7 +115,7 @@ const OFFERINGS = [
descr: "Order everything you need in your neighborhood with a single tap. A fast, organized last-mile delivery network ensures prompt fulfillment.",
icon: "touch_app",
color: "from-sky-500/10 to-blue-500/10 text-sky-700 border-sky-100",
- image: "/about.webp",
+ image: "/images/hero/about.webp",
isWide: true
},
{
@@ -124,7 +124,7 @@ const OFFERINGS = [
descr: "Get your fresh fruits, farm-fresh vegetables, dairy, and grocery items delivered instantly from verified local green grocers.",
icon: "eco",
color: "from-emerald-500/10 to-teal-500/10 text-emerald-700 border-emerald-100",
- image: "/images/cat-daily-essentials.webp",
+ image: "/images/stores/cat-daily-essentials.webp",
isWide: false
},
{
@@ -133,7 +133,7 @@ const OFFERINGS = [
descr: "Choose from the best local restaurants and neighborhood eateries close to your home. Hot food delivered with relevance and speed.",
icon: "restaurant",
color: "from-orange-500/10 to-red-500/10 text-orange-700 border-orange-100",
- image: "/images/cat-hot-food.webp",
+ image: "/images/stores/cat-hot-food.webp",
isWide: false
},
{
@@ -142,7 +142,7 @@ const OFFERINGS = [
descr: "Access custom-tailored local neighborhood services designed to integrate convenience directly into residential society living.",
icon: "stars",
color: "from-violet-500/10 to-purple-500/10 text-violet-700 border-violet-100",
- image: "/images/cat-health-wellness.webp",
+ image: "/images/stores/cat-health-wellness.webp",
isWide: false
},
{
@@ -151,7 +151,7 @@ const OFFERINGS = [
descr: "Book certified local service providers at your convenience. From handymen to laundry, pet care to pharmacy delivery, all in one place.",
icon: "construction",
color: "from-cyan-500/10 to-blue-500/10 text-cyan-700 border-cyan-100",
- image: "/images/cat-home-services.webp",
+ image: "/images/stores/cat-home-services.webp",
isWide: false
}
];
diff --git a/src/app/platform/page.tsx b/src/app/platform/page.tsx
index a5461be..73ace34 100644
--- a/src/app/platform/page.tsx
+++ b/src/app/platform/page.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { PlatformHero } from "@/components/platform/PlatformHero";
import { PlatformArchitecture } from "@/components/platform/PlatformArchitecture";
import { ProductFeatureGrid } from "@/components/platform/ProductFeatureGrid";
diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx
index 6f2e24f..764d711 100644
--- a/src/app/privacy/page.tsx
+++ b/src/app/privacy/page.tsx
@@ -1,7 +1,7 @@
import React from "react";
import Link from "next/link";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import type { Metadata } from "next";
export const metadata: Metadata = {
diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx
index 6a84f16..5628605 100644
--- a/src/app/terms/page.tsx
+++ b/src/app/terms/page.tsx
@@ -1,7 +1,7 @@
import React from "react";
import Link from "next/link";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import type { Metadata } from "next";
export const metadata: Metadata = {
diff --git a/src/app/why-nearle/page.tsx b/src/app/why-nearle/page.tsx
index 1644b74..6bed0fd 100644
--- a/src/app/why-nearle/page.tsx
+++ b/src/app/why-nearle/page.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { PremiumPageShell } from "@/components/layout/PremiumPageShell";
+import { PremiumPageShell } from "@/components/common/PremiumPageShell";
import { WhyNearleHero } from "@/components/why-nearle/WhyNearleHero";
import { CommerceEvolution } from "@/components/why-nearle/CommerceEvolution";
import { AICommerceExplainer } from "@/components/why-nearle/AICommerceExplainer";
diff --git a/src/components/about/FloatingDelivery.tsx b/src/components/about/FloatingDelivery.tsx
index f619f31..c61e4e3 100644
--- a/src/components/about/FloatingDelivery.tsx
+++ b/src/components/about/FloatingDelivery.tsx
@@ -13,7 +13,7 @@ export function FloatingDelivery() {
transition={{ duration: 6, ease: "easeInOut", repeat: Infinity }}
>
+
+
+
+ The Shift
+
+
+ Traditional commerce, reimagined by AI
+
+
+
+
+ {/* Column headers */}
+
+
+ Traditional Commerce
+
+
+ Nearle AI Commerce
+
+
+
+
+ {ROWS.map((row) => (
+
+ {/* Traditional side */}
+
+
+
+
+
+ {row.traditional}
+
+
+
+ {/* Nearle side */}
+
+
+
+
+
+ {row.nearle}
+
+
+
+ ))}
+
+
+
+
+ Learn More
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/home/BusinessSection.tsx b/src/components/home/BusinessSection.tsx
index a1e36af..e9351ea 100644
--- a/src/components/home/BusinessSection.tsx
+++ b/src/components/home/BusinessSection.tsx
@@ -11,7 +11,7 @@ const FEATURES = [
title: "AI Catalogue",
desc: "Turn your shelf into a live online catalogue with ProductLLM — no manual data entry.",
icon: "menu_book",
- visual: "/images/nearle-merchant/catalogue-ui.webp",
+ visual: "/images/stores/catalogue-ui.webp",
alt: "Clean grocery catalogue interface preview",
tone: "bg-primary/8 text-primary",
},
@@ -19,7 +19,7 @@ const FEATURES = [
title: "QR Ordering",
desc: "Let customers scan, browse, and place orders directly from your store.",
icon: "qr_code",
- visual: "/images/nearle-merchant/qr-stand.webp",
+ visual: "/images/stores/qr-stand.webp",
alt: "QR ordering stand on a local grocery store counter",
tone: "bg-secondary/8 text-secondary",
},
@@ -27,7 +27,7 @@ const FEATURES = [
title: "Local Discovery",
desc: "Get discovered by nearby customers searching for products you sell.",
icon: "location_on",
- visual: "/images/nearle-merchant/local-map.webp",
+ visual: "/images/stores/local-map.webp",
alt: "Local discovery map with purple pins and a store listing",
tone: "bg-[#de96f9]/10 text-primary",
},
@@ -35,7 +35,7 @@ const FEATURES = [
title: "Delivery Support",
desc: "Deliver orders easily with your own staff or trusted local delivery partners.",
icon: "local_shipping",
- visual: "/images/nearle-merchant/delivery-support.webp",
+ visual: "/images/stores/delivery-support.webp",
alt: "Local delivery partner handing a grocery bag to a customer",
tone: "bg-rose-500/5 text-rose-600",
},
@@ -105,7 +105,7 @@ function LocalDiscoveryVisual() {
{/* Store Card (Right Side) */}
-
+
Kumar Provision Store
@@ -279,7 +279,7 @@ export function BusinessSection() {
>
- );
-}
-
export function HowItWorks() {
return (
+ <>
{/* HERO AREA */}
@@ -316,7 +127,7 @@ export function HowItWorks() {
+
- {/* CARDS SECTION */}
-
+ {/* MERCHANT JOURNEY — self-contained cinematic showcase (auto-plays in
+ place; no sticky pin, so no empty scroll-track). */}
+
- How It Works
+ Merchant Journey
- Build Your Neighbourhood Brand
+ From your shelf to a growing business
- Three simple milestones to scale your retail reach from offline streets straight into neighbourhood homes.
+ Eight steps, powered by AI. Join once, and Nearle turns your shelf into a
+ discoverable storefront while you stay at the centre of every order.
-
- {STEPS.map((step) => (
-
-
- {step.num}
-
-
-
-
-
-
- {step.title}
-
-
- {step.desc}
-
-
-
-
-
-
- ))}
-
-
-
+
+
+ >
);
}
diff --git a/src/components/home/HyperlocalFeatures.tsx b/src/components/home/HyperlocalFeatures.tsx
index f77ef66..a9b59ee 100644
--- a/src/components/home/HyperlocalFeatures.tsx
+++ b/src/components/home/HyperlocalFeatures.tsx
@@ -49,7 +49,7 @@ export function HyperlocalFeatures() {
viewport={viewport}
>
+ {/* Voice waves sit behind everything, centred on the phone side */}
+
+
+
+
+
+ {/* ─── LEFT: what it does ─── */}
+
+
+
+ AI Voice Commerce
+
+
+
+
+
+ Just speak.
+
+
+
+ {/* text-balance keeps the mobile wrap as "Nearle handles /
+ the rest." instead of orphaning "rest." on its own line */}
+
+ Nearle handles the rest.
+
+
+
+
+
+ Order products, discover nearby stores, and book trusted local services using
+ natural conversation.
+
+
+
+ {FEATURE_CHIPS.map((chip) => (
+
+
+ {chip.emoji}
+
+ {chip.label}
+
+ ))}
+
+
+
+
+
+ Try Voice Commerce
+
+
+
+
+
+ {/* ─── RIGHT: the live phone ─── */}
+
+ {/* Width-driven at every breakpoint — the 476/941 aspect ratio
+ derives the height, so the phone never collapses. */}
+
+
+ );
+}
diff --git a/src/components/home/WhyAICommerceWins.tsx b/src/components/home/WhyAICommerceWins.tsx
index 33540d7..5f35ced 100644
--- a/src/components/home/WhyAICommerceWins.tsx
+++ b/src/components/home/WhyAICommerceWins.tsx
@@ -1,32 +1,46 @@
"use client";
import Link from "next/link";
-import { motion } from "framer-motion";
+import { motion, useReducedMotion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { fadeUp, fadeIn, staggerContainer, viewport } from "@/lib/animations";
const CARDS = [
{
- title: "Neighbourhood Trust",
- desc: "Every order is fulfilled by a real local shop your street already knows — not an anonymous warehouse.",
- icon: "handshake",
+ title: "Understands your intent",
+ desc: "Reads natural language and knows what you actually need.",
+ icon: "psychology",
color: "bg-primary/5 text-primary border-primary/10",
},
{
- title: "AI-Powered Catalog",
- desc: "ProductLLM turns a shelf into a live, shoppable catalog in minutes — so any store can go online without the busywork.",
- icon: "auto_awesome",
+ title: "Finds matching products",
+ desc: "Maps your intent to real items on your store’s shelves.",
+ icon: "inventory_2",
color: "bg-secondary/5 text-secondary border-secondary/10",
},
{
- title: "Offline-First Commerce",
- desc: "Billing and inventory keep working even when the network drops. Built for how neighbourhood stores really run.",
- icon: "wifi_off",
+ title: "Recommends alternatives",
+ desc: "Out of stock? It suggests the closest match in the store.",
+ icon: "autorenew",
color: "bg-[#de96f9]/10 text-primary border-[#de96f9]/20",
},
+ {
+ title: "Checks availability",
+ desc: "Confirms what’s in stock before you place the order.",
+ icon: "storefront",
+ color: "bg-secondary/5 text-secondary border-secondary/10",
+ },
+ {
+ title: "Helps you shop faster",
+ desc: "Fewer taps — everyday essentials in just seconds.",
+ icon: "bolt",
+ color: "bg-primary/5 text-primary border-primary/10",
+ },
];
export function WhyAICommerceWins() {
+ const reduce = useReducedMotion();
+
return (
@@ -38,39 +52,43 @@ export function WhyAICommerceWins() {
viewport={viewport}
>
- AI Commerce
+ The Assistant
- Why AI Commerce Wins
+ What Nearle AI Does
- Nearle isn’t another delivery app. It’s the AI layer that brings
- neighbourhood stores online — without replacing them.
+ The store stays the hero. Nearle AI is the quiet assistant that helps you
+ find what you need inside it — faster.
diff --git a/src/components/home/WhyNearle.tsx b/src/components/home/WhyNearle.tsx
index 7a10005..158cd1d 100644
--- a/src/components/home/WhyNearle.tsx
+++ b/src/components/home/WhyNearle.tsx
@@ -7,36 +7,28 @@ import { fadeUp, fadeIn, staggerContainer, viewport } from "@/lib/animations";
const CARDS = [
{
- title: "Shops You Know",
- desc: "Order from familiar neighbourhood stores near you.",
- icon: "store",
+ title: "Shop Your Trusted Store",
+ desc: "Browse products from neighbourhood stores you already know and trust.",
+ icon: "storefront",
color: "bg-primary/5 text-primary border-primary/10",
- image: "/images/nearle-redesign/shops-you-know.webp",
+ image: "/images/illustrations/shops-you-know.webp",
alt: "Friendly local grocery shop owner inside a kirana store",
},
{
- title: "Better Local Value",
- desc: "Discover local offers and daily savings nearby.",
- icon: "local_offer",
- color: "bg-secondary/5 text-secondary border-secondary/10",
- image: "/images/nearle-redesign/better-local-value.webp",
- alt: "Fresh local vegetables and fruits with a local offers sign",
- },
- {
- title: "Easy Reordering",
- desc: "Save your favourite stores for quick reordering.",
- icon: "history",
+ title: "AI Assisted Shopping",
+ desc: "Search naturally. Nearle AI understands what you’re looking for and recommends relevant products.",
+ icon: "auto_awesome",
color: "bg-[#de96f9]/10 text-primary border-[#de96f9]/20",
- image: "/images/nearle-redesign/easy-reordering.webp",
+ image: "/images/illustrations/easy-reordering.webp",
alt: "Hand holding a smartphone with a simple grocery ordering app",
},
{
- title: "Support Local Business",
- desc: "Every order directly supports nearby shopkeepers.",
- icon: "favorite",
- color: "bg-rose-500/5 text-rose-700 border-rose-100",
- image: "/images/nearle-redesign/support-local-business.webp",
- alt: "Local shop owner and customer shaking hands inside a store",
+ title: "Local Delivery",
+ desc: "Orders go directly to the merchant and are delivered by Nearle riders.",
+ icon: "two_wheeler",
+ color: "bg-secondary/5 text-secondary border-secondary/10",
+ image: "/images/illustrations/support-local-business.webp",
+ alt: "Local shop owner handing a packed order to a delivery rider",
},
];
@@ -54,7 +46,7 @@ export function WhyNearle() {
>
- Why Nearle Feels Different
+ Why Nearle
- Nearle brings the convenience of an app to the stores you already know and trust.
+ The store you trust, the assistant that helps you shop, and delivery that keeps
+ the merchant at the centre.
{/* Cards Grid */}
+ {steps.map((label, i) => {
+ const done = i < activeStep;
+ const active = i === activeStep;
+ return (
+
+ );
+}
diff --git a/src/components/home/experience/StoreJourney.tsx b/src/components/home/experience/StoreJourney.tsx
new file mode 100644
index 0000000..c39fcbd
--- /dev/null
+++ b/src/components/home/experience/StoreJourney.tsx
@@ -0,0 +1,212 @@
+"use client";
+
+import { AnimatePresence, motion } from "framer-motion";
+import { MaterialIcon } from "@/components/ui/MaterialIcon";
+import { fadeUp, staggerContainer, viewport } from "@/lib/animations";
+import { StoreCanvas } from "./StoreCanvas";
+import {
+ type JourneyStage,
+ STAGE_ORDER,
+ useStoreJourney,
+} from "@/hooks/useStoreJourney";
+
+// The seven progress beats (fulfilment + done collapse into one visible step).
+const PROGRESS = STAGE_ORDER.filter((s) => s !== "done");
+
+const CAPTIONS: Record = {
+ "choose-store": {
+ title: "Choose your store",
+ body: "Start with a neighbourhood shop you already know and trust — added once with a quick QR scan.",
+ },
+ "enter-store": {
+ title: "Step inside",
+ body: "Walk into your store digitally. The same shelves and the same shopkeeper — now searchable.",
+ },
+ search: {
+ title: "Say what you need",
+ body: "Search in plain language. No need to know exact brands or product names.",
+ },
+ thinking: {
+ title: "Nearle AI understands",
+ body: "AI reads your intent, finds matching products, and checks what’s actually in stock.",
+ },
+ results: {
+ title: "Picks from your store",
+ body: "Every recommendation comes from the store you chose — never an anonymous warehouse.",
+ },
+ cart: {
+ title: "One tap to order",
+ body: "Build your basket and send it straight to the merchant — no middleman.",
+ },
+ fulfilment: {
+ title: "Merchant packs, Nearle delivers",
+ body: "Your shopkeeper packs the order and a Nearle rider brings it to your door.",
+ },
+ done: {
+ title: "Delivered by your neighbourhood",
+ body: "Your shopkeeper packed the order and a Nearle rider brought it home.",
+ },
+};
+
+export function StoreJourney() {
+ const journey = useStoreJourney();
+ const { stage, intent, intents, pickIntent, replay } = journey;
+
+ const caption = CAPTIONS[stage];
+ const activeIndex = stage === "done" ? PROGRESS.length - 1 : PROGRESS.indexOf(stage);
+
+ return (
+
+
+ {/* Header */}
+ journey.start()}
+ className="mx-auto max-w-[760px] text-center"
+ >
+
+
+ Live Demo
+
+
+ Your Neighbourhood Store, Now Digital
+
+
+ Browse products from your trusted nearby stores anytime. Nearle AI helps you discover
+ products faster — without changing how your favourite store works.
+
+
+
+ {/* Interactive journey */}
+
+
+ {/* Closing product message */}
+
+ Your trusted neighbourhood store, now in your pocket.{" "}
+
+ Powered by AI, fulfilled by local merchants, delivered by Nearle.
+
+
+
+
+ );
+}
diff --git a/src/components/home/merchant/MerchantJourney.tsx b/src/components/home/merchant/MerchantJourney.tsx
new file mode 100644
index 0000000..47e05c6
--- /dev/null
+++ b/src/components/home/merchant/MerchantJourney.tsx
@@ -0,0 +1,175 @@
+"use client";
+
+import { motion, AnimatePresence } from "framer-motion";
+import { MaterialIcon } from "@/components/ui/MaterialIcon";
+import { useMerchantJourney, type MerchantStep } from "@/hooks/useMerchantJourney";
+import { MERCHANT_SCENES } from "./illustrations";
+import { MerchantPhone } from "./MerchantPhone";
+
+const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
+
+// The flagship merchant story as a self-contained cinematic showcase — NO sticky
+// pin, no tall scroll-track, so there is no empty space to scroll through. The
+// moment it enters view it auto-plays through the eight beats: the illustration
+// cross-dissolves (each scene re-mounts, so its entrance animation replays), the
+// live phone updates, the caption changes and the progress fills. Every chapter
+// node is clickable to jump; reduced-motion users drive it manually.
+export function MerchantJourney() {
+ const journey = useMerchantJourney();
+ const { steps, index, step, reduced, atEnd, start, goTo, next, prev, replay } = journey;
+ const N = steps.length;
+ const fillPct = (index / (N - 1)) * 100;
+
+ return (
+ start()}
+ >
+ {/* Caption — compact, centred */}
+
+
+ Chapter {index + 1} / {N}
+
+
+
+
+
+ {step.title}
+
+
+ {step.desc}
+
+
+
+
+
+
+ {/* Hero stage — illustration + live phone, centred and dominant */}
+
+
+ {steps.map((s, i) => {
+ const active = i === index;
+ const done = i < index;
+ return (
+
+
+
+ );
+ })}
+
+
+
+ {/* Controls — manual stepping + replay once the arc has played */}
+
+
+
+
+
+ {atEnd && (
+
+
+ Replay
+
+ )}
+
+
+
+ );
+}
+
+// Framed hero canvas — the illustration is the star. Each beat re-mounts, so the
+// scene's own entrance animation replays and it cross-dissolves into place.
+function Canvas({ step, reduced }: { step: MerchantStep; reduced: boolean }) {
+ const Scene = MERCHANT_SCENES[step.id];
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/home/merchant/MerchantPhone.tsx b/src/components/home/merchant/MerchantPhone.tsx
new file mode 100644
index 0000000..aadeaf6
--- /dev/null
+++ b/src/components/home/merchant/MerchantPhone.tsx
@@ -0,0 +1,235 @@
+"use client";
+
+import { motion, AnimatePresence } from "framer-motion";
+import { MaterialIcon } from "@/components/ui/MaterialIcon";
+import type { MerchantStep, MerchantStageId } from "@/hooks/useMerchantJourney";
+
+const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
+const screen = {
+ initial: { opacity: 0, y: 12 },
+ animate: { opacity: 1, y: 0 },
+ exit: { opacity: 0, y: -12 },
+ transition: { duration: 0.55, ease: EASE },
+};
+
+// The merchant-side companion device. Its screen mirrors the active stage, so
+// the illustration and the "real app" always tell the same beat of the story.
+export function MerchantPhone({ step, reduced }: { step: MerchantStep; reduced: boolean }) {
+ return (
+
+ );
+}
diff --git a/src/components/home/voice/PhoneStage.tsx b/src/components/home/voice/PhoneStage.tsx
new file mode 100644
index 0000000..c87a5d3
--- /dev/null
+++ b/src/components/home/voice/PhoneStage.tsx
@@ -0,0 +1,130 @@
+"use client";
+
+import { useEffect, useLayoutEffect, useRef, useState } from "react";
+import Image from "next/image";
+import { motion, useInView } from "framer-motion";
+import { VoiceScreen, SCREEN_W, SCREEN_H } from "./VoiceScreen";
+import { useVoiceJourney } from "@/hooks/useVoiceJourney";
+
+// Geometry read straight off the provided SVG (viewBox 0 0 476 941):
+// screen rect x42 y8 w391 h826 rx57. The frame artwork never changes — only
+// what we render inside this box does.
+const SCREEN = {
+ left: `${((42 / 476) * 100).toFixed(4)}%`,
+ top: `${((8 / 941) * 100).toFixed(4)}%`,
+ width: `${((391 / 476) * 100).toFixed(4)}%`,
+ height: `${((826 / 941) * 100).toFixed(4)}%`,
+ // rx57 expressed against the screen's own box: 57/391 wide, 57/826 tall
+ borderRadius: `${((57 / 391) * 100).toFixed(2)}% / ${((57 / 826) * 100).toFixed(2)}%`,
+} as const;
+
+export function PhoneStage({ className = "" }: { className?: string }) {
+ const journey = useVoiceJourney();
+ const { start, stop, reduced } = journey;
+
+ const rootRef = useRef(null);
+ const screenRef = useRef(null);
+ const [scale, setScale] = useState(0);
+
+ // The demo only runs while it is on screen — no timers burning in the
+ // background once a visitor has scrolled past the hero.
+ const inView = useInView(rootRef, { amount: 0.25 });
+ useEffect(() => {
+ if (inView) start();
+ else stop();
+ }, [inView, start, stop]);
+
+ // The screen UI is authored at the SVG's own 391×826 units, then scaled to
+ // whatever size the phone renders at. Same layout everywhere, every
+ // breakpoint — scaling only.
+ useLayoutEffect(() => {
+ const el = screenRef.current;
+ if (!el) return;
+ const measure = () => setScale(el.clientWidth / SCREEN_W);
+ measure();
+ const ro = new ResizeObserver(measure);
+ ro.observe(el);
+ return () => ro.disconnect();
+ }, []);
+
+ return (
+
+ {/* Breathing ground shadow — a separate blurred ellipse so nothing
+ animates box-shadow (which repaints every frame). */}
+
+
+ {/* Float — vertical drift */}
+
+ {/* Sway — slow rotateY on a deliberately different period, so the two
+ never sync into one obvious loop */}
+
+ {/* Layer 0 — the provided iPhone SVG, verbatim and permanent */}
+
+
+ {/* Layer 1 — the live app, clipped exactly to the screen rect */}
+
+ {scale > 0 && (
+
+
+
+ )}
+
+
+ {/* Layer 2 — notch + status bar, extracted from the same SVG, so the
+ app sits *under* the notch exactly like real iOS */}
+
+
+
+
+ );
+}
diff --git a/src/components/home/voice/VoiceScreen.tsx b/src/components/home/voice/VoiceScreen.tsx
new file mode 100644
index 0000000..24a8a90
--- /dev/null
+++ b/src/components/home/voice/VoiceScreen.tsx
@@ -0,0 +1,527 @@
+"use client";
+
+import { AnimatePresence, motion } from "framer-motion";
+import { MaterialIcon } from "@/components/ui/MaterialIcon";
+import { AIThinking } from "@/components/home/experience/AIThinking";
+import {
+ AI_STEPS,
+ FULFILMENT_STEPS,
+ SERVICE_FULFILMENT_STEPS,
+ type VoiceJourney,
+} from "@/hooks/useVoiceJourney";
+
+// This component is authored in the provided SVG's own screen units — a
+// 391 × 826 box — and PhoneStage scales it to whatever size the phone renders
+// at. So the layout is pixel-identical on every breakpoint: responsive scaling
+// only, never a different (or simpler) UI.
+export const SCREEN_W = 391;
+export const SCREEN_H = 826;
+
+// The Nearle icon inside the provided SVG: x92 y622 45×45, minus the screen's
+// own origin (42, 8). We overlay a matching icon here so it can compress under
+// the tap and the app can grow out of exactly the right spot.
+const ICON = { x: 50, y: 614, size: 45 } as const;
+const ICON_CX = ICON.x + ICON.size / 2; // 72.5
+const ICON_CY = ICON.y + ICON.size / 2; // 636.5
+// Where the app expands from, as a transform-origin on the 391×826 screen.
+const LAUNCH_ORIGIN = `${((ICON_CX / SCREEN_W) * 100).toFixed(2)}% ${((ICON_CY / SCREEN_H) * 100).toFixed(2)}%`;
+
+// Clear of the notch (which ends 30px into the screen) and the status bar.
+const SAFE_TOP = 56;
+
+const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
+
+// Panels are stacked absolutely and crossfade, so one screen is always fully
+// covering the other — the app never flashes empty between beats.
+const screenMotion = {
+ initial: { opacity: 0, y: 12 },
+ animate: { opacity: 1, y: 0, transition: { duration: 0.45, ease: EASE, delay: 0.12 } },
+ exit: { opacity: 0, y: -8, transition: { duration: 0.28, ease: "easeIn" as const } },
+};
+
+export function VoiceScreen({ journey }: { journey: VoiceJourney }) {
+ const {
+ stage,
+ request,
+ spokenWords,
+ thinkingStep,
+ fulfilStep,
+ reduced,
+ } = journey;
+
+ // The app mounts on "launch" and never unmounts again — the loop returns to
+ // "listening", not to the home screen — so the iOS expand plays exactly once.
+ const onHomeScreen = stage === "home" || stage === "tap";
+ const words = request.transcript.split(" ");
+ const isBooking = request.domain === "services";
+ // The request bubble appears the moment the AI takes over and stays put.
+ const showRequest =
+ stage !== "launch" && !onHomeScreen && stage !== "listening" && stage !== "speaking";
+ const fulfilmentSteps = isBooking ? SERVICE_FULFILMENT_STEPS : FULFILMENT_STEPS;
+ // On the final beat every fulfilment step is done — nothing left pulsing.
+ const fulfilDone = stage === "delivered" ? fulfilmentSteps.length : fulfilStep;
+
+ return (
+
+ {/* ── iOS home screen: the provided SVG shows through. We only overlay a
+ matching Nearle icon so it can react to the tap. ── */}
+ {onHomeScreen && (
+ <>
+
+
+ N
+
+ {/* the icon's own soft highlight, matching the SVG artwork */}
+
+
+
+ {/* Apple-style touch indicator — a finger tap, never a desktop cursor */}
+ {!reduced && (
+
+ )}
+ >
+ )}
+
+ {/* ── The Nearle app ── */}
+ {!onHomeScreen && (
+
+ {/* ambient purple bloom */}
+
+
+ {/* App header */}
+
+
+
+ N
+
+ Nearle
+
+
+
+ R.S. Puram
+
+
+
+ {/* What the customer said, kept on screen for the rest of the flow —
+ so the link between the sentence and everything the AI does with
+ it stays visible the whole way to the door. */}
+ {showRequest && (
+
+
+ );
+}
+
+// Live voice bars. Heights are fixed per bar so the pattern reads as a real
+// waveform rather than random noise; only scaleY animates.
+const BARS = [0.35, 0.6, 0.85, 1, 0.7, 0.45, 0.8, 1, 0.65, 0.4, 0.75, 0.55, 0.3];
+
+function Waveform({ reduced, active }: { reduced: boolean; active: boolean }) {
+ return (
+
+ {BARS.map((h, i) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/home/voice/VoiceWaves.tsx b/src/components/home/voice/VoiceWaves.tsx
new file mode 100644
index 0000000..9ddb14b
--- /dev/null
+++ b/src/components/home/voice/VoiceWaves.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import { motion, useReducedMotion } from "framer-motion";
+
+// A 2400-wide path holding six 400px periods. Sliding it exactly -1200
+// (three periods) loops seamlessly, so the drift never visibly restarts.
+const WAVE =
+ "M0 150 Q100 80 200 150 T400 150 T600 150 T800 150 T1000 150 T1200 150 T1400 150 T1600 150 T1800 150 T2000 150 T2200 150 T2400 150";
+
+const LAYERS = [
+ { y: 0, scaleY: 1, opacity: 0.18, duration: 12, width: 2 },
+ { y: 26, scaleY: 0.62, opacity: 0.13, duration: 16, width: 1.5 },
+ { y: -22, scaleY: 1.35, opacity: 0.1, duration: 21, width: 1.5 },
+];
+
+// Elegant voice waves behind the phone. Purely decorative, low opacity, and
+// animated on transform only — it should reinforce "this is voice" without
+// ever pulling attention off the screen.
+export function VoiceWaves({ className = "" }: { className?: string }) {
+ const reduced = useReducedMotion();
+
+ return (
+