update d2c brands
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user