style(app-hub): 허브 배경 밝기와 카드 가시성을 함께 보정

맥락:
- /app 허브에서 배경 이미지가 전면에 강하게 드러나 전체 인상이 어둡고, 룸별 배경 복잡도에 따라 카드 가독성이 흔들릴 수 있었다.
- 밝은 톤 전환과 동시에 카드 정보 표면을 안정화해 저자극 톤을 유지할 필요가 있었다.

변경사항:
- AppHub 배경에 blur + 밝기/채도 필터를 적용하고, 밝은 워시 오버레이로 명도를 상향했다.
- 전역 그레인 강도를 낮춰 시각적 노이즈를 줄였다.
- RoomPreviewCard 배경 마스크를 재조정하고, 내부 콘텐츠 영역에 반투명 패널을 추가해 텍스트 대비를 고정했다.
- RoomPreviewCard 내 메타 텍스트/칩의 대비를 상향해 복잡한 배경에서도 읽기 쉬운 상태로 보정했다.
- GlassCard의 표면 불투명도와 그림자를 조정해 /app 전반의 무게감을 완화했다.
- 세션 문서(90_current_state, session_brief)에 이번 패치 내역과 잔여 리스크를 반영했다.

검증:
- npx tsc --noEmit

세션-상태: /app 배경 밝기와 카드 가시성 안정화 패치 반영 완료
세션-다음: RoomSheet/도크 패널의 인원수 기반 표현을 분위기형 정보로 전환
세션-리스크: 배경 blur/filter 적용으로 저사양 환경에서 렌더링 비용이 소폭 증가할 수 있음
This commit is contained in:
2026-02-28 23:24:30 +09:00
parent 3679f4bb40
commit 105c5785b8
5 changed files with 46 additions and 19 deletions

View File

@@ -47,6 +47,11 @@ Last Updated: 2026-02-28
- `/app` 룸 카드 hydration 에러 수정:
- `RoomPreviewCard`의 중첩 `<button>` 구조 제거
- 카드 내부 칩을 비인터랙티브 `span`으로 변경해 HTML 규칙 위반 해소
- `/app` 허브 배경/카드 가시성 안정화 패치:
- 허브 배경에 blur + 밝기 보정 + 저채도 필터를 적용해 배경 복잡도 완화
- 허브 전역 오버레이를 밝은 워시 중심으로 조정하고 그레인 강도 축소
- `RoomPreviewCard` 내부 콘텐츠 영역에 반투명 패널을 추가해 배경 변동과 텍스트 대비를 분리
- `GlassCard` 표면을 조금 더 밝고 가벼운 글래스 톤으로 보정
- 몰입 모드 ON 시 `/space` 크롬 정리:
- 상단 `Current Room` 블록 숨김
- 우상단 허브 버튼 소형 아이콘화
@@ -79,6 +84,7 @@ Last Updated: 2026-02-28
- bar/ring 진행 표시는 서로 다른 구현(JS/CSS)이라 동기화 규칙 변경 시 회귀 점검이 필요
- 안내 카피가 HUD 목표 문구와 교체 표시되므로 정보 밀도 균형 점검 필요
- 밝아진 배경 구간에서 일부 white 텍스트의 대비가 환경(디스플레이 밝기)에 따라 약해질 수 있음
- 배경 필터/블러 적용으로 저사양 환경에서 스크롤 시 미세한 페인팅 비용 증가 가능성 존재
## CHANGED FILES
@@ -132,6 +138,9 @@ Last Updated: 2026-02-28
- `src/widgets/space-shell/ui/SpaceSkeletonWidget.tsx`
- `src/widgets/stats-overview/ui/StatsOverviewWidget.tsx`
- `src/widgets/settings-panel/ui/SettingsPanelWidget.tsx`
- `src/shared/ui/GlassCard.tsx`
- `src/widgets/app-hub/ui/AppHubWidget.tsx`
- `src/features/room-select/ui/RoomPreviewCard.tsx`
## QUICK VERIFY
@@ -140,3 +149,4 @@ Last Updated: 2026-02-28
3. `/space`: 하단 사운드 바 없음, 오른쪽 `🎧 Sound` 시트에서 프리셋 선택 가능
4. `/space`: 몰입 모드 ON 시 상단 룸 블록 숨김 + 레일 미니화 + HUD 저대비 적용
5. `/app`: 콘솔에 `button cannot be a descendant of button` hydration 에러가 재발하지 않음
6. `/app`: 숲/벽난로처럼 텍스처가 많은 룸 선택 시에도 카드 내부 텍스트 시인성이 유지됨

View File

@@ -41,6 +41,10 @@ Last Updated: 2026-02-28
- `/app` 룸 카드 hydration 에러를 수정했다.
- `RoomPreviewCard`의 중첩 `<button>` 구조를 제거했다.
- 카드 내부 칩을 비인터랙티브 태그로 바꿔 콘솔 에러를 해소했다.
- `/app` 배경과 카드 가시성을 함께 보정했다.
- 허브 배경에 blur + 밝기 보정 + 저채도 필터를 적용해 배경 복잡도를 낮췄다.
- 전역 오버레이를 밝은 워시 중심으로 조정하고 그레인 강도를 낮췄다.
- `RoomPreviewCard` 내부에 반투명 정보 패널을 추가해 텍스트 대비를 안정화했다.
- 몰입 모드 ON 시 상단 룸 블록 숨김, 레일 미니화, HUD 저대비, 비네팅 강화가 적용된다.
- 이후 작업은 `docs/work.md`를 기준으로 실행한다.
@@ -53,6 +57,7 @@ Last Updated: 2026-02-28
- bar/ring 진행 구현 방식이 달라 향후 진행 규칙 변경 시 회귀 확인이 필요함
- HUD 안내 문구와 목표 문구가 교체 노출되므로 정보 우선순위 점검이 필요함
- 밝은 배경 구간에서 white 텍스트 대비가 낮아질 수 있어 기기별 시인성 점검이 필요함
- 배경 blur/filter 적용으로 저사양 환경에서 렌더링 비용이 소폭 증가할 수 있음
## 상세 원문 위치

View File

@@ -24,21 +24,29 @@ export const RoomPreviewCard = ({
: 'border-white/18 hover:border-white/35',
)}
>
<div aria-hidden className="absolute inset-0" style={getRoomBackgroundStyle(room)} />
<div aria-hidden className="absolute inset-0 bg-slate-900/38" />
<div aria-hidden className="absolute inset-0 bg-[linear-gradient(to_top,rgba(2,6,23,0.72),rgba(2,6,23,0.18))]" />
<div
aria-hidden
className="absolute inset-0"
style={{
...getRoomBackgroundStyle(room),
filter: 'brightness(1.06) saturate(0.9)',
}}
/>
<div aria-hidden className="absolute inset-0 bg-slate-900/26" />
<div aria-hidden className="absolute inset-0 bg-[linear-gradient(to_top,rgba(2,6,23,0.62),rgba(2,6,23,0.22))]" />
<div aria-hidden className="absolute inset-0 bg-[radial-gradient(120%_80%_at_50%_0%,rgba(255,255,255,0.18),rgba(255,255,255,0)_58%)]" />
<div className="relative space-y-3">
<div className="relative space-y-3 rounded-xl border border-white/18 bg-slate-950/24 p-3 backdrop-blur-[1.5px]">
<div>
<h3 className="text-base font-semibold text-white">{room.name}</h3>
<p className="mt-1 text-xs text-white/72">{room.description}</p>
<p className="mt-1 text-xs text-white/82">{room.description}</p>
</div>
<div className="flex flex-wrap gap-2">
{room.tags.map((tag) => (
<span
key={tag}
className="inline-flex items-center gap-1 rounded-full bg-slate-300/25 px-3 py-1.5 text-xs font-medium text-slate-100 ring-1 ring-slate-200/40"
className="inline-flex items-center gap-1 rounded-full bg-white/14 px-3 py-1.5 text-xs font-medium text-white/92 ring-1 ring-white/24"
>
{tag}
</span>
@@ -46,14 +54,14 @@ export const RoomPreviewCard = ({
</div>
<div className="space-y-2">
<p className="text-xs text-white/76">
<p className="text-xs text-white/84">
: <span className="font-medium text-white">{room.recommendedSound}</span>
</p>
<div className="flex flex-wrap gap-2">
<span className="inline-flex items-center gap-1 rounded-full bg-sky-300/25 px-3 py-1.5 text-xs font-medium text-sky-100 ring-1 ring-sky-200/55">
<span className="inline-flex items-center gap-1 rounded-full bg-sky-300/22 px-3 py-1.5 text-xs font-medium text-sky-100 ring-1 ring-sky-200/48">
· {room.recommendedTime}
</span>
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1.5 text-xs font-medium text-white/90 ring-1 ring-white/20">
<span className="inline-flex items-center gap-1 rounded-full bg-white/13 px-3 py-1.5 text-xs font-medium text-white/92 ring-1 ring-white/22">
· {room.vibeLabel}
</span>
</div>

View File

@@ -15,8 +15,8 @@ export const GlassCard = ({
<div
{...props}
className={cn(
'rounded-2xl border border-white/20 bg-slate-950/35 backdrop-blur-xl',
elevated && 'shadow-[0_24px_60px_rgba(15,23,42,0.42)]',
'rounded-2xl border border-white/24 bg-slate-900/28 backdrop-blur-xl',
elevated && 'shadow-[0_20px_56px_rgba(15,23,42,0.3)]',
className,
)}
>

View File

@@ -93,19 +93,23 @@ export const AppHubWidget = () => {
<div className="relative min-h-screen overflow-hidden text-white">
<div
aria-hidden
className="absolute inset-0"
style={getRoomBackgroundStyle(selectedRoom)}
className="absolute inset-0 scale-[1.02] blur-[1.5px]"
style={{
...getRoomBackgroundStyle(selectedRoom),
filter: 'brightness(1.08) saturate(0.88)',
}}
/>
<div aria-hidden className="absolute inset-0 bg-slate-900/22" />
<div
aria-hidden
className="absolute inset-0 bg-[radial-gradient(circle_at_14%_0%,rgba(255,255,255,0.5),transparent_45%),radial-gradient(circle_at_86%_18%,rgba(191,219,254,0.3),transparent_44%),linear-gradient(165deg,rgba(248,250,252,0.26)_0%,rgba(226,232,240,0.24)_52%,rgba(203,213,225,0.3)_100%)]"
/>
<div
aria-hidden
className="absolute inset-0 bg-[radial-gradient(circle_at_14%_0%,rgba(255,255,255,0.34),transparent_44%),radial-gradient(circle_at_86%_18%,rgba(186,230,253,0.24),transparent_42%),linear-gradient(160deg,rgba(15,23,42,0.32)_0%,rgba(30,41,59,0.34)_52%,rgba(15,23,42,0.44)_100%)]"
/>
<div
aria-hidden
className="absolute inset-0 opacity-28"
className="absolute inset-0 opacity-[0.18]"
style={{
backgroundImage:
"url('/textures/grain.png'), repeating-linear-gradient(0deg, rgba(255,255,255,0.035) 0 1px, transparent 1px 2px)",
"url('/textures/grain.png'), repeating-linear-gradient(0deg, rgba(255,255,255,0.024) 0 1px, transparent 1px 2px)",
mixBlendMode: 'soft-light',
}}
/>