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. */ )} ); const link = isInternal(href) && !external ? ( {inner} ) : ( {inner} ); if (!micro) return link; return ( {link} {micro} ); } export default Button;