Refactor site hero and localized branding UI

This commit is contained in:
MOH
2026-03-08 05:13:10 +01:00
parent 9edb162148
commit 2bda611a3b
35 changed files with 682 additions and 234 deletions
+100
View File
@@ -0,0 +1,100 @@
"use client";
import { motion } from "framer-motion";
type HeroMotionBackdropProps = {
compact?: boolean;
};
export function HeroMotionBackdrop({ compact = false }: HeroMotionBackdropProps) {
return (
<div className="hero-sheet pointer-events-none absolute inset-0 overflow-hidden">
<div className="hero-sheet-base" />
<motion.div
animate={{
x: compact ? [0, 18, -10, 0] : [0, 44, -24, 0],
y: compact ? [0, -16, 10, 0] : [0, -34, 18, 0],
scale: compact ? [1, 1.03, 0.99, 1] : [1, 1.08, 0.96, 1],
}}
transition={{
duration: compact ? 18 : 26,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
className="hero-blob hero-blob-left"
/>
<motion.div
animate={{
x: compact ? [0, -14, 8, 0] : [0, -36, 18, 0],
y: compact ? [0, 10, -8, 0] : [0, 22, -14, 0],
scale: compact ? [1, 0.98, 1.04, 1] : [1, 0.94, 1.08, 1],
}}
transition={{
duration: compact ? 20 : 30,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
className="hero-blob hero-blob-right"
/>
<motion.div
animate={{
x: compact ? [0, 12, -8, 0] : [0, 26, -14, 0],
y: compact ? [0, -6, 8, 0] : [0, -18, 16, 0],
scale: compact ? [1, 1.02, 0.98, 1] : [1, 1.06, 0.97, 1],
opacity: compact ? [0.36, 0.5, 0.4, 0.36] : [0.34, 0.54, 0.4, 0.34],
}}
transition={{
duration: compact ? 16 : 24,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
className="hero-blob hero-blob-bottom"
/>
<motion.div
animate={{
opacity: compact ? [0.18, 0.26, 0.2, 0.18] : [0.2, 0.3, 0.22, 0.2],
scale: compact ? [1, 1.01, 1, 1] : [1, 1.02, 1, 1],
}}
transition={{
duration: compact ? 16 : 22,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
className="hero-sheet-glow"
/>
<motion.div
animate={{
x: compact ? [0, 10, -8, 0] : [0, 18, -14, 0],
y: compact ? [0, -8, 6, 0] : [0, -18, 10, 0],
rotate: compact ? [0, 2, -1, 0] : [0, 4, -2, 0],
opacity: compact ? [0.12, 0.2, 0.14, 0.12] : [0.14, 0.24, 0.16, 0.14],
}}
transition={{
duration: compact ? 20 : 28,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
className="hero-sheet-wave"
/>
<motion.div
animate={{
backgroundPosition: compact
? ["0 0, 0 0", "40px 24px, 0 0", "0 0, 0 0"]
: ["0 0, 0 0", "64px 40px, 0 0", "0 0, 0 0"],
}}
transition={{
duration: compact ? 20 : 28,
repeat: Number.POSITIVE_INFINITY,
ease: "linear",
}}
className="hero-sheet-noise"
/>
<div className="hero-sheet-veil" />
<div className="hero-sheet-fade" />
</div>
);
}
+52 -27
View File
@@ -1,8 +1,7 @@
import { ArrowRight } from "lucide-react";
import Link from "next/link";
import { HeroAtmosphere } from "@/components/layout/hero-atmosphere";
import { MotionFade } from "@/components/motion-fade";
import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop";
import { Button } from "@/components/ui/button";
import { getLocalizedPath } from "@/lib/locale";
@@ -23,34 +22,60 @@ export function HomeHero({
portfolioLabel,
contactLabel,
}: HomeHeroProps) {
const titleLines = title.split("\n").filter(Boolean);
const isArabic = locale === "ar";
return (
<section className="relative isolate flex min-h-[85vh] items-center overflow-hidden">
<HeroAtmosphere />
<section className="hero-surface hero-home relative isolate flex min-h-[88vh] items-center justify-center overflow-hidden">
<HeroMotionBackdrop />
<div className="hero-header-bridge" />
<div className="hero-content-bridge" />
<div className="relative z-10 mx-auto flex w-full max-w-[72rem] flex-col items-center justify-center px-4 pb-12 pt-24 text-center sm:px-6 lg:px-8 lg:pb-16 lg:pt-28">
<MotionFade className="w-full">
<div className="mx-auto max-w-[54rem]">
<p className="text-sm font-medium tracking-[0.08em] text-foreground/62">
{kicker}
</p>
<h1 className="mt-6 whitespace-pre-line text-balance text-[3rem] font-semibold leading-[0.92] tracking-[-0.06em] text-foreground sm:text-[4.5rem] lg:text-[6.75rem]">
{title}
</h1>
<p className="mx-auto mt-6 max-w-[30rem] text-sm leading-6 text-foreground/66 sm:text-base">
{description}
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Button asChild size="lg" className="px-6">
<Link href={getLocalizedPath(locale, "/portfolio")}>
{portfolioLabel}
<ArrowRight className="h-4 w-4" />
</Link>
</Button>
<Button asChild variant="outline" size="lg" className="px-6">
<Link href={getLocalizedPath(locale, "/contact")}>{contactLabel}</Link>
</Button>
</div>
<div className="mx-auto flex max-w-[46rem] flex-col items-center">
<p className="text-base font-medium tracking-[-0.03em] text-[hsl(var(--hero-ink)/0.76)] drop-shadow-[0_10px_24px_rgba(15,23,42,0.16)] sm:text-lg">
{kicker}
</p>
<h1
className={
isArabic
? "mt-5 flex w-full max-w-[40rem] flex-col text-[3rem] font-bold leading-[1.12] tracking-normal text-[hsl(var(--hero-ink))] drop-shadow-[0_18px_40px_rgba(15,23,42,0.16)] sm:text-[4.25rem] lg:max-w-[48rem] lg:text-[5.35rem]"
: "mt-4 flex w-full max-w-[36rem] flex-col gap-y-1 text-[3.25rem] font-semibold leading-[0.9] tracking-[-0.085em] text-[hsl(var(--hero-ink))] drop-shadow-[0_18px_40px_rgba(15,23,42,0.22)] sm:max-w-[38rem] sm:text-[4.9rem] lg:max-w-[39rem] lg:gap-y-2 lg:text-[6.15rem]"
}
>
{titleLines.map((line, index) => (
<span
key={`${locale}-${index}-${line}`}
className={
isArabic
? index === 1
? "self-center whitespace-nowrap"
: "self-center whitespace-nowrap bg-[linear-gradient(135deg,hsl(var(--hero-ink)),hsl(var(--hero-right)/0.78))] bg-clip-text text-transparent"
: index === 1
? "self-end"
: index === titleLines.length - 1
? "self-start bg-[linear-gradient(135deg,hsl(var(--hero-ink)),hsl(var(--hero-right)/0.9))] bg-clip-text text-transparent"
: "self-start bg-[linear-gradient(135deg,hsl(var(--hero-ink)),hsl(var(--hero-left)/0.9))] bg-clip-text text-transparent"
}
>
{line}
</span>
))}
</h1>
<p className="mx-auto mt-8 max-w-[30rem] whitespace-pre-line text-sm leading-7 text-[hsl(var(--hero-ink)/0.72)] drop-shadow-[0_8px_18px_rgba(15,23,42,0.12)] sm:text-base">
{description}
</p>
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<Button asChild size="lg" className="min-w-[10.5rem] px-6">
<Link href={getLocalizedPath(locale, "/portfolio")}>
{portfolioLabel}
<ArrowRight className="h-4 w-4" />
</Link>
</Button>
<Button asChild variant="outline" size="lg" className="min-w-[10.5rem] border-white/40 bg-white/35 px-6 backdrop-blur-sm hover:bg-white/55 dark:border-white/14 dark:bg-white/6 dark:hover:bg-white/10">
<Link href={getLocalizedPath(locale, "/contact")}>{contactLabel}</Link>
</Button>
</div>
</MotionFade>
</div>
</div>
</section>
);
+35 -13
View File
@@ -1,6 +1,6 @@
"use client";
import { Languages } from "lucide-react";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { Button } from "@/components/ui/button";
@@ -24,9 +24,16 @@ export function LocaleToggle({ locale, className, showLabel = false }: LocaleTog
const locales: AppLocale[] = ["de", "en", "ar"];
const currentPath = stripLocalePrefix(pathname);
const currentLocale = (["de", "en", "ar"].includes(locale) ? locale : "de") as AppLocale;
const availableLocales = locales.filter((targetLocale) => targetLocale !== currentLocale);
const contentFontClassName = currentLocale === "ar" ? "font-arabic" : "font-latin";
const localeFlags: Record<AppLocale, { src: string; alt: string }> = {
de: { src: "/flags/de.svg", alt: "German" },
en: { src: "/flags/us.svg", alt: "English" },
ar: { src: "/flags/sy.svg", alt: "Arabic" },
};
return (
<DropdownMenu>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button
type="button"
@@ -34,26 +41,41 @@ export function LocaleToggle({ locale, className, showLabel = false }: LocaleTog
size={showLabel ? "sm" : "icon"}
aria-label={`Locale: ${currentLocale.toUpperCase()}`}
className={cn(
"rounded-pill border border-transparent text-muted-foreground hover:border-border/70 hover:bg-background/80 hover:text-foreground",
"group cursor-pointer rounded-pill border border-transparent text-muted-foreground hover:border-border/70 hover:bg-background/80 hover:text-foreground",
showLabel ? "gap-2 px-3" : undefined,
className,
)}
>
<Languages className="h-4 w-4" />
{showLabel ? <span className="text-xs font-semibold uppercase tracking-[0.22em]">{currentLocale}</span> : null}
<Image
src={localeFlags[currentLocale].src}
alt={localeFlags[currentLocale].alt}
width={18}
height={18}
className="h-[18px] w-[18px] rounded-full object-cover transition-transform duration-300 ease-out group-hover:scale-110"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[8rem]">
{locales.map((targetLocale) => (
<DropdownMenuItem key={targetLocale} asChild>
<DropdownMenuContent align="end" className={cn("w-auto min-w-0", contentFontClassName)}>
{availableLocales.map((targetLocale) => (
<DropdownMenuItem
key={targetLocale}
asChild
className={cn(
"cursor-pointer",
targetLocale === "ar" ? "font-arabic" : "font-latin",
)}
>
<a
href={getLocalizedPath(targetLocale, currentPath)}
className="flex items-center justify-between gap-4"
className="group flex items-center justify-center"
>
<span>{targetLocale.toUpperCase()}</span>
{targetLocale === currentLocale ? (
<span className="text-xs text-muted-foreground">Active</span>
) : null}
<Image
src={localeFlags[targetLocale].src}
alt={localeFlags[targetLocale].alt}
width={18}
height={18}
className="h-[18px] w-[18px] rounded-full object-cover transition-transform duration-300 ease-out group-hover:scale-110"
/>
</a>
</DropdownMenuItem>
))}
+8 -9
View File
@@ -1,5 +1,4 @@
import { HeroAtmosphere } from "@/components/layout/hero-atmosphere";
import { MotionFade } from "@/components/motion-fade";
import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop";
type PageHeroProps = {
title: string;
@@ -8,22 +7,22 @@ type PageHeroProps = {
export function PageHero({ title, description }: PageHeroProps) {
return (
<section className="relative isolate flex min-h-[42vh] items-end overflow-hidden">
<HeroAtmosphere compact />
<section className="hero-surface hero-page relative isolate flex min-h-[42vh] items-end overflow-hidden">
<HeroMotionBackdrop compact />
<div className="relative z-10 mx-auto w-full max-w-[72rem] px-4 pb-10 pt-24 sm:px-6 lg:px-8 lg:pb-12 lg:pt-28">
<MotionFade className="max-w-[44rem]">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-foreground/48">
<div className="mx-auto max-w-[42rem] text-center">
<p className="text-sm font-medium tracking-[-0.02em] text-foreground/58">
{title}
</p>
<h1 className="mt-4 text-balance text-4xl font-semibold leading-[0.95] tracking-[-0.05em] text-foreground sm:text-5xl lg:text-6xl">
<h1 className="mt-4 text-balance text-4xl font-semibold leading-[0.9] tracking-[-0.07em] text-foreground sm:text-5xl lg:text-[4.5rem]">
{title}
</h1>
{description ? (
<p className="mt-5 max-w-[36rem] text-sm leading-6 text-foreground/66 sm:text-base">
<p className="mx-auto mt-5 max-w-[32rem] text-sm leading-6 text-foreground/64 sm:text-base">
{description}
</p>
) : null}
</MotionFade>
</div>
</div>
</section>
);
+25 -18
View File
@@ -61,9 +61,11 @@ function NavLinks({
key={item.key}
href={getLocalizedPath(locale, itemPath)}
className={cn(
"relative z-10 rounded-[var(--radius-pill)] text-sm font-medium transition-colors",
mobile ? "px-4 py-3" : "px-3.5 py-2 xl:px-4",
isActive ? "text-primary-foreground" : "text-muted-foreground hover:text-foreground",
"relative z-10 rounded-[var(--radius-pill)] transition-colors",
mobile ? "px-4 py-3 text-sm font-medium" : "px-3.5 py-2 text-sm font-medium xl:px-4",
isActive
? "text-primary-foreground"
: "text-[hsl(var(--hero-ink)/0.62)] hover:text-[hsl(var(--hero-ink))] dark:text-muted-foreground dark:hover:text-foreground",
)}
onClick={onNavigate}
>
@@ -92,21 +94,23 @@ export function SiteHeader({
const pathname = usePathname();
const locale = useLocale();
const t = useTranslations("navigation");
const isArabic = locale === "ar";
return (
<header className="fixed inset-x-0 top-3 z-40 bg-transparent">
<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="flex items-center">
<div className="col-start-1 flex items-center justify-self-start">
<Link
href={getLocalizedPath(locale)}
className="inline-flex items-center py-1 sm:py-1.5 lg:py-2"
className="inline-flex items-center rounded-[var(--radius-pill)] px-1 py-1 sm:py-1.5 lg:py-2"
>
<SiteLogo
lightLogoUrl={lightLogoUrl}
darkLogoUrl={darkLogoUrl}
alt="mohfarawati"
priority
className="h-7 opacity-88 sm:h-8 lg:h-9"
/>
</Link>
</div>
@@ -116,7 +120,10 @@ export function SiteHeader({
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.28, ease: "easeOut" }}
className="pointer-events-auto inline-flex min-w-[27rem] max-w-[42rem] items-center justify-center gap-1 rounded-[var(--radius-pill)] border border-white/18 bg-white/10 p-1.5 shadow-[0_18px_44px_-28px_rgba(15,23,42,0.24)] backdrop-blur-[24px] dark:border-white/10 dark:bg-white/5 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"
className={cn(
"pointer-events-auto inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1.5 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]",
isArabic ? "w-auto max-w-none" : "min-w-[27rem] max-w-[42rem]",
)}
>
<NavLinks />
</motion.nav>
@@ -127,15 +134,15 @@ export function SiteHeader({
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.28, ease: "easeOut", delay: 0.03 }}
className="inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-white/18 bg-white/10 p-1 shadow-[0_18px_44px_-28px_rgba(15,23,42,0.24)] backdrop-blur-[24px] dark:border-white/10 dark:bg-white/5 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"
className="inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"
>
<LocaleToggle
locale={locale}
showLabel
className="h-9 rounded-[var(--radius-pill)] px-3 text-foreground hover:bg-white/34 dark:hover:bg-white/10"
className="h-9 rounded-[var(--radius-pill)] px-3 text-[hsl(var(--hero-ink)/0.76)] hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/10"
/>
{isAdmin ? (
<Button asChild variant="ghost" size="icon" className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-foreground hover:border-white/12 hover:bg-white/34 dark:hover:border-white/10 dark:hover:bg-white/10">
<Button asChild variant="ghost" size="icon" className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10">
<Link href="/root" aria-label={t("root")}>
<LayoutDashboard className="h-4 w-4" />
</Link>
@@ -144,7 +151,7 @@ export function SiteHeader({
<ThemeToggle
ariaLabel={t("themeToggle")}
variant="ghost"
className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-foreground hover:border-white/12 hover:bg-white/34 dark:hover:border-white/10 dark:hover:bg-white/10"
className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10"
/>
</motion.div>
</div>
@@ -154,7 +161,7 @@ export function SiteHeader({
onClick={() => setIsOpen((open) => !open)}
variant="ghost"
size="icon"
className="ml-auto h-11 w-11 rounded-[var(--radius-pill)] border border-white/18 bg-white/12 text-foreground shadow-[0_18px_44px_-28px_rgba(15,23,42,0.24)] backdrop-blur-[24px] hover:bg-white/18 dark:border-white/10 dark:bg-white/5 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)] dark:hover:bg-white/10 lg:hidden"
className="col-start-3 ml-auto h-11 w-11 justify-self-end rounded-[var(--radius-pill)] border border-black/6 bg-white/24 text-[hsl(var(--hero-ink))] shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] hover:bg-white/34 dark:border-white/10 dark:bg-white/4 dark:text-foreground dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)] dark:hover:bg-white/10 lg:hidden"
aria-label={isOpen ? t("closeMenu") : t("openMenu")}
>
<motion.span
@@ -183,7 +190,7 @@ export function SiteHeader({
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className="overflow-hidden rounded-surface border border-white/24 bg-[linear-gradient(180deg,rgba(255,255,255,0.24),rgba(255,255,255,0.1))] shadow-[inset_0_1px_0_rgba(255,255,255,0.46),inset_0_-1px_0_rgba(255,255,255,0.08),0_18px_44px_-28px_rgba(15,23,42,0.32)] backdrop-blur-[28px] dark:border-white/12 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.09),rgba(255,255,255,0.03))] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.12),inset_0_-1px_0_rgba(255,255,255,0.03),0_20px_48px_-28px_rgba(0,0,0,0.72)]"
className="overflow-hidden rounded-surface border border-black/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.46),rgba(255,255,255,0.24))] shadow-[inset_0_1px_0_rgba(255,255,255,0.54),0_16px_34px_-24px_rgba(15,23,42,0.24)] backdrop-blur-[20px] dark:border-white/12 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.09),rgba(255,255,255,0.03))] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.12),inset_0_-1px_0_rgba(255,255,255,0.03),0_20px_48px_-28px_rgba(0,0,0,0.72)]"
>
<motion.nav
initial="closed"
@@ -223,8 +230,8 @@ export function SiteHeader({
className={cn(
"flex items-center justify-between rounded-nested px-4 py-3 text-sm font-medium transition-colors",
isActive
? "bg-primary text-primary-foreground"
: "text-foreground hover:bg-accent hover:text-accent-foreground",
? "bg-white/72 text-[hsl(var(--hero-ink))] shadow-[0_10px_20px_-18px_rgba(15,23,42,0.24)] dark:bg-white/10 dark:text-foreground"
: "text-[hsl(var(--hero-ink)/0.78)] hover:bg-white/40 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/6",
)}
onClick={() => setIsOpen(false)}
>
@@ -243,17 +250,17 @@ export function SiteHeader({
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.18, ease: "easeOut", delay: 0.04 }}
className="border-t border-white/18 bg-[linear-gradient(180deg,rgba(255,255,255,0.14),rgba(255,255,255,0.06))] p-2 dark:border-white/8 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]"
className="border-t border-black/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.2),rgba(255,255,255,0.08))] p-2 dark:border-white/8 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]"
>
<div className="flex items-center justify-between gap-2">
<LocaleToggle
locale={locale}
showLabel
className="h-10 rounded-nested px-3 text-foreground hover:bg-white/34 dark:hover:bg-white/10"
className="h-10 rounded-nested px-3 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/10"
/>
<div className="flex items-center gap-2">
{isAdmin ? (
<Button asChild variant="ghost" size="icon" className="h-10 w-10 rounded-nested border border-white/18 bg-white/18 text-foreground hover:bg-white/34 dark:border-white/10 dark:bg-white/6 dark:hover:bg-white/10">
<Button asChild variant="ghost" size="icon" className="h-10 w-10 rounded-nested border border-black/6 bg-white/32 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/42 hover:text-[hsl(var(--hero-ink))] dark:border-white/10 dark:bg-white/6 dark:text-foreground dark:hover:bg-white/10">
<Link href="/root" aria-label={t("root")} onClick={() => setIsOpen(false)}>
<LayoutDashboard className="h-4 w-4" />
</Link>
@@ -262,7 +269,7 @@ export function SiteHeader({
<ThemeToggle
ariaLabel={t("themeToggle")}
variant="ghost"
className="h-10 w-10 rounded-nested border border-white/18 bg-white/18 text-foreground hover:bg-white/34 dark:border-white/10 dark:bg-white/6 dark:hover:bg-white/10"
className="h-10 w-10 rounded-nested border border-black/6 bg-white/32 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/42 hover:text-[hsl(var(--hero-ink))] dark:border-white/10 dark:bg-white/6 dark:text-foreground dark:hover:bg-white/10"
/>
</div>
</div>
+15 -3
View File
@@ -1,14 +1,18 @@
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
type SiteLogoProps = {
lightLogoUrl?: string | null;
darkLogoUrl?: string | null;
alt: string;
priority?: boolean;
className?: string;
};
export function SiteLogo({
@@ -16,6 +20,7 @@ export function SiteLogo({
darkLogoUrl,
alt,
priority = false,
className,
}: SiteLogoProps) {
const { theme, resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
@@ -30,16 +35,23 @@ export function SiteLogo({
const src = isDark ? darkLogoUrl || lightLogoUrl || fallbackSrc : lightLogoUrl || darkLogoUrl || fallbackSrc;
return (
<span className="block">
<motion.span
className="block"
whileHover={{ scale: 1.04, y: -1 }}
transition={{ duration: 0.28, ease: "easeOut" }}
>
<Image
src={src}
alt={alt}
width={360}
height={92}
sizes="(min-width: 1280px) 360px, (min-width: 1024px) 320px, (min-width: 640px) 280px, 240px"
className="h-9 w-auto object-contain object-left sm:h-10 lg:h-11 xl:h-12"
className={cn(
"h-9 w-auto object-contain object-left drop-shadow-[0_10px_24px_rgba(15,23,42,0.12)] transition-[filter,opacity,transform] duration-300 ease-out sm:h-10 lg:h-11 xl:h-12",
className,
)}
priority={priority}
/>
</span>
</motion.span>
);
}