131 lines
4.4 KiB
TypeScript
131 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
|
|
import { SiteLogo } from "@/components/layout/site-logo";
|
|
|
|
type AsciiWaterRippleProps = {
|
|
lightLogoUrl?: string | null;
|
|
darkLogoUrl?: string | null;
|
|
logoAlt: string;
|
|
};
|
|
|
|
const glyphs = [".", ":", "+", "*", "o"];
|
|
const rings = [72, 108, 148, 196, 248, 308, 372, 438, 506];
|
|
const pointsPerRing = [16, 22, 30, 38, 48, 58, 68, 80, 92];
|
|
|
|
function buildRipplePoints() {
|
|
return rings.flatMap((radius, ringIndex) => {
|
|
const points = pointsPerRing[ringIndex];
|
|
|
|
return Array.from({ length: points }, (_, pointIndex) => {
|
|
const angle = (Math.PI * 2 * pointIndex) / points;
|
|
|
|
return {
|
|
id: `${radius}-${pointIndex}`,
|
|
x: Math.cos(angle) * radius,
|
|
y: Math.sin(angle) * radius,
|
|
ringIndex,
|
|
pointIndex,
|
|
glyph: glyphs[(ringIndex + pointIndex) % glyphs.length],
|
|
};
|
|
});
|
|
});
|
|
}
|
|
|
|
const ripplePoints = buildRipplePoints();
|
|
|
|
export function AsciiWaterRipple({
|
|
lightLogoUrl,
|
|
darkLogoUrl,
|
|
logoAlt,
|
|
}: AsciiWaterRippleProps) {
|
|
return (
|
|
<div className="relative flex h-[14rem] w-full items-center justify-center overflow-visible sm:h-[17rem] lg:h-[20rem]">
|
|
<motion.div
|
|
animate={{
|
|
scale: [0.96, 1.08, 0.98],
|
|
opacity: [0.42, 0.68, 0.48],
|
|
}}
|
|
transition={{ duration: 5.2, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }}
|
|
className="absolute left-1/2 top-1/2 h-[16rem] w-[16rem] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/16 blur-3xl sm:h-[22rem] sm:w-[22rem] lg:h-[28rem] lg:w-[28rem]"
|
|
/>
|
|
|
|
<motion.div
|
|
animate={{
|
|
scale: [1, 1.24, 1.06],
|
|
opacity: [0.08, 0.18, 0.1],
|
|
}}
|
|
transition={{ duration: 7.5, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }}
|
|
className="absolute left-1/2 top-1/2 h-[34rem] w-[34rem] -translate-x-1/2 -translate-y-1/2 rounded-full border border-primary/12"
|
|
/>
|
|
|
|
<motion.div
|
|
animate={{
|
|
scale: [0.94, 1.3, 1.02],
|
|
opacity: [0.06, 0.14, 0.08],
|
|
}}
|
|
transition={{ duration: 9.2, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut", delay: 0.4 }}
|
|
className="absolute left-1/2 top-1/2 h-[44rem] w-[44rem] -translate-x-1/2 -translate-y-1/2 rounded-full border border-primary/10"
|
|
/>
|
|
|
|
<motion.div
|
|
animate={{
|
|
scale: [1, 1.035, 0.992, 1],
|
|
x: [0, 10, -8, 0],
|
|
y: [0, -6, 4, 0],
|
|
}}
|
|
transition={{ duration: 8, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }}
|
|
className="absolute inset-0 flex items-center justify-center overflow-visible"
|
|
>
|
|
{ripplePoints.map((point) => (
|
|
<motion.span
|
|
key={point.id}
|
|
initial={{ opacity: 0.14, scale: 0.9 }}
|
|
animate={{
|
|
opacity: [0.1, 0.62, 0.16],
|
|
scale: [0.88, 1.2, 0.94],
|
|
x: [0, point.ringIndex % 2 === 0 ? 5 : -5, 0],
|
|
y: [0, point.ringIndex % 3 === 0 ? -4 : 4, 0],
|
|
}}
|
|
transition={{
|
|
duration: 2.6 + point.ringIndex * 0.42,
|
|
delay: point.pointIndex * 0.012,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: "easeInOut",
|
|
}}
|
|
className="absolute text-[10px] font-medium leading-none text-primary/75 sm:text-xs"
|
|
style={{
|
|
transform: `translate(${point.x}px, ${point.y}px)`,
|
|
}}
|
|
>
|
|
{point.glyph}
|
|
</motion.span>
|
|
))}
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
animate={{
|
|
boxShadow: [
|
|
"0 0 0 1px hsl(var(--primary) / 0.24), 0 0 42px hsl(var(--primary) / 0.22)",
|
|
"0 0 0 1px hsl(var(--primary) / 0.34), 0 0 84px hsl(var(--primary) / 0.34)",
|
|
"0 0 0 1px hsl(var(--primary) / 0.24), 0 0 42px hsl(var(--primary) / 0.22)",
|
|
],
|
|
scale: [1, 1.02, 1],
|
|
y: [0, -3, 0],
|
|
}}
|
|
transition={{ duration: 4.8, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }}
|
|
className="relative z-10 flex h-28 w-28 items-center justify-center rounded-full border border-primary/30 bg-background/92 p-2 backdrop-blur-xl sm:h-32 sm:w-32 lg:h-40 lg:w-40"
|
|
>
|
|
<SiteLogo
|
|
lightLogoUrl={lightLogoUrl}
|
|
darkLogoUrl={darkLogoUrl}
|
|
alt={logoAlt}
|
|
priority
|
|
className="h-full w-full object-center"
|
|
/>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
}
|