두 버튼 모두 무대에서 stageGhost 를 쓰므로 변형 하나로 해결된다. 구성은 넷 — 옅은 흰 채움, 뒤를 흐리는 backdrop-blur(12px), 위쪽 1px 안쪽 하이라이트 (유리 모서리에 빛이 걸리는 부분), 아래로 깔리는 부드러운 그림자. 채도를 살짝 올리면 뒤가 흐려지며 빠지는 색기가 돌아온다(saturate 1.5). 테두리는 white/40 에서 더 내리지 않았다. /30 이면 무대 광원부(#16205A) 대비가 2.60:1 로 떨어져 WCAG 1.4.11(UI 경계 3:1)에 미달한다 — 유리 느낌 때문에 버튼이 안 보이던 원래 문제(대비 1.09:1)로 되돌아간다. 투명감은 채움과 blur 로 내고 테두리는 지킨다. hover 는 흰색 반전이 아니라 채움을 밝힌다. 유리는 뒤가 비치는 게 정체성이라 불투명하게 뒤집으면 그 순간 유리가 아니게 된다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 lines
4.3 KiB
TypeScript
71 lines
4.3 KiB
TypeScript
import * as React from "react"
|
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
// CTA 버튼 토큰. 랜딩의 CTA 는 전부 앵커 스크롤이라 href 를 주면 <a> 로 렌더한다
|
|
// (SSG 특성상 하이드레이션 전에도 동작).
|
|
const buttonVariants = cva(
|
|
/* transition-colors 로 좁힌다. transition-all 이면 헤더 CTA 가 무대↔라이트로 variant 를
|
|
통째로 바꿀 때 배경이 투명에서 차오르며 플리커로 읽힌다. */
|
|
"inline-flex items-center justify-center gap-2 transition-colors duration-200 cursor-pointer disabled:opacity-50",
|
|
{
|
|
variants: {
|
|
variant: {
|
|
primary: "bg-primary hover:bg-primary-deep text-white",
|
|
secondary: "bg-fill hover:bg-fill-hover text-ink-soft hover:text-ink",
|
|
// 밝은 배경 위 반투명 보조 버튼 (글라스 히어로)
|
|
glass: "bg-white/70 hover:bg-white text-ink-soft hover:text-ink border border-white shadow-sm",
|
|
// 다크 무대 위 1차 CTA
|
|
stage: "bg-primary hover:bg-primary-deep text-white",
|
|
/* 다크 무대 위 2차 CTA — 흰 알약.
|
|
테두리를 stage-line(#1E2A52)으로 두면 안 된다. 그 색은 무대 바닥(#070E24) 위
|
|
카드용이라, 방사형 광원부(#16205A)에 앉는 GNB 자리에서는 배경과 대비가 1.09:1 로
|
|
사라진다. 채움도 없으면 남는 건 흰 글자뿐이라 옆 nav 링크와 구분되지 않는다.
|
|
그래서 무대색 토큰이 아니라 흰색 알파로 잡는다 — 무대 어디에 놓여도 같이 성립한다.
|
|
옅은 채움이 테두리보다 "누를 수 있는 것"을 더 강하게 전달하므로 둘 다 준다. */
|
|
/* 유리(glassmorphism). 배경 영상이 비쳐 보이되 글자는 읽히는 상태를 만든다.
|
|
구성은 넷이다 — 옅은 흰 채움, 뒤를 흐리는 backdrop-blur, 위쪽 1px 안쪽 하이라이트
|
|
(유리 모서리에 빛이 걸리는 부분), 아래로 깔리는 부드러운 그림자(떠 있는 느낌).
|
|
채도를 살짝 올리면(saturate) 뒤가 흐려지며 빠지는 색기가 돌아온다.
|
|
|
|
테두리는 white/40 에서 더 못 내린다. /30 으로 낮추면 무대 광원부(#16205A) 대비가
|
|
2.60:1 로 떨어져 WCAG 1.4.11(UI 경계 3:1)에 미달한다 — 유리 느낌 때문에 버튼이
|
|
안 보이던 원래 문제로 돌아간다. 투명감은 채움과 blur 로 내고 테두리는 지킨다.
|
|
|
|
hover 는 흰색 반전이 아니라 채움을 밝힌다. 유리는 뒤가 비치는 게 정체성이라
|
|
불투명하게 뒤집으면 그 순간 유리가 아니게 된다. */
|
|
stageGhost:
|
|
"bg-white/12 hover:bg-white/25 text-white border border-white/40 hover:border-white/60 " +
|
|
"backdrop-blur-md backdrop-saturate-150 " +
|
|
"shadow-[inset_0_1px_0_0_rgba(255,255,255,0.30),0_8px_24px_-10px_rgba(3,6,15,0.7)]",
|
|
},
|
|
/* 레퍼런스(statworx) 실측: 10.4px / 600 / 알약(radius 39px) / 패딩 12·24 / 높이 33px.
|
|
작고 조밀한 버튼이 확신 있어 보인다 — 크고 굵은 버튼은 설득이 아니라 호소로 읽힌다.
|
|
한글 보정: 10.4px 는 판독이 어려워 12~13px 로 올리고 높이도 터치 타깃까지 확보한다.
|
|
(기존 주석의 "알약형 금지" 규칙은 레퍼런스 채택으로 폐기했다.) */
|
|
size: {
|
|
pill: "px-5 py-2.5 text-[12px] font-semibold rounded-full",
|
|
md: "px-6 py-3 text-[12px] font-semibold rounded-full",
|
|
lg: "px-7 py-3.5 text-[13px] font-semibold rounded-full",
|
|
xl: "px-8 py-4 text-[14px] font-semibold rounded-full",
|
|
stageRound: "px-7 py-3.5 text-[13px] font-semibold rounded-full",
|
|
},
|
|
},
|
|
defaultVariants: { variant: "primary", size: "lg" },
|
|
},
|
|
)
|
|
|
|
type ButtonProps = VariantProps<typeof buttonVariants> &
|
|
React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
href?: string
|
|
}
|
|
|
|
function Button({ className, variant, size, href, ...props }: ButtonProps) {
|
|
const Comp: React.ElementType = href ? "a" : "button"
|
|
return <Comp href={href} className={cn(buttonVariants({ variant, size }), className)} {...props} />
|
|
}
|
|
|
|
export { Button, buttonVariants }
|