feat(ui): 카드 그라디언트 헤어라인 + 앰비언트 글로우 (레퍼런스 #5)

- glass.borderGradient: 좌상단 발광→우하단 소멸 1px 링 (mask exclude 기법 —
  반투명 글래스 표면에서도 정확)
- glow.card/cardHover: 블루 앰비언트 상단 글로우 + 깊이 그림자 결합
- sheen 재정의: 좌상단 라디얼 앰비언트 + 수직 시인 레이어
- 6개 테마 전부 액센트색 파생, MetalCard/InstrumentPanel/MuiCard 적용
This commit is contained in:
Yun Chan 2026-07-21 20:36:25 +09:00
parent 082ed2ef02
commit e0a1864e60
4 changed files with 102 additions and 23 deletions

View file

@ -74,8 +74,10 @@ interface RawTheme {
/** 헤어라인 보더 */
hairline: string
hairlineStrong: string
/** 카드 상단 시인(광택) 오버레이 */
/** 카드 상단 시인(광택) 오버레이 — 앰비언트 라디얼 + 수직 시인 레이어 */
sheen: string
/** 그라디언트 헤어라인 보더 — 좌상단이 밝고 우하단으로 소멸 (mask 링에 사용) */
borderGradient: string
/** backdrop-filter blur 반경 */
blur: string
}
@ -100,6 +102,10 @@ interface RawTheme {
/** 프라이머리 버튼/액티브 글로우 */
accent: string
soft: string
/** 카드 외곽 앰비언트 글로우 (+깊이 그림자 결합) */
card: string
/** 카드 hover 시 강화 글로우 */
cardHover: string
}
}
@ -128,7 +134,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
raised: 'rgba(26,38,68,0.55)',
hairline: 'rgba(148,180,255,0.10)',
hairlineStrong: 'rgba(148,180,255,0.18)',
sheen: 'linear-gradient(180deg, rgba(148,180,255,0.06) 0%, rgba(148,180,255,0) 42%)',
sheen: 'radial-gradient(ellipse 70% 55% at 18% -8%, rgba(59,130,246,0.14) 0%, transparent 55%), linear-gradient(180deg, rgba(148,180,255,0.05) 0%, rgba(148,180,255,0) 40%)',
borderGradient: 'linear-gradient(135deg, rgba(125,180,255,0.55) 0%, rgba(148,180,255,0.14) 32%, rgba(148,180,255,0.06) 58%, rgba(59,130,246,0.22) 100%)',
blur: '24px',
},
gradient: {
@ -141,6 +148,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
glow: {
accent: '0 0 0 1px rgba(59,130,246,0.35), 0 8px 24px rgba(37,99,235,0.35)',
soft: '0 0 16px rgba(59,130,246,0.25)',
card: '0 -8px 32px -12px rgba(59,130,246,0.22), 0 12px 40px rgba(3,7,18,0.45)',
cardHover: '0 -10px 40px -10px rgba(59,130,246,0.34), 0 12px 40px rgba(3,7,18,0.5)',
},
},
// ── Light — 블루 액센트 유지, 밝은 글래스 ────────────────────
@ -165,7 +174,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
raised: 'rgba(255,255,255,0.85)',
hairline: 'rgba(30,58,138,0.10)',
hairlineStrong: 'rgba(30,58,138,0.18)',
sheen: 'linear-gradient(180deg, rgba(255,255,255,0.6) 0%, rgba(255,255,255,0) 42%)',
sheen: 'radial-gradient(ellipse 70% 55% at 18% -8%, rgba(37,99,235,0.08) 0%, transparent 55%), linear-gradient(180deg, rgba(255,255,255,0.6) 0%, rgba(255,255,255,0) 42%)',
borderGradient: 'linear-gradient(135deg, rgba(37,99,235,0.45) 0%, rgba(30,58,138,0.14) 32%, rgba(30,58,138,0.08) 58%, rgba(37,99,235,0.18) 100%)',
blur: '24px',
},
gradient: {
@ -178,6 +188,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
glow: {
accent: '0 0 0 1px rgba(37,99,235,0.25), 0 8px 24px rgba(37,99,235,0.25)',
soft: '0 0 16px rgba(37,99,235,0.18)',
card: '0 -6px 28px -12px rgba(37,99,235,0.18), 0 8px 30px rgba(30,58,138,0.08)',
cardHover: '0 -8px 36px -10px rgba(37,99,235,0.28), 0 8px 30px rgba(30,58,138,0.10)',
},
},
// ── Nord (https://www.nordtheme.com/) ──────────────────────
@ -202,7 +214,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
raised: 'rgba(59,66,82,0.55)',
hairline: 'rgba(216,222,233,0.09)',
hairlineStrong: 'rgba(216,222,233,0.16)',
sheen: 'linear-gradient(180deg, rgba(216,222,233,0.05) 0%, rgba(216,222,233,0) 42%)',
sheen: 'radial-gradient(ellipse 70% 55% at 18% -8%, rgba(136,192,208,0.14) 0%, transparent 55%), linear-gradient(180deg, rgba(216,222,233,0.05) 0%, rgba(216,222,233,0) 40%)',
borderGradient: 'linear-gradient(135deg, rgba(160,205,220,0.55) 0%, rgba(216,222,233,0.14) 32%, rgba(216,222,233,0.06) 58%, rgba(136,192,208,0.22) 100%)',
blur: '24px',
},
gradient: {
@ -215,6 +228,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
glow: {
accent: '0 0 0 1px rgba(136,192,208,0.3), 0 8px 24px rgba(94,129,172,0.35)',
soft: '0 0 16px rgba(136,192,208,0.22)',
card: '0 -8px 32px -12px rgba(136,192,208,0.22), 0 12px 40px rgba(3,7,18,0.45)',
cardHover: '0 -10px 40px -10px rgba(136,192,208,0.34), 0 12px 40px rgba(3,7,18,0.5)',
},
},
// ── Solarized Dark (https://ethanschoonover.com/solarized/) ─
@ -239,7 +254,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
raised: 'rgba(13,66,80,0.55)',
hairline: 'rgba(131,148,150,0.11)',
hairlineStrong: 'rgba(131,148,150,0.19)',
sheen: 'linear-gradient(180deg, rgba(238,232,213,0.04) 0%, rgba(238,232,213,0) 42%)',
sheen: 'radial-gradient(ellipse 70% 55% at 18% -8%, rgba(181,137,0,0.14) 0%, transparent 55%), linear-gradient(180deg, rgba(131,148,150,0.05) 0%, rgba(131,148,150,0) 40%)',
borderGradient: 'linear-gradient(135deg, rgba(212,160,23,0.55) 0%, rgba(131,148,150,0.14) 32%, rgba(131,148,150,0.06) 58%, rgba(181,137,0,0.22) 100%)',
blur: '24px',
},
gradient: {
@ -252,6 +268,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
glow: {
accent: '0 0 0 1px rgba(181,137,0,0.3), 0 8px 24px rgba(181,137,0,0.3)',
soft: '0 0 16px rgba(181,137,0,0.22)',
card: '0 -8px 32px -12px rgba(181,137,0,0.22), 0 12px 40px rgba(3,7,18,0.45)',
cardHover: '0 -10px 40px -10px rgba(181,137,0,0.34), 0 12px 40px rgba(3,7,18,0.5)',
},
},
// ── Catppuccin Mocha (https://catppuccin.com/) ──────────────
@ -276,7 +294,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
raised: 'rgba(49,50,68,0.55)',
hairline: 'rgba(205,214,244,0.08)',
hairlineStrong: 'rgba(205,214,244,0.15)',
sheen: 'linear-gradient(180deg, rgba(205,214,244,0.05) 0%, rgba(205,214,244,0) 42%)',
sheen: 'radial-gradient(ellipse 70% 55% at 18% -8%, rgba(203,166,247,0.14) 0%, transparent 55%), linear-gradient(180deg, rgba(205,214,244,0.05) 0%, rgba(205,214,244,0) 40%)',
borderGradient: 'linear-gradient(135deg, rgba(215,185,255,0.55) 0%, rgba(205,214,244,0.14) 32%, rgba(205,214,244,0.06) 58%, rgba(203,166,247,0.22) 100%)',
blur: '24px',
},
gradient: {
@ -289,6 +308,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
glow: {
accent: '0 0 0 1px rgba(203,166,247,0.3), 0 8px 24px rgba(203,166,247,0.3)',
soft: '0 0 16px rgba(203,166,247,0.22)',
card: '0 -8px 32px -12px rgba(203,166,247,0.22), 0 12px 40px rgba(3,7,18,0.45)',
cardHover: '0 -10px 40px -10px rgba(203,166,247,0.34), 0 12px 40px rgba(3,7,18,0.5)',
},
},
// ── Dracula (https://draculatheme.com/) ─────────────────────
@ -313,7 +334,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
raised: 'rgba(56,58,74,0.55)',
hairline: 'rgba(248,248,242,0.08)',
hairlineStrong: 'rgba(248,248,242,0.15)',
sheen: 'linear-gradient(180deg, rgba(248,248,242,0.05) 0%, rgba(248,248,242,0) 42%)',
sheen: 'radial-gradient(ellipse 70% 55% at 18% -8%, rgba(189,147,249,0.14) 0%, transparent 55%), linear-gradient(180deg, rgba(248,248,242,0.05) 0%, rgba(248,248,242,0) 40%)',
borderGradient: 'linear-gradient(135deg, rgba(205,170,255,0.55) 0%, rgba(248,248,242,0.14) 32%, rgba(248,248,242,0.06) 58%, rgba(189,147,249,0.22) 100%)',
blur: '24px',
},
gradient: {
@ -326,6 +348,8 @@ const RAW: Record<ThemeKey, RawTheme> = {
glow: {
accent: '0 0 0 1px rgba(189,147,249,0.3), 0 8px 24px rgba(189,147,249,0.3)',
soft: '0 0 16px rgba(189,147,249,0.22)',
card: '0 -8px 32px -12px rgba(189,147,249,0.22), 0 12px 40px rgba(3,7,18,0.45)',
cardHover: '0 -10px 40px -10px rgba(189,147,249,0.34), 0 12px 40px rgba(3,7,18,0.5)',
},
},
}
@ -364,6 +388,7 @@ export const d3roPalette = {
hairline: 'var(--d3-glass-hairline)',
hairlineStrong: 'var(--d3-glass-hairlineStrong)',
sheen: 'var(--d3-glass-sheen)',
borderGradient: 'var(--d3-glass-borderGradient)',
blur: 'var(--d3-glass-blur)',
},
/** v2: 그라디언트 토큰 */
@ -381,6 +406,8 @@ export const d3roPalette = {
glow: {
accent: 'var(--d3-glow-accent)',
soft: 'var(--d3-glow-soft)',
card: 'var(--d3-glow-card)',
cardHover: 'var(--d3-glow-cardHover)',
},
tag: {
purple: '#a78bfa',
@ -487,6 +514,8 @@ export const d3roShadow = {
/** v2: 액센트 글로우 */
glowAccent: 'var(--d3-glow-accent)',
glowSoft: 'var(--d3-glow-soft)',
glowCard: 'var(--d3-glow-card)',
glowCardHover: 'var(--d3-glow-cardHover)',
} as const
// ── SSOT: 반경 토큰 ────────────────────────────────────
@ -547,6 +576,7 @@ function buildCssVars(key: ThemeKey): Record<string, string> {
'--d3-glass-hairline': r.glass.hairline,
'--d3-glass-hairlineStrong': r.glass.hairlineStrong,
'--d3-glass-sheen': r.glass.sheen,
'--d3-glass-borderGradient': r.glass.borderGradient,
'--d3-glass-blur': r.glass.blur,
'--d3-gradient-accent': r.gradient.accent,
'--d3-gradient-logo': r.gradient.logo,
@ -558,6 +588,8 @@ function buildCssVars(key: ThemeKey): Record<string, string> {
'--d3-gradient-wave4': r.gradient.wave4,
'--d3-glow-accent': r.glow.accent,
'--d3-glow-soft': r.glow.soft,
'--d3-glow-card': r.glow.card,
'--d3-glow-cardHover': r.glow.cardHover,
}
}
@ -642,13 +674,27 @@ function createD3ROTheme(key: ThemeKey): Theme {
defaultProps: { elevation: 0 },
styleOverrides: {
root: {
position: 'relative',
backgroundColor: 'var(--d3-glass-surface)',
backdropFilter: 'blur(var(--d3-glass-blur))',
borderRadius: 16,
border: `1px solid ${r.glass.hairline}`,
boxShadow: r.shadow.card,
transition: 'border-color 0.2s ease',
'&:hover': { borderColor: r.glass.hairlineStrong },
boxShadow: 'var(--d3-glow-card)',
transition: 'box-shadow 0.25s ease',
overflow: 'hidden',
// 그라디언트 헤어라인 링 (MetalCard와 동일 기법)
'&::after': {
content: '""',
position: 'absolute',
inset: 0,
borderRadius: 'inherit',
padding: '1px',
background: 'var(--d3-glass-borderGradient)',
WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
WebkitMaskComposite: 'xor',
maskComposite: 'exclude',
pointerEvents: 'none',
},
'&:hover': { boxShadow: 'var(--d3-glow-cardHover)' },
},
},
},