import { cn } from "@/lib/utils";
import { TransitionLink } from "@/components/motion/TransitionLink";
export interface ButtonProps {
href: string;
label: string;
/** Mono suffix that rises out of a mask inside the button on hover (≥ 1024); its width is reserved. */
suffix?: string;
/** Mono micro line under the button, e.g. „Telefonisch oder online · Deutsch oder Englisch“. */
micro?: string;
/** `primary` = 1 px accent outline + gold sheen fill on hover; `ghost` = neutral hairline, no fill. */
variant?: "primary" | "ghost";
/** `target="_blank" rel="noopener noreferrer"`. */
external?: boolean;
className?: string;
}
export const BUTTON_BASE =
"group/btn relative inline-flex items-center overflow-hidden whitespace-nowrap border px-[2.1em] py-[1.05em] font-sans text-[0.8125rem] font-medium uppercase leading-none tracking-[0.2em] text-fg transition-[color,background-color,border-color] duration-300";
/* Sheen fill: --gold-sheen at 250 % width, background-position 0 % → 100 % in .6 s. Text turns ink. */
export const BUTTON_PRIMARY =
"border-accent hover:text-ink focus-visible:text-ink before:pointer-events-none before:absolute before:inset-0 before:bg-[image:var(--gold-sheen)] before:bg-[length:250%_100%] before:bg-[position:0%_50%] before:opacity-0 before:transition-[opacity,background-position] before:duration-[600ms] before:ease-[var(--ease-out-quart)] hover:before:bg-[position:100%_50%] hover:before:opacity-100 focus-visible:before:bg-[position:100%_50%] focus-visible:before:opacity-100";
const GHOST = "border-line hover:border-accent focus-visible:border-accent";
function isInternal(href: string) {
return href.startsWith("/");
}
/**
* Outline button (radius 0, no shadow). Internal routes use the curtain
* (`TransitionLink`); mailto/tel/http render a plain ``.
*/
export function Button({ href, label, suffix, micro, variant = "primary", external = false, className }: ButtonProps) {
const cls = cn(BUTTON_BASE, variant === "primary" ? BUTTON_PRIMARY : GHOST, className);
const inner = (
<>
{label}
{suffix && (
/* The mask sits INSIDE the button and its width is reserved, so the
control never resizes under the pointer – only the line rises. */
{suffix}
)}
>
);
const link =
isInternal(href) && !external ? (
{inner}
) : (
{inner}
);
if (!micro) return link;
return (
{link}
{micro}
);
}
export default Button;