feat(app-hub): 씬 중심 허브 화면으로 전면 리빌드
맥락: - /app 첫 화면의 정보량이 많아 LifeAt/Portal 같은 몰입형 경험과 거리가 있었습니다. - Start, 공간 선택, 메모 진입을 점진 노출 구조로 재정렬할 필요가 있었습니다. 변경사항: - AppHub를 데스크톱 2열(컨트롤/씬) 구조와 모바일 스택 구조로 재편했습니다. - Start CTA를 컴팩트 위계로 정리하고 커스텀 액션을 2순위 텍스트형으로 유지했습니다. - Room 영역을 선택 Hero 1개 + 추천 썸네일 스트립 + 더보기 시트 구조로 전면 변경했습니다. - 최근 생각은 단일 진입점 + 인박스 시트로 통합하고 localStorage 기반 thought inbox를 추가했습니다. - /space 종료 동선에 세션 요약 시트(최근 메모 3개)를 연결해 허브 복귀 흐름을 정리했습니다. - AppHub 기본 비주얼 모드를 cinematic으로 고정하고 배경 오버레이를 가독성 중심으로 재조정했습니다. 검증: - npx tsc --noEmit - npm run build 세션-상태: 씬 중심 허브 리빌드와 메모/시트 기반 점진 노출 구조 반영 완료 세션-다음: 실제 사용자 테스트 후 카드/텍스트 밀도 미세 조정 세션-리스크: 실디바이스 대비(특히 저사양 모바일)에서 배경/블러 렌더링 비용 확인 필요
This commit is contained in:
@@ -1,23 +1,101 @@
|
||||
import { MembershipTierBadge, type ViewerProfile } from '@/entities/user';
|
||||
import type { AppHubVisualMode } from '@/shared/config/appHubVisualMode';
|
||||
import { cn } from '@/shared/lib/cn';
|
||||
import { ProfileMenu } from '@/features/profile-menu';
|
||||
|
||||
interface AppTopBarProps {
|
||||
user: ViewerProfile;
|
||||
oneLiner: string;
|
||||
onLogout: () => void;
|
||||
visualMode?: AppHubVisualMode;
|
||||
thoughtCount?: number;
|
||||
onOpenThoughtInbox?: () => void;
|
||||
onOpenBilling?: () => void;
|
||||
}
|
||||
|
||||
export const AppTopBar = ({ user, oneLiner, onLogout }: AppTopBarProps) => {
|
||||
export const AppTopBar = ({
|
||||
user,
|
||||
oneLiner,
|
||||
onLogout,
|
||||
visualMode = 'light',
|
||||
thoughtCount = 0,
|
||||
onOpenThoughtInbox,
|
||||
onOpenBilling,
|
||||
}: AppTopBarProps) => {
|
||||
const cinematic = visualMode === 'cinematic';
|
||||
const thoughtCountLabel = thoughtCount > 99 ? '99+' : `${thoughtCount}`;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-20 border-b border-brand-dark/12 bg-white/78 px-4 py-3 text-brand-dark backdrop-blur-md sm:px-6">
|
||||
<div className="mx-auto flex w-full max-w-7xl items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-semibold tracking-tight text-brand-dark">VibeRoom</p>
|
||||
<header className="relative z-20 px-4 pt-4 sm:px-6 lg:px-8">
|
||||
<div
|
||||
className={cn(
|
||||
'mx-auto flex w-full max-w-6xl items-center justify-between gap-3 rounded-2xl border px-3.5 py-2.5 backdrop-blur-xl sm:px-4',
|
||||
cinematic
|
||||
? 'border-white/18 bg-slate-950/44 text-white shadow-[0_16px_44px_rgba(2,6,23,0.36)]'
|
||||
: 'border-brand-dark/12 bg-white/80 text-brand-dark shadow-[0_14px_40px_rgba(15,23,42,0.12)]',
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p
|
||||
className={cn(
|
||||
'text-sm font-semibold tracking-tight',
|
||||
cinematic ? 'text-white/94' : 'text-brand-dark',
|
||||
)}
|
||||
>
|
||||
VibeRoom
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
'mt-0.5 hidden truncate text-xs sm:block',
|
||||
cinematic ? 'text-white/62' : 'text-brand-dark/56',
|
||||
)}
|
||||
>
|
||||
{oneLiner}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="hidden text-center text-sm text-brand-dark/64 md:block">{oneLiner}</p>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2.5 sm:gap-3">
|
||||
{onOpenBilling ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenBilling}
|
||||
className={cn(
|
||||
'hidden text-xs font-medium transition-colors md:inline-flex',
|
||||
cinematic
|
||||
? 'text-white/62 hover:text-white/90'
|
||||
: 'text-brand-dark/56 hover:text-brand-dark/84',
|
||||
)}
|
||||
>
|
||||
PRO 기능 보기
|
||||
</button>
|
||||
) : null}
|
||||
{onOpenThoughtInbox ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenThoughtInbox}
|
||||
className={cn(
|
||||
'inline-flex h-8 items-center gap-1.5 rounded-full border px-2.5 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/75 sm:h-9 sm:px-3 sm:text-xs',
|
||||
cinematic
|
||||
? 'border-white/22 bg-white/10 text-white/86 hover:bg-white/16'
|
||||
: 'border-brand-dark/14 bg-white/72 text-brand-dark/80 hover:bg-white/90',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden>📝</span>
|
||||
<span className="hidden sm:inline">메모</span>
|
||||
{thoughtCount > 0 ? (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex min-w-[1.2rem] items-center justify-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold',
|
||||
cinematic
|
||||
? 'bg-sky-200/20 text-sky-100'
|
||||
: 'bg-brand-primary/14 text-brand-primary/86',
|
||||
)}
|
||||
>
|
||||
{thoughtCountLabel}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
) : null}
|
||||
<MembershipTierBadge tier={user.membershipTier} />
|
||||
<ProfileMenu user={user} onLogout={onLogout} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user