94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useAnimationControls, useReducedMotion } 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;
|
|
replayKey?: number;
|
|
};
|
|
|
|
export function SiteLogo({
|
|
lightLogoUrl,
|
|
darkLogoUrl,
|
|
alt,
|
|
priority = false,
|
|
className,
|
|
replayKey = 0,
|
|
}: SiteLogoProps) {
|
|
const { theme, resolvedTheme } = useTheme();
|
|
const [mounted, setMounted] = useState(false);
|
|
const controls = useAnimationControls();
|
|
const reducedMotion = useReducedMotion();
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (reducedMotion) {
|
|
return;
|
|
}
|
|
|
|
controls.set({
|
|
opacity: 0,
|
|
x: -42,
|
|
scale: 0.88,
|
|
rotate: -360,
|
|
filter: "blur(8px)",
|
|
});
|
|
|
|
void controls.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]);
|
|
|
|
const activeTheme = theme === "system" ? resolvedTheme : theme;
|
|
const isDark = mounted && activeTheme === "dark";
|
|
const fallbackSrc = isDark ? "/logos/dark-primary.svg" : "/logos/light-primary.svg";
|
|
const src = isDark ? darkLogoUrl || lightLogoUrl || fallbackSrc : lightLogoUrl || darkLogoUrl || fallbackSrc;
|
|
|
|
return (
|
|
<motion.span
|
|
className="block [transform-style:preserve-3d]"
|
|
initial={reducedMotion ? false : undefined}
|
|
animate={reducedMotion ? undefined : controls}
|
|
whileHover={{ scale: 1.05, y: -2, rotate: -2, filter: "drop-shadow(0 16px 28px rgba(15,23,42,0.14))" }}
|
|
whileTap={{ scale: 0.9, rotate: 6, y: 1, filter: "drop-shadow(0 8px 18px rgba(15,23,42,0.18))" }}
|
|
>
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
width={360}
|
|
height={92}
|
|
sizes="(min-width: 1280px) 360px, (min-width: 1024px) 320px, (min-width: 640px) 280px, 240px"
|
|
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",
|
|
mounted ? "opacity-100" : "opacity-0",
|
|
className,
|
|
)}
|
|
priority={priority}
|
|
/>
|
|
</motion.span>
|
|
);
|
|
}
|