"use client";
import { useId, type CSSProperties } from "react";
import { cn } from "@/lib/utils";
export interface MetallicTextProps {
text?: string;
sweepSpeed?: number;
sweepWidth?: number;
sweepAngle?: number;
contrast?: number;
specular?: number;
lag?: number;
sweep?: "alternate" | "loop";
grain?: number;
baseColor?: string;
highlightColor?: string;
accentColor?: string;
paused?: boolean;
reducedMotion?: boolean;
className?: string;
}
function sheet(ns: string): string {
return `
.${ns} {
display: inline-block;
color: var(--mt-base);
}
.${ns} > .${ns}-core {
display: block;
color: inherit;
}
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
.${ns},
.${ns} > .${ns}-core {
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
animation-duration: var(--mt-dur);
animation-iteration-count: infinite;
animation-direction: var(--mt-dir);
animation-play-state: var(--mt-play);
}
.${ns} {
background-image:
linear-gradient(
var(--mt-angle),
transparent 0 var(--mt-w0),
color-mix(in srgb, var(--mt-accent) var(--mt-halo), transparent) 50%,
transparent var(--mt-w2) 100%
),
repeating-linear-gradient(
var(--mt-angle),
rgb(255 255 255 / var(--mt-grain)) 0 1px,
rgb(0 0 0 / var(--mt-grain)) 1px 2px,
transparent 2px 4px
),
linear-gradient(
180deg,
color-mix(in oklab, var(--mt-base) var(--mt-dark), black) 0%,
color-mix(in oklab, var(--mt-base) var(--mt-lite), var(--mt-hi)) 26%,
var(--mt-base) 44%,
color-mix(in oklab, var(--mt-base) var(--mt-dark), black) 56%,
color-mix(in oklab, var(--mt-base) var(--mt-lite), var(--mt-hi)) 80%,
color-mix(in oklab, var(--mt-base) 88%, black) 100%
);
background-size: 300% 300%, auto, 100% 100%;
background-position: var(--mt-park) 50%, 0 0, 0 0;
background-repeat: no-repeat, repeat, no-repeat;
background-blend-mode: screen, overlay, normal;
animation-name: ${ns}-wake;
animation-timing-function: var(--mt-ease-wake);
animation-delay: var(--mt-delay);
animation-fill-mode: backwards;
}
.${ns} > .${ns}-core {
background-image: linear-gradient(
var(--mt-angle),
transparent 0 var(--mt-s0),
color-mix(in srgb, var(--mt-hi) var(--mt-spec), transparent) 50%,
transparent var(--mt-s2) 100%
);
background-size: 300% 300%;
background-position: var(--mt-park) 50%;
background-repeat: no-repeat;
animation-name: ${ns}-core;
animation-timing-function: var(--mt-ease-core);
}
}
.${ns}.${ns}-still,
.${ns}.${ns}-still > .${ns}-core {
animation-name: none;
}
@keyframes ${ns}-core {
from { background-position: 100% 50%; }
to { background-position: 0% 50%; }
}
@keyframes ${ns}-wake {
from { background-position: 100% 50%, 0 0, 0 0; }
to { background-position: 0% 50%, 0 0, 0 0; }
}
`;
}
const PARK = "62%";
export default function MetallicText({
text = "METALLIC",
sweepSpeed = 3.2,
sweepWidth = 0.28,
sweepAngle = 18,
contrast = 0.7,
specular = 0.85,
lag = 0.35,
sweep = "alternate",
grain = 0.12,
baseColor = "#8f94a3",
highlightColor = "#ffffff",
accentColor = "#a855f7",
paused = false,
reducedMotion = false,
className,
}: MetallicTextProps) {
const ns = `sg${useId().replace(/[^a-zA-Z0-9]/g, "")}`;
const half = (Math.max(0.02, sweepWidth) * 100) / 6;
const wakeHalf = Math.min(26, half * 2.6);
return (
<>
<style>{sheet(ns)}</style>
<span
className={cn(ns, reducedMotion && `${ns}-still`, className)}
style={
{
"--mt-base": baseColor,
"--mt-hi": highlightColor,
"--mt-accent": accentColor,
"--mt-angle": `${90 + sweepAngle}deg`,
"--mt-dur": `${sweepSpeed}s`,
"--mt-delay": `${(lag * 0.3 * sweepSpeed).toFixed(3)}s`,
"--mt-dir": sweep === "alternate" ? "alternate" : "normal",
"--mt-play": paused ? "paused" : "running",
"--mt-park": PARK,
"--mt-grain": `${grain}`,
"--mt-dark": `${Math.max(20, 100 - contrast * 45).toFixed(1)}%`,
"--mt-lite": `${Math.max(15, 100 - contrast * 55).toFixed(1)}%`,
"--mt-spec": `${Math.round(Math.min(1, specular) * 100)}%`,
"--mt-halo": `${Math.round(Math.min(0.6, specular * 0.4) * 100)}%`,
"--mt-s0": `${(50 - half).toFixed(2)}%`,
"--mt-s2": `${(50 + half).toFixed(2)}%`,
"--mt-w0": `${(50 - wakeHalf).toFixed(2)}%`,
"--mt-w2": `${(50 + wakeHalf).toFixed(2)}%`,
"--mt-ease-core": "cubic-bezier(0.45, 0, 0.55, 1)",
"--mt-ease-wake": "cubic-bezier(0.34, 1.56, 0.64, 1)",
} as CSSProperties
}
>
<span className={`${ns}-core`}>{text}</span>
</span>
</>
);
}