feat(app-hub): 스테이지 카드 칩 제거와 추천 공간 캐러셀 적용

맥락:
- 허브 스테이지 내부에 메모/전체공간 칩이 동시에 노출되어 시선이 분산되고 조작 밀도가 높았습니다.
- 추천 공간은 단순 스크롤보다 회전 가능한 캐러셀 인터랙션이 필요했습니다.

변경사항:
- 오늘의 공간 카드 헤더에서 메모 칩과 전체공간 칩을 제거했습니다.
- 추천 공간 섹션에 이전/다음 컨트롤을 추가해 캐러셀 회전이 가능하도록 변경했습니다.
- 전체 공간 진입은 칩 대신 텍스트 링크로 정리했습니다.
- 메모 진입 컴포넌트는 스테이지 카드 밖(하단)으로 이동해 카드 내부 밀도를 낮췄습니다.
- 스테이지 오버레이 구성을 유지하기 위해 관련 위젯 조합을 함께 정리했습니다.

검증:
- npx tsc --noEmit
- npm run build

세션-상태: 스테이지 카드 내부 칩 제거 및 추천 공간 캐러셀 반영 완료
세션-다음: 캐러셀 전환 모션(페이드/슬라이드) 미세 조정
세션-리스크: 추천 공간 수가 1개 이하일 때 회전 UI 효용이 낮아질 수 있음
This commit is contained in:
2026-03-02 11:12:08 +09:00
parent 47e80e59d2
commit 6dfa4677d9
5 changed files with 220 additions and 110 deletions

View File

@@ -2,7 +2,7 @@
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { getRoomCardBackgroundStyle, ROOM_THEMES } from '@/entities/room';
import { ROOM_THEMES } from '@/entities/room';
import {
GOAL_CHIPS,
SOUND_PRESETS,
@@ -52,7 +52,7 @@ export const AppHubWidget = () => {
const router = useRouter();
const { pushToast } = useToast();
const { thoughts, thoughtCount, clearThoughts } = useThoughtInbox();
const { selectedRoom, selectedRoomId, selectRoom } = useRoomSelection(
const { selectedRoomId, selectRoom } = useRoomSelection(
ROOM_THEMES[0].id,
);
@@ -108,15 +108,11 @@ export const AppHubWidget = () => {
<div className="relative min-h-screen overflow-hidden text-white">
<div
aria-hidden
className={cn(
'absolute inset-0',
cinematic && 'scale-[1.01]',
)}
className={cn('absolute inset-0')}
style={{
...getRoomCardBackgroundStyle(selectedRoom),
filter: cinematic
? 'brightness(0.86) saturate(1.06) contrast(1.03)'
: 'brightness(0.94) saturate(0.96)',
backgroundImage: cinematic
? 'radial-gradient(132% 94% at 8% 0%, rgba(148,163,184,0.34) 0%, rgba(15,23,42,0) 46%), radial-gradient(116% 88% at 94% 6%, rgba(125,211,252,0.18) 0%, rgba(15,23,42,0) 52%), linear-gradient(162deg, #0f172a 0%, #111827 42%, #0b1220 100%)'
: 'radial-gradient(130% 92% at 8% 0%, rgba(148,163,184,0.22) 0%, rgba(248,250,252,0) 48%), radial-gradient(104% 86% at 90% 8%, rgba(125,211,252,0.2) 0%, rgba(248,250,252,0) 54%), linear-gradient(164deg, #f8fafc 0%, #e2e8f0 48%, #dbe7f5 100%)',
}}
/>
<div
@@ -124,8 +120,8 @@ export const AppHubWidget = () => {
className={cn(
'absolute inset-0',
cinematic
? 'bg-[linear-gradient(180deg,rgba(2,6,23,0.22)_0%,rgba(2,6,23,0.34)_50%,rgba(2,6,23,0.52)_100%)]'
: 'bg-[linear-gradient(180deg,rgba(248,250,252,0.44)_0%,rgba(241,245,249,0.22)_42%,rgba(15,23,42,0.3)_100%)]',
? 'bg-[radial-gradient(circle_at_24%_16%,rgba(148,163,184,0.14),transparent_36%),radial-gradient(circle_at_78%_14%,rgba(125,211,252,0.1),transparent_34%),linear-gradient(180deg,rgba(2,6,23,0.16)_0%,rgba(2,6,23,0.32)_52%,rgba(2,6,23,0.5)_100%)]'
: 'bg-[radial-gradient(circle_at_20%_14%,rgba(148,163,184,0.16),transparent_38%),radial-gradient(circle_at_82%_16%,rgba(125,211,252,0.12),transparent_38%),linear-gradient(180deg,rgba(248,250,252,0.32)_0%,rgba(241,245,249,0.12)_40%,rgba(15,23,42,0.26)_100%)]',
)}
/>
<div
@@ -157,8 +153,12 @@ export const AppHubWidget = () => {
/>
<main className="mx-auto w-full max-w-6xl flex-1 px-4 pb-8 pt-5 sm:px-6 sm:pt-6 lg:px-8 lg:pb-10">
<div className="grid gap-4 lg:grid-cols-[minmax(0,360px)_minmax(0,1fr)] lg:gap-5">
<section className="order-1">
<RoomsGalleryWidget
visualMode={visualMode}
rooms={ROOM_THEMES}
selectedRoomId={selectedRoomId}
onRoomSelect={selectRoom}
startPanel={(
<StartRitualWidget
visualMode={visualMode}
goalInput={goalInput}
@@ -168,25 +168,16 @@ export const AppHubWidget = () => {
onGoalChipSelect={handleGoalChipSelect}
onQuickEnter={handleQuickEnter}
onOpenCustomEntry={() => setCustomEntryOpen(true)}
inStage
/>
</section>
<section className="order-2 lg:row-span-2">
<RoomsGalleryWidget
visualMode={visualMode}
rooms={ROOM_THEMES}
selectedRoomId={selectedRoomId}
onRoomSelect={selectRoom}
/>
</section>
<section className="order-3">
<ThoughtSummaryEntryWidget
visualMode={visualMode}
thoughtCount={thoughtCount}
onOpen={() => setThoughtInboxOpen(true)}
/>
</section>
)}
/>
<div className="mt-3 sm:mt-4">
<ThoughtSummaryEntryWidget
visualMode={visualMode}
thoughtCount={thoughtCount}
onOpen={() => setThoughtInboxOpen(true)}
/>
</div>
</main>
</div>