update d2c brands

This commit is contained in:
2026-07-02 15:12:45 +05:30
parent 08a44524fd
commit 2b1e7094dc
13 changed files with 282 additions and 130 deletions

View File

@@ -1,12 +1,15 @@
"use client";
import { Fragment } from "react";
import { Fragment, useRef, type MouseEvent } from "react";
import Image from "next/image";
import { motion, useReducedMotion } from "framer-motion";
import { motion, useReducedMotion, useMotionValue, useSpring, useTransform } from "framer-motion";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { HeroLogo } from "@/components/heroes/HeroLogo";
import { viewport } from "@/lib/animations";
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
// power4.out approximation, matches HeroLogo's entrance ease
const EASE_LOGO = [0.16, 1, 0.3, 1] as [number, number, number, number];
const PROOF_ITEMS = [
{ label: "Verified", text: "Local Stores", icon: "store" },
@@ -15,31 +18,25 @@ const PROOF_ITEMS = [
{ label: "Trusted", text: "Local Platform", icon: "star" },
];
const heroStagger = {
hidden: {},
visible: { transition: { staggerChildren: 0.12, delayChildren: 0.05 } },
};
// Shared cascade timing: logo settles at ~1.2s, then badge → heading → description
const badgeVariant = {
hidden: { opacity: 0, y: 10, scale: 0.95 },
visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.38, ease: EASE } },
hidden: { opacity: 0, y: 25 },
visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 0.9, ease: EASE } },
};
// Lines stagger internally
const lineStagger = {
hidden: {},
visible: { transition: { staggerChildren: 0.09 } },
visible: { transition: { staggerChildren: 0.15, delayChildren: 1.05 } },
};
// True mask reveal: text slides up from below a clipping parent
const lineReveal = {
hidden: { y: "110%" },
visible: { y: 0, transition: { duration: 0.72, ease: EASE } },
hidden: { y: 60, opacity: 0, filter: "blur(8px)" },
visible: { y: 0, opacity: 1, filter: "blur(0px)", transition: { duration: 0.9, ease: EASE_LOGO } },
};
const descVariant = {
hidden: { opacity: 0, y: 18 },
visible: { opacity: 1, y: 0, transition: { duration: 0.55, ease: EASE } },
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 1.25, ease: EASE } },
};
const statStagger = {
@@ -54,47 +51,83 @@ const statItem = {
export function Hero() {
const shouldReduce = useReducedMotion();
const sectionRef = useRef<HTMLDivElement>(null);
// Subtle mouse parallax: background drifts 5px toward the cursor
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const springX = useSpring(mouseX, { stiffness: 60, damping: 20, mass: 0.5 });
const springY = useSpring(mouseY, { stiffness: 60, damping: 20, mass: 0.5 });
const bgParallaxX = useTransform(springX, [-0.5, 0.5], [-5, 5]);
const bgParallaxY = useTransform(springY, [-0.5, 0.5], [-5, 5]);
function handleMouseMove(e: MouseEvent<HTMLDivElement>) {
if (shouldReduce) return;
const rect = e.currentTarget.getBoundingClientRect();
mouseX.set((e.clientX - rect.left) / rect.width - 0.5);
mouseY.set((e.clientY - rect.top) / rect.height - 0.5);
}
function handleMouseLeave() {
mouseX.set(0);
mouseY.set(0);
}
return (
<section className="bg-white select-none w-full">
<div className="relative overflow-hidden bg-[#FAF8FC]">
<div
ref={sectionRef}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
className="relative overflow-hidden bg-[#FAF8FC]"
>
<motion.div
className="absolute inset-0 z-0"
initial={{ scale: 1 }}
animate={shouldReduce ? undefined : { scale: 1.06 }}
transition={{ duration: 28, ease: "linear", repeat: Infinity, repeatType: "reverse" }}
style={shouldReduce ? undefined : { x: bgParallaxX, y: bgParallaxY }}
initial={shouldReduce ? undefined : { scale: 1.08 }}
animate={{ scale: 1 }}
transition={{ duration: 3, ease: "easeOut" }}
>
<Image
src="/images/home1.webp"
alt=""
fill
sizes="100vw"
className="absolute inset-0 h-full w-full object-cover object-[62%_center] md:object-[58%_center] xl:object-center"
className="absolute inset-0 h-full w-full object-cover object-[70%_center] md:object-[66%_center] xl:object-[60%_center]"
priority
fetchPriority="high"
aria-hidden="true"
/>
</motion.div>
<div className="absolute inset-0 z-[1] bg-black/40 md:bg-[radial-gradient(ellipse_100%_100%_at_50%_45%,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0.4)_40%,rgba(0,0,0,0.1)_100%)]" />
<motion.div
className="absolute inset-0 z-[1] bg-black/40 md:bg-[radial-gradient(ellipse_100%_100%_at_50%_45%,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0.4)_40%,rgba(0,0,0,0.1)_100%)]"
initial={shouldReduce ? undefined : { opacity: 0.6 }}
animate={{ opacity: 1 }}
transition={{ duration: 1.2, ease: "easeOut" }}
/>
<div className="page-container relative z-10 flex min-h-[560px] items-center justify-center px-6 pt-20 pb-10 sm:px-8 sm:pt-24 md:min-h-[85vh] md:py-20 lg:py-24">
<motion.div
className="relative z-20 flex w-full max-w-[860px] flex-col items-center text-center px-6 py-10 md:px-14 md:py-12"
variants={heroStagger}
initial="hidden"
animate="visible"
>
<div className="relative z-20 flex w-full max-w-[860px] flex-col items-center text-center px-6 py-10 md:px-14 md:py-12">
<HeroLogo />
{/* Badge */}
<motion.span
variants={badgeVariant}
initial="hidden"
animate="visible"
className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-xs font-bold tracking-widest bg-white text-[#6D28D9] shadow-md uppercase mb-6 w-fit"
>
Welcome to Nearle
</motion.span>
{/* H1: line-by-line mask reveal */}
<motion.h1 variants={lineStagger} className="font-display text-[34px] sm:text-5xl lg:text-[68px] xl:text-[76px] font-extrabold text-white tracking-[-0.05em] [text-shadow:0_4px_32px_rgba(0,0,0,0.5)]">
{/* H1: line-by-line reveal */}
<motion.h1
variants={lineStagger}
initial="hidden"
animate="visible"
className="font-display text-[34px] sm:text-5xl lg:text-[68px] xl:text-[76px] font-extrabold text-white tracking-[-0.05em] [text-shadow:0_4px_32px_rgba(0,0,0,0.5)]"
>
<div className="overflow-hidden pb-1">
<motion.div variants={lineReveal} className="leading-[0.95]">
Everything Local.
@@ -110,11 +143,13 @@ export function Hero() {
{/* Description */}
<motion.p
variants={descVariant}
initial="hidden"
animate="visible"
className="max-w-[520px] text-lg md:text-xl text-white/95 leading-[1.6] font-medium mt-6 [text-shadow:0_2px_16px_rgba(0,0,0,0.6)]"
>
Discover verified local stores, exclusive offers, fast delivery, and a better way to shop within your neighbourhood.
</motion.p>
</motion.div>
</div>
</div>
</div>
@@ -150,7 +185,7 @@ export function Hero() {
</motion.div>
{idx < PROOF_ITEMS.length - 1 && (
<div
className="hidden lg:block w-px h-[72px] bg-[#E9D8FD] shrink-0 self-center"
className="hidden lg:block w-px h-[72px] bg-[#E9D8FD] shrink-0 self-center lg:mx-4 xl:mx-6"
aria-hidden="true"
/>
)}