diff --git a/components/layout/site-header.tsx b/components/layout/site-header.tsx index ff3d8ac..1f5e1ba 100644 --- a/components/layout/site-header.tsx +++ b/components/layout/site-header.tsx @@ -24,47 +24,24 @@ const navItems = [ { key: "contact", path: "/contact" }, ]; -const brandTitleCharacters = [ - ...Array.from("MOH").map((character) => ({ character, tone: "base" as const })), - { character: " ", tone: "space" as const }, - ...Array.from("FARAWATI").map((character) => ({ character, tone: "accent" as const })), -]; -const brandShellVariants = { - hidden: {}, - visible: { - transition: { - staggerChildren: 0.035, - delayChildren: 0.18, - }, - }, -} as const; -const brandCharacterVariants = { - hidden: { - opacity: 0, - y: 18, - rotateX: -80, - filter: "blur(8px)", - }, - visible: { - opacity: 1, - y: 0, - rotateX: 0, - filter: "blur(0px)", - transition: { - type: "spring" as const, - stiffness: 280, - damping: 20, - mass: 0.8, - }, - }, -} as const; - type SiteHeaderProps = { isAdmin?: boolean; lightLogoUrl?: string | null; darkLogoUrl?: string | null; }; +function getDirectionalReveal(direction: "rtl" | "ltr") { + return direction === "rtl" + ? { + initial: { opacity: 0, x: -16, clipPath: "inset(0 0 0 100%)", filter: "blur(10px)" }, + animate: { opacity: 1, x: 0, y: 0, clipPath: "inset(0 0 0 0)", filter: "blur(0px)" }, + } + : { + initial: { opacity: 0, x: 16, clipPath: "inset(0 100% 0 0)", filter: "blur(10px)" }, + animate: { opacity: 1, x: 0, y: 0, clipPath: "inset(0 0 0 0)", filter: "blur(0px)" }, + }; +} + function isNavItemActive(currentPath: string, itemPath: string) { if (itemPath === "/") { return currentPath === "/"; @@ -134,6 +111,29 @@ export function SiteHeader({ const t = useTranslations("navigation"); const reducedMotion = useReducedMotion(); const isArabic = locale === "ar"; + const languageDirection = isArabic ? "rtl" : "ltr"; + const titleMotion = 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 ? "مــــــطـــــــــــــور ويــــــــــب | فــــــــــــريــــــلانــــــســــــر" : "Webdesigner Bremen | Freelancer"; @@ -182,51 +182,39 @@ export function SiteHeader({ priority className="relative h-10 opacity-95" replayKey={logoReplayKey} + direction={languageDirection} /> - - {isArabic ? ( - - مــــحـــمــد - {" "} - فــــرواتـــي - - ) : ( - - {brandTitleCharacters.map(({ character, tone }, index) => ( - - {character === " " ? "\u00A0" : character} - - ))} - - )} + {isArabic ? ( + <> + مــــحـــمــد + {" "} + فــــرواتـــي + + ) : ( + <> + MOH + {" "} + FARAWATI + + )} + + {brandSubtitle} - +
- + + {isOpen ? : } + + +
diff --git a/components/layout/site-logo.tsx b/components/layout/site-logo.tsx index 7ef571b..4d18293 100644 --- a/components/layout/site-logo.tsx +++ b/components/layout/site-logo.tsx @@ -14,6 +14,7 @@ type SiteLogoProps = { priority?: boolean; className?: string; replayKey?: number; + direction?: "rtl" | "ltr"; }; export function SiteLogo({ @@ -23,11 +24,15 @@ export function SiteLogo({ priority = false, className, replayKey = 0, + direction = "ltr", }: SiteLogoProps) { const { theme, resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); - const controls = useAnimationControls(); + const containerControls = useAnimationControls(); + const spinControls = useAnimationControls(); const reducedMotion = useReducedMotion(); + const directionalOffset = direction === "rtl" ? 42 : -42; + const spinStart = direction === "rtl" ? -360 : 360; useEffect(() => { setMounted(true); @@ -38,29 +43,35 @@ export function SiteLogo({ return; } - controls.set({ + containerControls.set({ opacity: 0, - x: -42, + x: directionalOffset, scale: 0.88, - rotate: -360, filter: "blur(8px)", }); + spinControls.set({ rotate: 0 }); - void controls.start({ + void containerControls.start({ opacity: 1, x: 0, scale: 1, - rotate: 0, filter: "blur(0px)", transition: { x: { duration: 0.78, ease: [0.22, 1, 0.36, 1] }, opacity: { duration: 0.46 }, - rotate: { duration: 0.56, ease: "easeOut" }, scale: { duration: 0.72, ease: [0.22, 1, 0.36, 1] }, filter: { duration: 0.42, ease: [0.22, 1, 0.36, 1] }, }, }); - }, [controls, reducedMotion, replayKey]); + + void spinControls.start({ + rotate: spinStart, + transition: { + duration: 0.78, + ease: [0.22, 1, 0.36, 1], + }, + }); + }, [containerControls, directionalOffset, reducedMotion, replayKey, spinControls, spinStart]); const activeTheme = theme === "system" ? resolvedTheme : theme; const isDark = mounted && activeTheme === "dark"; @@ -69,25 +80,32 @@ export function SiteLogo({ return ( - {alt} + + {alt} + ); }