Files
sass-mohfarawati/components/layout/ascii-water-ripple.tsx
T

137 lines
4.5 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;
showLogo?: boolean;
className?: 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 = "",
showLogo = true,
className,
}: AsciiWaterRippleProps) {
return (
<div className={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>
{showLogo && (
<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>
);
}