88 lines
5.3 KiB
TypeScript
88 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useMotionTemplate, useMotionValue, useSpring, useTransform } from "framer-motion";
|
|
import { ArrowRight, ShieldCheck } from "lucide-react";
|
|
import Image from "next/image";
|
|
import { useEffect, useState } from "react";
|
|
import { business } from "@/config/business";
|
|
import type { Messages } from "@/messages";
|
|
import PhysicsButton from "./PhysicsButton";
|
|
|
|
export default function HeroSection({ t, whatsappUrl }: { t: Messages; whatsappUrl: string }) {
|
|
const [canAnimateIntro, setCanAnimateIntro] = useState(false);
|
|
const pointerX = useMotionValue(0);
|
|
const pointerY = useMotionValue(0);
|
|
const smoothX = useSpring(pointerX, { stiffness: 90, damping: 22, mass: 0.4 });
|
|
const smoothY = useSpring(pointerY, { stiffness: 90, damping: 22, mass: 0.4 });
|
|
const rotateX = useTransform(smoothY, [-0.5, 0.5], [4, -4]);
|
|
const rotateY = useTransform(smoothX, [-0.5, 0.5], [-5, 5]);
|
|
const imageTransform = useMotionTemplate`perspective(1200px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
|
|
|
|
useEffect(() => {
|
|
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
const desktop = window.matchMedia("(min-width: 1024px)").matches;
|
|
setCanAnimateIntro(desktop && !reduced);
|
|
}, []);
|
|
|
|
return (
|
|
<section id="home" className="relative px-4 pb-20 pt-32 sm:px-6 lg:pb-28 lg:pt-40">
|
|
<motion.div className="absolute left-1/2 top-24 hidden h-72 w-72 -translate-x-1/2 rounded-full bg-optical/10 blur-3xl lg:block" animate={canAnimateIntro ? { scale: [1, 1.16, 1], opacity: [0.55, 0.88, 0.55] } : undefined} transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }} />
|
|
<div className="mx-auto grid max-w-7xl items-center gap-12 lg:grid-cols-[1fr_0.92fr]">
|
|
<motion.div initial={canAnimateIntro ? { opacity: 0, y: 24 } : false} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }} className="relative z-10 text-center lg:text-start">
|
|
<div className="mx-auto mb-6 inline-flex items-center gap-2 rounded-full border border-ink/10 bg-white/65 px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-ink/60 shadow-sm backdrop-blur lg:mx-0">
|
|
<ShieldCheck size={15} className="text-optical" />
|
|
{t.hero.eyebrow}
|
|
</div>
|
|
<h1 className="mx-auto max-w-4xl text-5xl font-semibold tracking-[-0.075em] text-ink sm:text-7xl lg:mx-0 lg:text-8xl">{t.hero.title}</h1>
|
|
<p className="mx-auto mt-6 max-w-2xl text-lg leading-8 text-ink/64 sm:text-xl lg:mx-0">{t.hero.subtitle}</p>
|
|
<div className="mt-9 flex flex-col justify-center gap-3 sm:flex-row lg:justify-start">
|
|
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-7 py-4 text-sm font-semibold text-white shadow-soft transition-colors hover:bg-optical">
|
|
{t.hero.primary}
|
|
<ArrowRight size={16} className="transition group-hover:translate-x-0.5 rtl:rotate-180" />
|
|
</PhysicsButton>
|
|
<PhysicsButton href="#services" className="rounded-full border border-ink/10 bg-white/65 px-7 py-4 text-sm font-semibold text-ink shadow-sm backdrop-blur transition-colors hover:bg-white">
|
|
{t.hero.secondary}
|
|
</PhysicsButton>
|
|
</div>
|
|
<p className="mt-7 text-sm text-ink/48">{t.hero.note}</p>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={canAnimateIntro ? { opacity: 0, scale: 0.96, y: 24 } : false}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
transition={{ duration: 1, delay: 0.1, ease: [0.22, 1, 0.36, 1] }}
|
|
onPointerMove={(event) => {
|
|
if (!canAnimateIntro) return;
|
|
const bounds = event.currentTarget.getBoundingClientRect();
|
|
pointerX.set((event.clientX - bounds.left) / bounds.width - 0.5);
|
|
pointerY.set((event.clientY - bounds.top) / bounds.height - 0.5);
|
|
}}
|
|
onPointerLeave={() => {
|
|
pointerX.set(0);
|
|
pointerY.set(0);
|
|
}}
|
|
style={canAnimateIntro ? { transform: imageTransform } : undefined}
|
|
className="relative mx-auto w-full max-w-xl lg:max-w-none lg:will-change-transform"
|
|
>
|
|
<div className="absolute -inset-6 hidden rounded-[3rem] bg-gradient-to-br from-optical/14 via-white/0 to-silver/45 blur-2xl lg:block" />
|
|
<div className="glass relative overflow-hidden rounded-[2.6rem] p-3">
|
|
<span className="floating-sheen" />
|
|
<div className="relative aspect-[4/3] overflow-hidden rounded-[2rem] bg-silver/40">
|
|
<Image src={business.assets.hero} alt={t.hero.imageAlt} fill sizes="(min-width: 1024px) 45vw, 92vw" className="object-cover" priority />
|
|
</div>
|
|
<div className="absolute bottom-7 left-7 right-7 rounded-[1.7rem] border border-white/70 bg-white/72 p-4 shadow-glass backdrop-blur-xl sm:p-5">
|
|
<div className="flex items-center justify-between gap-4">
|
|
<div>
|
|
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-optical/80">{business.name}</p>
|
|
<p className="mt-1 text-sm font-semibold text-ink">Temara, Morocco</p>
|
|
</div>
|
|
<div className="grid size-12 place-items-center rounded-full bg-ink text-xs font-semibold text-white">2011</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|