Remove header block entrance motion
This commit is contained in:
@@ -5,7 +5,8 @@ import { LayoutDashboard, Menu, X } from "lucide-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useLocale, useTranslations } from "next-intl";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { Container } from "@/components/layout/container";
|
import { Container } from "@/components/layout/container";
|
||||||
import { LocaleToggle } from "@/components/layout/locale-toggle";
|
import { LocaleToggle } from "@/components/layout/locale-toggle";
|
||||||
@@ -106,6 +107,7 @@ export function SiteHeader({
|
|||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
const [logoReplayKey, setLogoReplayKey] = useState(0);
|
const [logoReplayKey, setLogoReplayKey] = useState(0);
|
||||||
|
const logoClickTimesRef = useRef<number[]>([]);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const t = useTranslations("navigation");
|
const t = useTranslations("navigation");
|
||||||
@@ -114,26 +116,6 @@ export function SiteHeader({
|
|||||||
const languageDirection = isArabic ? "rtl" : "ltr";
|
const languageDirection = isArabic ? "rtl" : "ltr";
|
||||||
const titleMotion = getDirectionalReveal(languageDirection);
|
const titleMotion = getDirectionalReveal(languageDirection);
|
||||||
const subtitleMotion = getDirectionalReveal(languageDirection);
|
const subtitleMotion = getDirectionalReveal(languageDirection);
|
||||||
const navEntranceInitial = reducedMotion ? false : { opacity: 0, y: -14, scale: 0.985, filter: "blur(8px)" };
|
|
||||||
const navEntranceVisible = { opacity: 1, y: 0, scale: 1, filter: "blur(0px)" };
|
|
||||||
const controlEntranceInitial = reducedMotion
|
|
||||||
? false
|
|
||||||
: {
|
|
||||||
opacity: 0,
|
|
||||||
x: languageDirection === "rtl" ? -18 : 18,
|
|
||||||
scale: 0.98,
|
|
||||||
filter: "blur(8px)",
|
|
||||||
};
|
|
||||||
const controlEntranceVisible = { opacity: 1, x: 0, scale: 1, filter: "blur(0px)" };
|
|
||||||
const mobileMenuInitial = reducedMotion
|
|
||||||
? false
|
|
||||||
: {
|
|
||||||
opacity: 0,
|
|
||||||
x: languageDirection === "rtl" ? -14 : 14,
|
|
||||||
scale: 0.94,
|
|
||||||
filter: "blur(8px)",
|
|
||||||
};
|
|
||||||
const mobileMenuVisible = { opacity: 1, x: 0, scale: 1, filter: "blur(0px)" };
|
|
||||||
const brandSubtitle = isArabic
|
const brandSubtitle = isArabic
|
||||||
? "مــــــطـــــــــــــور ويــــــــــب | فــــــــــــريــــــلانــــــســــــر"
|
? "مــــــطـــــــــــــور ويــــــــــب | فــــــــــــريــــــلانــــــســــــر"
|
||||||
: "Webdesigner Bremen | Freelancer";
|
: "Webdesigner Bremen | Freelancer";
|
||||||
@@ -157,6 +139,24 @@ export function SiteHeader({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleLogoClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||||
|
setLogoReplayKey((value) => value + 1);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const recentClicks = logoClickTimesRef.current.filter((time) => now - time < 1200);
|
||||||
|
const nextClicks = [...recentClicks, now];
|
||||||
|
|
||||||
|
logoClickTimesRef.current = nextClicks;
|
||||||
|
|
||||||
|
if (nextClicks.length < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logoClickTimesRef.current = [];
|
||||||
|
event.preventDefault();
|
||||||
|
toast(t("logoTripleClick"));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="fixed inset-x-0 top-3 z-40 bg-transparent">
|
<header className="fixed inset-x-0 top-3 z-40 bg-transparent">
|
||||||
<Container className="max-w-[88rem] px-5 sm:px-6 lg:px-8">
|
<Container className="max-w-[88rem] px-5 sm:px-6 lg:px-8">
|
||||||
@@ -173,7 +173,7 @@ export function SiteHeader({
|
|||||||
<Link
|
<Link
|
||||||
href={getLocalizedPath(locale)}
|
href={getLocalizedPath(locale)}
|
||||||
className="inline-flex items-center gap-2.5 rounded-pill px-1 py-1 sm:gap-3 lg:py-2"
|
className="inline-flex items-center gap-2.5 rounded-pill px-1 py-1 sm:gap-3 lg:py-2"
|
||||||
onClick={() => setLogoReplayKey((value) => value + 1)}
|
onClick={handleLogoClick}
|
||||||
>
|
>
|
||||||
<SiteLogo
|
<SiteLogo
|
||||||
lightLogoUrl={lightLogoUrl}
|
lightLogoUrl={lightLogoUrl}
|
||||||
@@ -230,13 +230,9 @@ export function SiteHeader({
|
|||||||
|
|
||||||
<div className="col-start-2 hidden justify-center lg:flex">
|
<div className="col-start-2 hidden justify-center lg:flex">
|
||||||
<motion.nav
|
<motion.nav
|
||||||
initial={navEntranceInitial}
|
initial={false}
|
||||||
animate={
|
animate={isScrolled ? { y: -1, scale: 0.985 } : { y: 0, scale: 1 }}
|
||||||
isScrolled
|
transition={reducedMotion ? { duration: 0 } : { duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||||
? { opacity: 1, y: -1, scale: 0.985, filter: "blur(0px)" }
|
|
||||||
: navEntranceVisible
|
|
||||||
}
|
|
||||||
transition={reducedMotion ? { duration: 0 } : { duration: 0.56, delay: 0.22, ease: [0.22, 1, 0.36, 1] }}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1.5 backdrop-blur-chrome",
|
"inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1.5 backdrop-blur-chrome",
|
||||||
isArabic ? "w-auto max-w-none" : "min-w-[27rem] max-w-[42rem]",
|
isArabic ? "w-auto max-w-none" : "min-w-[27rem] max-w-[42rem]",
|
||||||
@@ -247,13 +243,9 @@ export function SiteHeader({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={controlEntranceInitial}
|
initial={false}
|
||||||
animate={
|
animate={isScrolled ? { opacity: 0, x: 28 } : { opacity: 1, x: 0 }}
|
||||||
isScrolled
|
transition={reducedMotion ? { duration: 0 } : { duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||||
? { opacity: 0, x: languageDirection === "rtl" ? -28 : 28, scale: 0.98, filter: "blur(4px)" }
|
|
||||||
: controlEntranceVisible
|
|
||||||
}
|
|
||||||
transition={reducedMotion ? { duration: 0 } : { duration: 0.56, delay: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-3 hidden items-center justify-end lg:flex",
|
"col-start-3 hidden items-center justify-end lg:flex",
|
||||||
isScrolled ? "pointer-events-none invisible" : "",
|
isScrolled ? "pointer-events-none invisible" : "",
|
||||||
@@ -262,16 +254,25 @@ export function SiteHeader({
|
|||||||
<div className={desktopControlRailClassName}>
|
<div className={desktopControlRailClassName}>
|
||||||
<LocaleToggle
|
<LocaleToggle
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
localeChangedLabels={{
|
||||||
|
de: t("localeChangedDe"),
|
||||||
|
en: t("localeChangedEn"),
|
||||||
|
ar: t("localeChangedAr"),
|
||||||
|
}}
|
||||||
className={desktopControlButtonClassName}
|
className={desktopControlButtonClassName}
|
||||||
/>
|
/>
|
||||||
<SoundToggle
|
<SoundToggle
|
||||||
ariaLabel={t("soundMute")}
|
ariaLabel={t("soundMute")}
|
||||||
mutedAriaLabel={t("soundUnmute")}
|
mutedAriaLabel={t("soundUnmute")}
|
||||||
|
mutedToastLabel={t("soundMuted")}
|
||||||
|
unmutedToastLabel={t("soundEnabled")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={desktopControlButtonClassName}
|
className={desktopControlButtonClassName}
|
||||||
/>
|
/>
|
||||||
<ThemeToggle
|
<ThemeToggle
|
||||||
ariaLabel={t("themeToggle")}
|
ariaLabel={t("themeToggle")}
|
||||||
|
lightToastLabel={t("themeLight")}
|
||||||
|
darkToastLabel={t("themeDark")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={desktopControlButtonClassName}
|
className={desktopControlButtonClassName}
|
||||||
/>
|
/>
|
||||||
@@ -286,9 +287,9 @@ export function SiteHeader({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={mobileMenuInitial}
|
initial={false}
|
||||||
animate={mobileMenuVisible}
|
animate={{ opacity: 1, x: 0, scale: 1, filter: "blur(0px)" }}
|
||||||
transition={reducedMotion ? { duration: 0 } : { duration: 0.52, delay: 0.2, ease: [0.22, 1, 0.36, 1] }}
|
transition={reducedMotion ? { duration: 0 } : { duration: 0.2, ease: [0.22, 1, 0.36, 1] }}
|
||||||
className="col-start-3 ml-auto justify-self-end lg:hidden"
|
className="col-start-3 ml-auto justify-self-end lg:hidden"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -391,17 +392,26 @@ export function SiteHeader({
|
|||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<LocaleToggle
|
<LocaleToggle
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
localeChangedLabels={{
|
||||||
|
de: t("localeChangedDe"),
|
||||||
|
en: t("localeChangedEn"),
|
||||||
|
ar: t("localeChangedAr"),
|
||||||
|
}}
|
||||||
className={mobileControlButtonClassName}
|
className={mobileControlButtonClassName}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<SoundToggle
|
<SoundToggle
|
||||||
ariaLabel={t("soundMute")}
|
ariaLabel={t("soundMute")}
|
||||||
mutedAriaLabel={t("soundUnmute")}
|
mutedAriaLabel={t("soundUnmute")}
|
||||||
|
mutedToastLabel={t("soundMuted")}
|
||||||
|
unmutedToastLabel={t("soundEnabled")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={mobileControlButtonClassName}
|
className={mobileControlButtonClassName}
|
||||||
/>
|
/>
|
||||||
<ThemeToggle
|
<ThemeToggle
|
||||||
ariaLabel={t("themeToggle")}
|
ariaLabel={t("themeToggle")}
|
||||||
|
lightToastLabel={t("themeLight")}
|
||||||
|
darkToastLabel={t("themeDark")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={mobileControlButtonClassName}
|
className={mobileControlButtonClassName}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user