Refine localized header branding

This commit is contained in:
MOH
2026-03-09 08:03:57 +01:00
parent 583729db53
commit 5e33395a9b
+94 -42
View File
@@ -1,11 +1,11 @@
"use client"; "use client";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { LayoutDashboard, Menu, X } from "lucide-react"; 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 { useState } from "react"; import { useEffect, useState } from "react";
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";
@@ -127,10 +127,15 @@ export function SiteHeader({
darkLogoUrl, darkLogoUrl,
}: SiteHeaderProps) { }: SiteHeaderProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const pathname = usePathname(); const pathname = usePathname();
const locale = useLocale(); const locale = useLocale();
const t = useTranslations("navigation"); const t = useTranslations("navigation");
const reducedMotion = useReducedMotion();
const isArabic = locale === "ar"; const isArabic = locale === "ar";
const brandSubtitle = isArabic
? "مــــــطـــــــــــــور ويــــــــــب | فــــــــــــريــــــلانــــــســــــر"
: "Webdesigner Bremen | Freelancer";
const desktopControlRailClassName = const desktopControlRailClassName =
"inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1 shadow-panel backdrop-blur-chrome"; "inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1 shadow-panel backdrop-blur-chrome";
const desktopControlButtonClassName = const desktopControlButtonClassName =
@@ -138,19 +143,40 @@ export function SiteHeader({
const mobileControlButtonClassName = const mobileControlButtonClassName =
"h-10 w-10 rounded-pill border border-border/70 bg-background text-foreground/80 shadow-xs hover:bg-accent hover:text-foreground"; "h-10 w-10 rounded-pill border border-border/70 bg-background text-foreground/80 shadow-xs hover:bg-accent hover:text-foreground";
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 24);
};
handleScroll();
window.addEventListener("scroll", handleScroll, { passive: true });
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
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]"> <Container className="max-w-[88rem]">
<div className="relative grid min-h-[4.5rem] grid-cols-[auto_1fr_auto] items-center gap-3 lg:min-h-[5rem]"> <div className="relative grid min-h-[4.5rem] grid-cols-[auto_1fr_auto] items-center gap-3 lg:min-h-[5rem]">
<div className="col-start-1 flex items-center justify-self-start"> <motion.div
initial={false}
animate={isScrolled ? { opacity: 0, x: -28 } : { opacity: 1, x: 0 }}
transition={reducedMotion ? { duration: 0 } : { duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
className={cn(
"col-start-1 flex items-center justify-self-start",
isScrolled ? "lg:pointer-events-none lg:invisible" : "",
)}
>
<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"
> >
<motion.span <motion.span
initial={{ opacity: 0, x: -18, scale: 0.82, rotate: -14, filter: "blur(10px)" }} initial={reducedMotion ? false : isArabic ? { opacity: 0, x: 18, scale: 0.82, rotate: 14, filter: "blur(10px)" } : { opacity: 0, x: -18, scale: 0.82, rotate: -14, filter: "blur(10px)" }}
animate={{ opacity: 1, x: 0, scale: 1, rotate: 0, filter: "blur(0px)" }} animate={{ opacity: 1, x: 0, scale: 1, rotate: 0, filter: "blur(0px)" }}
transition={{ type: "spring", stiffness: 240, damping: 20, mass: 0.9 }} transition={reducedMotion ? { duration: 0 } : { duration: 0.32, ease: [0.22, 1, 0.36, 1] }}
className="relative inline-flex items-center justify-center [transform-style:preserve-3d]" className="relative inline-flex items-center justify-center [transform-style:preserve-3d]"
> >
<SiteLogo <SiteLogo
@@ -162,45 +188,68 @@ export function SiteHeader({
/> />
</motion.span> </motion.span>
<motion.span <motion.span
initial="hidden" initial={isArabic ? false : "hidden"}
animate="visible" animate="visible"
variants={brandShellVariants} variants={reducedMotion || isArabic ? undefined : brandShellVariants}
className="flex flex-col justify-center" className="flex flex-col justify-center"
> >
<span className="flex flex-wrap items-center text-[1.28rem] font-black leading-[1.2]"> {isArabic ? (
{brandTitleCharacters.map(({ character, tone }, index) => ( <motion.span
<motion.span initial={reducedMotion ? false : { opacity: 0, x: 16, filter: "blur(8px)" }}
key={`${character}-${index}`} animate={{ opacity: 1, x: 0, filter: "blur(0px)" }}
variants={brandCharacterVariants} transition={reducedMotion ? { duration: 0 } : { duration: 0.42, delay: 0.12, ease: [0.22, 1, 0.36, 1] }}
className={cn( className="flex flex-col text-[1.28rem] font-black leading-[1.2]"
character === " " ? "mr-[0.11em] w-[0.11em]" : "mr-[0.005em] inline-block", dir="rtl"
tone === "base" ? "text-[hsl(var(--hero-ink))] dark:text-foreground" : "", >
tone === "accent" ? "text-primary" : "", <span className="text-[hsl(var(--hero-ink))] dark:text-foreground">مــــحـــمــد</span>
)} <span className="text-primary">فــــرواتـــي</span>
> </motion.span>
{character === " " ? "\u00A0" : character} ) : (
</motion.span> <span className="flex flex-wrap items-center text-[1.28rem] font-black leading-[1.2]">
))} {brandTitleCharacters.map(({ character, tone }, index) => (
</span> <motion.span
key={`${character}-${index}`}
variants={reducedMotion ? undefined : brandCharacterVariants}
className={cn(
character === " " ? "mr-[0.11em] w-[0.11em]" : "mr-[0.005em] inline-block",
tone === "base" ? "text-[hsl(var(--hero-ink))] dark:text-foreground" : "",
tone === "accent" ? "text-primary" : "",
)}
>
{character === " " ? "\u00A0" : character}
</motion.span>
))}
</span>
)}
<motion.span <motion.span
initial={{ opacity: 0, y: 20, clipPath: "inset(0 0 100% 0)", filter: "blur(10px)" }} initial={
animate={{ opacity: 1, y: 0, clipPath: "inset(0 0 0% 0)", filter: "blur(0px)" }} reducedMotion
transition={{ duration: 0.7, delay: 0.34, ease: [0.22, 1, 0.36, 1] }} ? false
className="-mt-0.5 text-[0.65rem] font-light leading-[1.2] tracking-[0.008em] text-foreground/78" : isArabic
? { opacity: 0, x: -16, clipPath: "inset(0 0 0 100%)", filter: "blur(10px)" }
: { 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)" }}
transition={reducedMotion ? { duration: 0 } : { duration: 0.7, delay: 0.34, ease: [0.22, 1, 0.36, 1] }}
className={cn(
"-mt-0.5 text-[0.65rem] font-light leading-[1.2] tracking-[0.008em] text-foreground/78",
isArabic ? "text-right" : "",
)}
dir={isArabic ? "rtl" : undefined}
> >
Webdesigner Bremen | Freelancer {brandSubtitle}
</motion.span> </motion.span>
</motion.span> </motion.span>
</Link> </Link>
</div> </motion.div>
<div className="pointer-events-none absolute left-1/2 top-1/2 hidden -translate-x-1/2 -translate-y-1/2 lg:flex"> <div className="col-start-2 hidden justify-center lg:flex">
<motion.nav <motion.nav
initial={{ opacity: 0, y: -8 }} initial={false}
animate={{ opacity: 1, y: 0 }} animate={isScrolled ? { y: -1, scale: 0.985 } : { y: 0, scale: 1 }}
transition={{ duration: 0.28, ease: "easeOut" }} transition={reducedMotion ? { duration: 0 } : { duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
className={cn( className={cn(
"pointer-events-auto inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1.5 shadow-panel backdrop-blur-chrome", "inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1.5 shadow-panel 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]",
)} )}
> >
@@ -208,13 +257,16 @@ export function SiteHeader({
</motion.nav> </motion.nav>
</div> </div>
<div className="hidden items-center justify-end lg:flex"> <motion.div
<motion.div initial={false}
initial={{ opacity: 0, y: -8 }} animate={isScrolled ? { opacity: 0, x: 28 } : { opacity: 1, x: 0 }}
animate={{ opacity: 1, y: 0 }} transition={reducedMotion ? { duration: 0 } : { duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
transition={{ duration: 0.28, ease: "easeOut", delay: 0.03 }} className={cn(
className={desktopControlRailClassName} "col-start-3 hidden items-center justify-end lg:flex",
> isScrolled ? "pointer-events-none invisible" : "",
)}
>
<div className={desktopControlRailClassName}>
<LocaleToggle <LocaleToggle
locale={locale} locale={locale}
className={desktopControlButtonClassName} className={desktopControlButtonClassName}
@@ -237,8 +289,8 @@ export function SiteHeader({
</Link> </Link>
</Button> </Button>
) : null} ) : null}
</motion.div> </div>
</div> </motion.div>
<Button <Button
type="button" type="button"