Add responsive nav link stretch
This commit is contained in:
@@ -9,6 +9,7 @@ All notable changes to the New Optic website will be documented in this file.
|
||||
- Removed the desktop liquid glass WebGL layer from navbar and CTA controls so text remains readable and glass surfaces do not show dark rendering artifacts while scrolling.
|
||||
- Replaced the mobile hamburger dropdown with a fast, solid menu animation without bounce or stretch effects.
|
||||
- Kept the language switcher visible in the mobile top navigation bar.
|
||||
- Added cursor-reactive stretch motion to desktop nav links and matched the glossy dark WhatsApp CTA style on mobile.
|
||||
|
||||
## [1.0.0] - 2026-05-16
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { AnimatePresence, motion, useMotionValue, useSpring, useTransform } from "framer-motion";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { type MouseEvent, useState } from "react";
|
||||
import { type MouseEvent, type PointerEvent, useState } from "react";
|
||||
import { business, type Locale } from "@/config/business";
|
||||
import type { Messages } from "@/messages";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
import PhysicsButton from "./PhysicsButton";
|
||||
|
||||
const navCtaClassName =
|
||||
"rounded-full border border-white/45 bg-[linear-gradient(180deg,#565b63_0%,#20242a_48%,#111317_100%)] font-semibold text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.38),inset_0_-10px_18px_rgba(0,0,0,0.18),0_14px_32px_rgba(17,19,23,0.18)] transition-colors hover:bg-[linear-gradient(180deg,#476a95_0%,#264b73_54%,#173655_100%)]";
|
||||
|
||||
export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { locale: Locale; onLocaleChange: (locale: Locale) => void; t: Messages; whatsappUrl: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -34,22 +37,20 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
|
||||
|
||||
<div className="relative z-[3] hidden items-center gap-7 lg:flex">
|
||||
{t.nav.links.map((link) => (
|
||||
<a key={link.href} href={link.href} onClick={(event) => scrollToSection(event, link.href)} className="text-sm font-medium text-ink/58 transition hover:text-ink">
|
||||
{link.label}
|
||||
</a>
|
||||
<StretchNavLink key={link.href} href={link.href} label={link.label} onClick={(event) => scrollToSection(event, link.href)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="relative z-[3] hidden items-center gap-3 md:flex">
|
||||
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} />
|
||||
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-5 py-2.5 text-sm font-semibold text-white shadow-soft transition-colors hover:bg-optical">
|
||||
<PhysicsButton href={whatsappUrl} external className={`${navCtaClassName} px-5 py-2.5`}>
|
||||
{t.nav.cta}
|
||||
</PhysicsButton>
|
||||
</div>
|
||||
|
||||
<div className="relative z-[3] flex items-center gap-2 md:hidden">
|
||||
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} className="bg-white/80 backdrop-blur-none" buttonClassName="px-2 sm:px-3" />
|
||||
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-3 py-2.5 text-xs font-semibold text-white shadow-soft transition-colors hover:bg-optical max-[374px]:hidden sm:px-5 sm:text-sm">
|
||||
<PhysicsButton href={whatsappUrl} external className={`${navCtaClassName} px-3 py-2.5 text-xs max-[374px]:hidden sm:px-5 sm:text-sm`}>
|
||||
{t.nav.cta}
|
||||
</PhysicsButton>
|
||||
<motion.button
|
||||
@@ -97,3 +98,42 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function StretchNavLink({ href, label, onClick }: { href: string; label: string; onClick: (event: MouseEvent<HTMLAnchorElement>) => void }) {
|
||||
const pullX = useMotionValue(0);
|
||||
const pullY = useMotionValue(0);
|
||||
const x = useSpring(pullX, { stiffness: 260, damping: 16, mass: 0.3 });
|
||||
const y = useSpring(pullY, { stiffness: 260, damping: 16, mass: 0.3 });
|
||||
const scaleX = useTransform(x, [-18, 0, 18], [1.12, 1, 1.12]);
|
||||
const scaleY = useTransform(y, [-12, 0, 12], [0.94, 1, 0.94]);
|
||||
|
||||
function handlePointerMove(event: PointerEvent<HTMLAnchorElement>) {
|
||||
if (event.pointerType === "touch") return;
|
||||
|
||||
const bounds = event.currentTarget.getBoundingClientRect();
|
||||
const localX = event.clientX - bounds.left - bounds.width / 2;
|
||||
const localY = event.clientY - bounds.top - bounds.height / 2;
|
||||
|
||||
pullX.set(Math.max(-18, Math.min(18, localX * 0.42)));
|
||||
pullY.set(Math.max(-12, Math.min(12, localY * 0.38)));
|
||||
}
|
||||
|
||||
function release() {
|
||||
pullX.set(0);
|
||||
pullY.set(0);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.a
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerLeave={release}
|
||||
onPointerCancel={release}
|
||||
style={{ x, y, scaleX, scaleY }}
|
||||
className="inline-flex min-h-9 items-center rounded-full px-1 text-sm font-medium text-ink/58 will-change-transform transition-colors hover:text-ink"
|
||||
>
|
||||
{label}
|
||||
</motion.a>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user