product feature grid

This commit is contained in:
2026-07-20 16:37:54 +05:30
parent 0549793a7c
commit c5631a6011
6 changed files with 846 additions and 57 deletions

View File

@@ -4,6 +4,7 @@ import { PlatformHero } from "@/components/platform/PlatformHero";
import { PlatformArchitecture } from "@/components/platform/PlatformArchitecture";
import { ProductFeatureGrid } from "@/components/platform/ProductFeatureGrid";
import { ProductLLMWorkflow } from "@/components/platform/ProductLLMWorkflow";
import { ConsumerFeatures } from "@/components/platform/ConsumerFeatures";
import { CTABanner } from "@/components/ui/CTABanner";
export const metadata: Metadata = {
@@ -30,17 +31,6 @@ const MERCHANT_OS = [
{ icon: "dashboard", title: "Store Dashboard", desc: "Run the whole store from one simple screen." },
];
const CONSUMER_OS = [
{ icon: "explore", title: "Local Discovery", desc: "Find the stores and products right around you." },
{ icon: "photo_camera", title: "Camera Search", desc: "Point your camera to find a product nearby." },
{ icon: "favorite", title: "Favorites", desc: "Save the stores and items you buy again and again." },
{ icon: "storefront", title: "Nearby Stores", desc: "Shop shops within walking distance, not warehouses." },
{ icon: "shopping_bag", title: "Pickup", desc: "Order ahead and grab it on your way past." },
{ icon: "local_shipping", title: "Delivery", desc: "Short, local trips from the store to your door." },
{ icon: "redeem", title: "Rewards", desc: "Earn as you shop your neighbourhood." },
{ icon: "history", title: "Fast Reordering", desc: "Re-buy your usuals in a single tap." },
];
export default function PlatformPage() {
return (
<PremiumPageShell>
@@ -57,15 +47,7 @@ export default function PlatformPage() {
footnote="AI Catalog powered by ProductLLM."
/>
<ProductLLMWorkflow />
<ProductFeatureGrid
id="consumeros"
eyebrow="ConsumerOS"
name="How the neighbourhood shops"
tagline="Discovery, search, ordering, and rewards — the app that brings local customers to nearby stores."
features={CONSUMER_OS}
variant="lavender"
showcase="phone"
/>
<ConsumerFeatures />
<CTABanner
badge={{ icon: "hub", label: "Built on Nearle" }}
heading="Put your store on the platform"

View File

@@ -0,0 +1,118 @@
"use client";
import { useState, useEffect } from "react";
import { motion, useReducedMotion } from "framer-motion";
import { fadeUp, staggerContainer, viewport } from "@/lib/animations";
import { PhoneMockup } from "./PhoneMockup";
import { FeatureGrid } from "./FeatureGrid";
const getScreenFromCardIndex = (cardIndex: number): number => {
switch (cardIndex) {
case 0: // Discover Nearby Stores
case 4: // Favorites
return 0; // Nearby Stores Screen
case 1: // Camera Search
return 1; // Product Search Screen
case 3: // Rewards
return 2; // Rewards Screen
case 2: // Pickup & Delivery
case 5: // Quick Reordering
return 3; // Orders Screen
default:
return 0;
}
};
export function ConsumerFeatures() {
const reduce = useReducedMotion();
const [activeScreen, setActiveScreen] = useState(0);
const [hoveredCardIndex, setHoveredCardIndex] = useState<number | null>(null);
// Auto-cycling when no card is hovered
useEffect(() => {
if (reduce || hoveredCardIndex !== null) return;
const interval = setInterval(() => {
setActiveScreen((prev) => (prev + 1) % 4);
}, 4500); // Cycle screen every 4.5 seconds
return () => clearInterval(interval);
}, [hoveredCardIndex, reduce]);
// Handle card hover updates
const handleHoverCard = (index: number | null) => {
setHoveredCardIndex(index);
if (index !== null) {
setActiveScreen(getScreenFromCardIndex(index));
}
};
// Determine active card highlighted in grid (reflecting current active screen when not hovering)
const activeCardIndex =
hoveredCardIndex !== null
? hoveredCardIndex
: activeScreen === 0
? 0
: activeScreen === 1
? 1
: activeScreen === 2
? 3
: activeScreen === 3
? 2
: 0;
return (
<section
id="consumeros"
className="section w-full border-b border-[#6330D6]/10 overflow-hidden bg-[#faf7ff]"
>
<div className="page-container">
{/* Header Block (Mirrors the MerchantOS header styling) */}
<motion.div
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={viewport}
className="max-w-[760px]"
>
<motion.span
variants={fadeUp}
className="inline-flex items-center rounded-full bg-[#6330D6]/[0.08] px-3.5 py-1 text-xs font-black uppercase tracking-widest text-[#6330D6]"
>
ConsumerOS
</motion.span>
<motion.h2
variants={fadeUp}
className="font-display font-black text-[#16001D] mt-4 tracking-tight"
style={{ fontSize: "clamp(30px, 3vw, 48px)", lineHeight: 1.1 }}
>
How the neighbourhood shops
</motion.h2>
<motion.p
variants={fadeUp}
className="text-[#5E5866] mt-4"
style={{ fontSize: 18, lineHeight: 1.65 }}
>
Discovery, search, ordering, and rewards the app that brings local customers to nearby stores.
</motion.p>
</motion.div>
{/* Dynamic Showcase Grid */}
<div className="mt-16 grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-center">
{/* LEFT SIDE: Smartphone mockup */}
<div className="lg:col-span-5 w-full flex justify-center items-center relative py-6 order-1">
<PhoneMockup activeScreen={activeScreen} />
</div>
{/* RIGHT SIDE: Feature list grid */}
<div className="lg:col-span-7 w-full order-2">
<FeatureGrid
activeCardIndex={activeCardIndex}
setActiveCardIndex={handleHoverCard}
/>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,70 @@
"use client";
import { motion } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
type FeatureCardProps = {
icon: string;
title: string;
description: string;
isActive: boolean;
onHoverStart: () => void;
onHoverEnd: () => void;
};
export function FeatureCard({
icon,
title,
description,
isActive,
onHoverStart,
onHoverEnd,
}: FeatureCardProps) {
// Entrance animations are orchestrated by FeatureGrid, but we manage our interactive transitions here
return (
<div
onMouseEnter={onHoverStart}
onMouseLeave={onHoverEnd}
onClick={onHoverStart} // Support tap on mobile devices to switch
className={`group relative cursor-pointer rounded-2xl p-6 transition-all duration-300 ${
isActive
? "bg-[#65149A]/[0.02] border-[#65149A] shadow-[0_10px_25px_rgba(99,48,214,0.06)] -translate-y-1"
: "bg-white border-[#6330D6]/10 hover:border-[#6330D6]/30 shadow-[0_4px_18px_rgba(22,0,29,0.02)] hover:shadow-[0_12px_28px_rgba(22,0,29,0.06)] hover:-translate-y-1"
} border-[1.5px]`}
>
{/* Accent glow on card hover or active */}
<div
className={`absolute inset-0 -z-10 rounded-2xl bg-gradient-to-tr from-[#6330D6]/[0.02] to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 ${
isActive ? "opacity-100" : ""
}`}
/>
<div className="flex flex-col items-start gap-4">
{/* Purple circular icon */}
<div
className={`flex h-12 w-12 shrink-0 items-center justify-center rounded-full transition-all duration-300 ${
isActive
? "bg-[#65149A] text-white rotate-12 scale-110"
: "bg-[#6330D6]/[0.08] text-[#6330D6] group-hover:bg-[#65149A] group-hover:text-white group-hover:rotate-12 group-hover:scale-110"
}`}
>
<MaterialIcon icon={icon} size={22} />
</div>
{/* Text details */}
<div>
<h3
className={`font-display text-[17px] font-black tracking-tight transition-colors duration-200 ${
isActive ? "text-[#65149A]" : "text-[#16001D] group-hover:text-[#65149A]"
}`}
>
{title}
</h3>
<p className="mt-1.5 text-[14px] leading-relaxed text-[#5E5866]">
{description}
</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,98 @@
"use client";
import { motion } from "framer-motion";
import { FeatureCard } from "./FeatureCard";
type Feature = {
icon: string;
title: string;
description: string;
};
const FEATURES: Feature[] = [
{
icon: "explore",
title: "Discover Nearby Stores",
description: "Find trusted local businesses around you.",
},
{
icon: "photo_camera",
title: "Camera Search",
description: "Scan products and instantly find nearby availability.",
},
{
icon: "local_shipping",
title: "Pickup & Delivery",
description: "Choose how you want to receive your order.",
},
{
icon: "redeem",
title: "Rewards",
description: "Earn points every time you support local businesses.",
},
{
icon: "favorite",
title: "Favorites",
description: "Save your preferred stores and products.",
},
{
icon: "history",
title: "Quick Reordering",
description: "Buy your regular products again in seconds.",
},
];
type FeatureGridProps = {
activeCardIndex: number | null;
setActiveCardIndex: (index: number | null) => void;
};
// Animation variants for staggered fade-up on scroll
const containerVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: {
opacity: 0,
y: 25,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: [0.22, 1, 0.36, 1],
},
},
};
export function FeatureGrid({ activeCardIndex, setActiveCardIndex }: FeatureGridProps) {
return (
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.15 }}
className="grid grid-cols-1 sm:grid-cols-2 gap-6 lg:gap-8 w-full"
>
{FEATURES.map((feature, idx) => (
<motion.div key={idx} variants={itemVariants}>
<FeatureCard
icon={feature.icon}
title={feature.title}
description={feature.description}
isActive={activeCardIndex === idx}
onHoverStart={() => setActiveCardIndex(idx)}
onHoverEnd={() => setActiveCardIndex(null)}
/>
</motion.div>
))}
</motion.div>
);
}

View File

@@ -0,0 +1,524 @@
"use client";
import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
type PhoneMockupProps = {
activeScreen: number;
};
export function PhoneMockup({ activeScreen }: PhoneMockupProps) {
// Screens definitions
const screens = [
// 0: Nearby Stores
{
id: "nearby-stores",
content: (
<div className="flex h-full flex-col bg-[#FDFBFF] text-[#16001D]">
{/* Header */}
<div className="bg-[#65149A] px-4 pt-8 pb-3 text-white">
<div className="flex items-center gap-1.5 text-[10px] font-bold opacity-90">
<MaterialIcon icon="location_on" size={12} className="text-white shrink-0" />
<span className="truncate">R.S. Puram, Coimbatore</span>
<MaterialIcon icon="keyboard_arrow_down" size={12} className="text-white shrink-0" />
</div>
<div className="mt-3 flex items-center justify-between">
<span className="font-display text-lg font-black tracking-tight">nearle</span>
<div className="relative flex h-7 w-7 items-center justify-center rounded-full bg-white/20">
<MaterialIcon icon="notifications" size={16} />
<span className="absolute top-1.5 right-1.5 h-1.5 w-1.5 rounded-full bg-[#22C55E]" />
</div>
</div>
</div>
{/* Search bar */}
<div className="px-4 py-3">
<div className="flex items-center gap-2 rounded-full border border-[#6330D6]/15 bg-white px-3.5 py-1.5 shadow-sm">
<MaterialIcon icon="search" size={14} className="text-[#6330D6]/60 shrink-0" />
<span className="text-[10px] text-[#5E5866]/70 truncate">Search stores & products...</span>
</div>
</div>
{/* Categories */}
<div className="px-4 py-1">
<div className="flex gap-2 overflow-x-hidden pb-1">
{[
{ name: "Sweets", active: true },
{ name: "Grocery", active: false },
{ name: "Veggies", active: false },
{ name: "Bakery", active: false },
].map((cat, idx) => (
<span
key={idx}
className={`rounded-full px-2.5 py-0.5 text-[9px] font-bold transition-colors shrink-0 ${
cat.active
? "bg-[#65149A] text-white"
: "bg-[#6330D6]/5 text-[#6330D6]"
}`}
>
{cat.name}
</span>
))}
</div>
</div>
{/* Stores List */}
<div className="flex-1 overflow-y-auto px-4 py-2 space-y-3">
{[
{
name: "Sri Krishna Sweets",
rating: "4.8",
distance: "300m away",
desc: "Mysore Pak, Laddu & Savouries",
imgBg: "bg-purple-100",
imgText: "🍰",
},
{
name: "Organic Greens Store",
rating: "4.9",
distance: "600m away",
desc: "Fresh Farm Spinach & Organic Veggies",
imgBg: "bg-emerald-100",
imgText: "🥬",
},
{
name: "A1 Biryani House",
rating: "4.7",
distance: "850m away",
desc: "Local Spiced Biryani & Kebabs",
imgBg: "bg-orange-100",
imgText: "🍗",
},
].map((store, i) => (
<div
key={i}
className="flex items-center gap-3 rounded-xl border border-[#6330D6]/5 bg-white p-2.5 shadow-sm transition-transform hover:scale-[1.02]"
>
<div className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${store.imgBg} text-lg`}>
{store.imgText}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between">
<h4 className="truncate font-display text-[11px] font-black text-[#16001D]">
{store.name}
</h4>
<span className="flex items-center text-[9px] font-bold text-[#E59900] shrink-0">
{store.rating}
</span>
</div>
<p className="truncate text-[9px] text-[#5E5866]/80">{store.desc}</p>
<span className="mt-1 inline-block text-[9px] font-bold text-[#6330D6]/80">
{store.distance}
</span>
</div>
</div>
))}
</div>
{/* Bottom Navigation Bar */}
<div className="flex border-t border-[#6330D6]/10 bg-white py-1.5 justify-around items-center shrink-0">
<div className="flex flex-col items-center text-[#65149A]">
<MaterialIcon icon="explore" size={16} fill />
<span className="text-[8px] font-bold mt-0.5">Explore</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="favorite" size={16} />
<span className="text-[8px] mt-0.5">Favorites</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="redeem" size={16} />
<span className="text-[8px] mt-0.5">Rewards</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="shopping_bag" size={16} />
<span className="text-[8px] mt-0.5">Orders</span>
</div>
</div>
</div>
),
},
// 1: Product Search (Camera scanning)
{
id: "product-search",
content: (
<div className="flex h-full flex-col bg-neutral-900 text-white relative overflow-hidden">
{/* Header */}
<div className="absolute top-0 left-0 right-0 z-10 px-4 pt-8 pb-3 bg-gradient-to-b from-black/60 to-transparent flex items-center justify-between">
<span className="text-[11px] font-bold tracking-tight">Camera Search</span>
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-black/40">
<MaterialIcon icon="close" size={14} />
</div>
</div>
{/* Scanning frame area */}
<div className="flex-1 flex flex-col items-center justify-center p-6 relative">
{/* Camera mock elements */}
<div className="w-40 h-40 border-2 border-dashed border-white/40 rounded-3xl relative flex items-center justify-center">
{/* Corner brackets */}
<div className="absolute top-[-2px] left-[-2px] w-6 h-6 border-t-4 border-l-4 border-[#7C3AED] rounded-tl-xl" />
<div className="absolute top-[-2px] right-[-2px] w-6 h-6 border-t-4 border-r-4 border-[#7C3AED] rounded-tr-xl" />
<div className="absolute bottom-[-2px] left-[-2px] w-6 h-6 border-b-4 border-l-4 border-[#7C3AED] rounded-bl-xl" />
<div className="absolute bottom-[-2px] right-[-2px] w-6 h-6 border-b-4 border-r-4 border-[#7C3AED] rounded-br-xl" />
{/* Scanning laser line animation */}
<motion.div
animate={{ top: ["10%", "90%", "10%"] }}
transition={{ duration: 2.2, repeat: Infinity, ease: "easeInOut" }}
className="absolute left-[5%] right-[5%] h-[2px] bg-[#7C3AED] shadow-[0_0_12px_#7C3AED] z-10"
/>
{/* Mock product inside */}
<div className="text-4xl filter blur-[0.5px]">🍪</div>
</div>
<p className="text-[9px] text-white/70 mt-6 bg-black/50 px-3 py-1 rounded-full font-medium">
Point at any item label
</p>
</div>
{/* Bottom sheet showing scanning result */}
<motion.div
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.3 }}
className="bg-white text-[#16001D] p-3 rounded-t-2xl z-10 border-t border-[#6330D6]/10 shrink-0"
>
<div className="flex items-start gap-3">
<div className="h-10 w-10 shrink-0 flex items-center justify-center bg-purple-50 rounded-lg text-lg">
🍪
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5">
<span className="text-[9px] uppercase font-bold text-[#6330D6] bg-[#6330D6]/10 px-2 py-0.5 rounded-full shrink-0">
Match Found
</span>
<span className="text-[9px] text-[#22C55E] font-bold shrink-0">200m away</span>
</div>
<h4 className="text-[11px] font-black truncate mt-1 text-[#16001D]">Chocolate Chip Cookies</h4>
<p className="text-[9px] text-[#5E5866] mt-0.5 truncate">Available at Sri Krishna Bakery</p>
</div>
</div>
<div className="mt-3 flex gap-2">
<button className="flex-1 bg-[#65149A] text-white text-[10px] font-bold py-1.5 rounded-lg">
Buy for 80
</button>
</div>
</motion.div>
</div>
),
},
// 2: Rewards
{
id: "rewards",
content: (
<div className="flex h-full flex-col bg-[#FAF8FC] text-[#16001D]">
{/* Header */}
<div className="px-4 pt-8 pb-3 bg-white border-b border-[#6330D6]/5 flex items-center justify-between shrink-0">
<span className="font-display text-xs font-black tracking-tight text-[#65149A]">Nearle Club</span>
<span className="text-[9px] font-bold text-[#65149A] bg-[#65149A]/5 px-2.5 py-0.5 rounded-full">
Gold Tier
</span>
</div>
{/* Points Card */}
<div className="p-4 shrink-0">
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-tr from-[#65149A] to-[#8B5CF6] p-4 text-white shadow-md">
{/* Decorative background shape */}
<div className="absolute right-[-20px] top-[-20px] h-28 w-28 rounded-full bg-white/10 blur-xl pointer-events-none" />
<span className="text-[9px] uppercase font-bold tracking-wider opacity-85">Available Points</span>
<h2 className="font-display text-2xl font-black mt-1">2,450</h2>
<div className="mt-3 flex items-center justify-between text-[9px] opacity-90">
<span>Next reward at 3,000 pts</span>
<span>81%</span>
</div>
<div className="mt-1 h-1.5 w-full rounded-full bg-white/20 overflow-hidden">
<div className="h-full w-[81%] rounded-full bg-white" />
</div>
</div>
</div>
{/* Active Offers */}
<div className="flex-1 overflow-y-auto px-4 pb-2 space-y-2.5">
<h4 className="text-[9px] font-black uppercase tracking-wider text-[#5E5866] mb-1.5">
Available Rewards
</h4>
<div className="space-y-2">
{[
{
title: "₹50 Off sweets",
store: "Sri Krishna Sweets",
points: "500 pts",
icon: "🍰",
},
{
title: "Free Organic Honey",
store: "Organic Greens Store",
points: "1200 pts",
icon: "🍯",
},
{
title: "₹100 Off order",
store: "A1 Biryani House",
points: "900 pts",
icon: "🍗",
},
].map((reward, idx) => (
<div
key={idx}
className="flex items-center justify-between rounded-xl border border-dashed border-[#6330D6]/20 bg-white p-2.5"
>
<div className="flex items-center gap-3 min-w-0">
<span className="text-lg shrink-0">{reward.icon}</span>
<div className="min-w-0">
<h5 className="text-[10px] font-bold text-[#16001D] truncate">{reward.title}</h5>
<p className="text-[8px] text-[#5E5866] truncate">{reward.store}</p>
</div>
</div>
<button className="rounded-lg bg-[#6330D6]/10 px-2 py-0.5 text-[8px] font-black text-[#6330D6] shrink-0">
{reward.points}
</button>
</div>
))}
</div>
</div>
{/* Bottom Nav Bar */}
<div className="flex border-t border-[#6330D6]/10 bg-white py-1.5 justify-around items-center shrink-0">
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="explore" size={16} />
<span className="text-[8px] mt-0.5">Explore</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="favorite" size={16} />
<span className="text-[8px] mt-0.5">Favorites</span>
</div>
<div className="flex flex-col items-center text-[#65149A]">
<MaterialIcon icon="redeem" size={16} fill />
<span className="text-[8px] font-bold mt-0.5">Rewards</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="shopping_bag" size={16} />
<span className="text-[8px] mt-0.5">Orders</span>
</div>
</div>
</div>
),
},
// 3: Orders (Pickup/Delivery & Reordering)
{
id: "orders",
content: (
<div className="flex h-full flex-col bg-[#FDFBFF] text-[#16001D]">
{/* Header */}
<div className="px-4 pt-8 pb-3 bg-white border-b border-[#6330D6]/5 flex items-center justify-between shrink-0">
<span className="font-display text-xs font-black tracking-tight text-[#16001D]">Track Order</span>
<span className="text-[9px] font-bold text-[#22C55E] bg-[#22C55E]/10 px-2.5 py-0.5 rounded-full shrink-0">
Out for Delivery
</span>
</div>
{/* Map simulation */}
<div className="h-28 bg-[#EEF4FF] relative overflow-hidden shrink-0">
{/* Roads */}
<svg className="absolute inset-0 w-full h-full" xmlns="http://www.w3.org/2000/svg">
<path d="M-10 40 h300 M50 0 v200 M180 0 v200" stroke="white" strokeWidth="6" />
<path d="M-10 40 h300 M50 0 v200 M180 0 v200" stroke="#E2E8F0" strokeWidth="1" strokeDasharray="3 3" />
{/* Route */}
<motion.path
d="M50 80 L180 80 L180 30"
fill="none"
stroke="#6330D6"
strokeWidth="3.5"
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 4, repeat: Infinity, ease: "linear" }}
/>
</svg>
{/* Store Pin */}
<div className="absolute left-[38px] top-[68px] flex h-6 w-6 items-center justify-center rounded-full bg-[#65149A] text-white shadow-md">
<MaterialIcon icon="store" size={11} className="text-white" />
</div>
{/* Delivery Biker Pin */}
<motion.div
animate={{
left: ["50px", "180px", "180px"],
top: ["80px", "80px", "30px"],
}}
transition={{ duration: 4, repeat: Infinity, ease: "linear" }}
className="absolute -translate-x-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full bg-[#22C55E] text-white shadow-md border border-white"
>
<MaterialIcon icon="local_shipping" size={9} className="text-white" />
</motion.div>
{/* Customer Pin */}
<div className="absolute left-[168px] top-[18px] flex h-6 w-6 items-center justify-center rounded-full bg-[#E59900] text-white shadow-md animate-pulse">
<MaterialIcon icon="location_on" size={11} className="text-white" />
</div>
</div>
{/* Order Details */}
<div className="flex-1 p-4 space-y-3 overflow-y-auto">
<div className="rounded-xl border border-[#6330D6]/5 bg-white p-3 shadow-sm">
<div className="flex justify-between items-start">
<div>
<h4 className="text-[11px] font-black text-[#16001D]">Sri Krishna Sweets</h4>
<p className="text-[8px] text-[#5E5866] mt-0.5">Order #88392 Delivery in 12 mins</p>
</div>
<span className="text-[11px] font-bold text-[#6330D6] shrink-0">240</span>
</div>
<div className="mt-2.5 pt-2.5 border-t border-[#6330D6]/5 text-[9px] text-[#5E5866] space-y-1">
<div className="flex justify-between">
<span className="truncate">1x Special Mysore Pak (250g)</span>
<span className="shrink-0 ml-1">160</span>
</div>
<div className="flex justify-between">
<span className="truncate">1x Fine Laddu (4 pcs)</span>
<span className="shrink-0 ml-1">80</span>
</div>
</div>
</div>
{/* Quick Reorder Section */}
<div className="rounded-xl border border-[#6330D6]/5 bg-white p-3 shadow-sm">
<span className="text-[9px] uppercase font-black text-[#5E5866] tracking-wider block mb-2">
Order Again
</span>
<div className="flex items-center justify-between bg-[#65149A]/5 p-2 rounded-lg gap-2">
<div className="min-w-0 flex-1">
<h5 className="text-[9px] font-black truncate text-[#65149A]">Last ordered 3 days ago</h5>
<p className="text-[8px] text-[#5E5866] truncate">Mysore Pak from Sri Krishna Sweets</p>
</div>
<button className="bg-[#65149A] hover:bg-[#520f7d] transition-colors text-white text-[9px] font-bold px-2 py-0.5 rounded shrink-0 flex items-center gap-1">
<MaterialIcon icon="history" size={10} className="text-white" />
<span>Reorder</span>
</button>
</div>
</div>
</div>
{/* Bottom Nav Bar */}
<div className="flex border-t border-[#6330D6]/10 bg-white py-1.5 justify-around items-center shrink-0">
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="explore" size={16} />
<span className="text-[8px] mt-0.5">Explore</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="favorite" size={16} />
<span className="text-[8px] mt-0.5">Favorites</span>
</div>
<div className="flex flex-col items-center text-[#5E5866]/60">
<MaterialIcon icon="redeem" size={16} />
<span className="text-[8px] mt-0.5">Rewards</span>
</div>
<div className="flex flex-col items-center text-[#65149A]">
<MaterialIcon icon="shopping_bag" size={16} fill />
<span className="text-[8px] font-bold mt-0.5">Orders</span>
</div>
</div>
</div>
),
},
];
return (
<div className="relative mx-auto flex w-full max-w-[280px] sm:max-w-[310px] items-center justify-center">
{/* Decorative ambient background glows */}
<div className="absolute inset-0 -z-10 bg-gradient-to-tr from-purple-500/20 to-[#6330D6]/20 blur-3xl rounded-full transform scale-110 pointer-events-none" />
{/* Phone chassis */}
<motion.div
animate={{
y: [-8, 8, -8],
rotate: [-0.5, 0.5, -0.5]
}}
transition={{
repeat: Infinity,
duration: 6,
ease: "easeInOut",
}}
className="w-full relative z-10 border-[10px] sm:border-[12px] border-neutral-900 rounded-[38px] sm:rounded-[44px] shadow-[0_24px_50px_rgba(22,0,29,0.15)] bg-neutral-950 aspect-[9/18.8] overflow-hidden select-none"
>
{/* Dynamic Island */}
<div className="absolute top-2.5 sm:top-3.5 left-1/2 -translate-x-1/2 w-20 sm:w-24 h-4 sm:h-5 bg-neutral-900 rounded-full z-30 flex items-center justify-end px-3">
<span className="h-1 w-1 sm:h-1.5 sm:w-1.5 rounded-full bg-emerald-500 animate-pulse mr-0.5" />
<span className="h-1 w-1 rounded-full bg-blue-600/60" />
</div>
{/* Gloss overlay reflection */}
<div className="absolute inset-0 pointer-events-none bg-gradient-to-tr from-transparent via-white/[0.04] to-transparent z-25 rounded-[26px] sm:rounded-[32px]" />
{/* Screens container */}
<div className="w-full h-full rounded-[26px] sm:rounded-[32px] overflow-hidden bg-white relative">
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={activeScreen}
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.04 }}
transition={{ duration: 0.45, ease: [0.22, 1, 0.36, 1] }}
className="absolute inset-0 w-full h-full"
>
{screens[activeScreen]?.content}
</motion.div>
</AnimatePresence>
</div>
</motion.div>
{/* Small floating decorative location pins and subtle connection dots */}
<div className="absolute -left-8 top-1/4 z-20 pointer-events-none hidden sm:block">
<motion.div
animate={{ y: [-5, 5, -5] }}
transition={{ repeat: Infinity, duration: 4.5, ease: "easeInOut" }}
className="flex items-center gap-1.5 rounded-full bg-white px-2.5 py-1 shadow-md border border-[#6330D6]/10"
>
<span className="text-xs">📍</span>
<span className="text-[9px] font-black text-[#16001D]">300m away</span>
</motion.div>
</div>
<div className="absolute -right-10 bottom-1/3 z-20 pointer-events-none hidden sm:block">
<motion.div
animate={{ y: [5, -5, 5] }}
transition={{ repeat: Infinity, duration: 5, ease: "easeInOut" }}
className="flex items-center gap-1.5 rounded-full bg-white px-2.5 py-1 shadow-md border border-[#6330D6]/10"
>
<span className="text-xs">🎁</span>
<span className="text-[9px] font-black text-[#16001D]">+50 pts</span>
</motion.div>
</div>
{/* Decorative dot connectors */}
<svg className="absolute inset-0 w-full h-full -z-10 pointer-events-none overflow-visible hidden sm:block" xmlns="http://www.w3.org/2000/svg">
{/* Left connector */}
<motion.path
d="M -30 110 Q -5 130 5 100"
fill="none"
stroke="#6330D6"
strokeWidth="1"
strokeDasharray="3 3"
opacity="0.25"
/>
<circle cx="-30" cy="110" r="2.5" fill="#6330D6" opacity="0.4" />
{/* Right connector */}
<motion.path
d="M 330 220 Q 300 240 280 210"
fill="none"
stroke="#6330D6"
strokeWidth="1"
strokeDasharray="3 3"
opacity="0.25"
/>
<circle cx="330" cy="220" r="2.5" fill="#6330D6" opacity="0.4" />
</svg>
</div>
);
}

View File

@@ -91,11 +91,42 @@ export function ProductFeatureGrid({
</motion.p>
</motion.div>
<div className="mt-12 grid items-center gap-10 lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)] lg:gap-16">
{/* ── Animated device illustration (parallax) ── */}
<div className="mt-12 grid items-center gap-10 lg:grid-cols-12 lg:gap-16">
{/* ── Compact 2-Column Feature Grid (Left on Desktop) ── */}
<motion.div
variants={staggerCards}
initial="hidden"
whileInView="visible"
viewport={viewport20}
className="lg:col-span-7 order-1"
>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{features.map((f) => (
<motion.div
key={f.title}
variants={rowVariant}
className="group relative flex flex-col gap-3 rounded-2xl border border-[#6330D6]/10 bg-white p-5 shadow-[0_4px_16px_rgba(22,0,29,0.02)] transition-all duration-300 hover:-translate-y-1 hover:border-[#6330D6]/25 hover:shadow-[0_10px_24px_rgba(22,0,29,0.05)]"
>
<div className="flex items-center gap-3">
<span className="relative z-10 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-[#6330D6]/[0.08] text-[#6330D6] transition-all duration-300 group-hover:scale-110 group-hover:bg-[#6330D6] group-hover:text-white">
<MaterialIcon icon={f.icon} size={18} />
</span>
<h3 className="font-display font-black text-[#16001D] text-[15px] leading-tight transition-colors duration-200 group-hover:text-[#65149A]">
{f.title}
</h3>
</div>
<p className="text-[13px] leading-relaxed text-[#5E5866]">
{f.desc}
</p>
</motion.div>
))}
</div>
</motion.div>
{/* ── Animated device illustration (Right on Desktop) ── */}
<motion.div
style={{ y: mediaY }}
className={`relative mx-auto w-full ${isPhone ? "max-w-[600px]" : "max-w-[440px]"} ${mediaRight ? "lg:order-2" : "lg:order-1"}`}
className={`lg:col-span-5 order-2 relative mx-auto w-full ${isPhone ? "max-w-[600px]" : "max-w-[440px]"}`}
>
{/* ambient glow */}
<div
@@ -108,40 +139,6 @@ export function ProductFeatureGrid({
/>
{isPhone ? <ConsumerShowcase /> : <DashboardMockup />}
</motion.div>
{/* ── Editorial feature list (was a 4-col card grid) ── */}
<motion.ul
variants={staggerCards}
initial="hidden"
whileInView="visible"
viewport={viewport20}
className={`relative ${mediaRight ? "lg:order-1" : "lg:order-2"}`}
>
{/* connection line threading the icons */}
<span
aria-hidden="true"
className="pointer-events-none absolute left-[23px] top-6 bottom-6 w-px bg-gradient-to-b from-[#6330D6]/25 via-[#6330D6]/12 to-transparent"
/>
{features.map((f) => (
<motion.li
key={f.title}
variants={rowVariant}
className="group relative flex items-start gap-4 rounded-2xl px-3 py-3.5 transition-colors duration-200 hover:bg-[#6330D6]/[0.04]"
>
<span className="relative z-10 mt-0.5 flex h-[38px] w-[38px] shrink-0 items-center justify-center rounded-xl bg-[#6330D6]/[0.08] text-[#6330D6] transition-transform duration-200 group-hover:scale-110 group-hover:bg-[#6330D6] group-hover:text-white">
<MaterialIcon icon={f.icon} size={20} />
</span>
<span className="min-w-0">
<span className="block font-display font-black text-[#16001D] text-[17px] leading-tight">
{f.title}
</span>
<span className="mt-1 block text-[14px] leading-relaxed text-[#5E5866]">
{f.desc}
</span>
</span>
</motion.li>
))}
</motion.ul>
</div>
{footnote && (