맥락: - /space에서 실수 이탈을 줄이면서도 명확한 탈출 동선을 유지하기 위해 나가기 액션을 1초 롱프레스 방식으로 변경 변경사항: - features/exit-hold 추가(useHoldToConfirm, ExitHoldButton) - 1초 롱프레스와 가속 진행 규칙(0.05초 -> 20%)을 feature 내부에 구현 - 몰입 OFF에서는 bar(좌->우 fill), 몰입 ON에서는 ring 진행 표시로 분기 - 1초 미만 해제/마우스 leave/touch cancel 시 진행률 즉시 리셋 - 완료 시 나가기(더미) 토스트 + 몰입 모드 OFF 동작 연결 - docs/90_current_state.md, docs/session_brief.md 상태 업데이트 검증: - npx tsc --noEmit 세션-상태: 상단 나가기 액션은 롱프레스 완료 시에만 트리거됨 세션-다음: 롱프레스 인터랙션 인지성 보완용 힌트 카피 도입 여부 검토 세션-리스크: 터치 환경에서 롱프레스 UI 의도를 즉시 이해하지 못할 가능성 있음
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import { useMemo } from 'react';
|
|
import { useSearchParams } from 'next/navigation';
|
|
import { getRoomBackgroundStyle, getRoomById, ROOM_THEMES } from '@/entities/room';
|
|
import { SOUND_PRESETS } from '@/entities/session';
|
|
import { useImmersionMode } from '@/features/immersion-mode';
|
|
import { cn } from '@/shared/lib/cn';
|
|
import { SpaceChromeWidget } from '@/widgets/space-chrome';
|
|
import { SpaceTimerHudWidget } from '@/widgets/space-timer-hud';
|
|
import { SpaceToolsDockWidget } from '@/widgets/space-tools-dock';
|
|
|
|
export const SpaceSkeletonWidget = () => {
|
|
const searchParams = useSearchParams();
|
|
|
|
const roomId = searchParams.get('room') ?? ROOM_THEMES[0].id;
|
|
const goal = searchParams.get('goal') ?? '오늘은 한 조각만 집중해요';
|
|
const timerLabel = searchParams.get('timer') ?? '25/5';
|
|
const soundFromQuery = searchParams.get('sound');
|
|
|
|
const room = useMemo(() => getRoomById(roomId) ?? ROOM_THEMES[0], [roomId]);
|
|
const { isImmersionMode, toggleImmersionMode, exitImmersionMode } = useImmersionMode();
|
|
const initialSoundPresetId =
|
|
SOUND_PRESETS.find((preset) => preset.id === soundFromQuery)?.id ??
|
|
SOUND_PRESETS[0].id;
|
|
|
|
return (
|
|
<div className="relative min-h-screen overflow-x-hidden overflow-y-hidden text-white">
|
|
<div
|
|
aria-hidden
|
|
className="absolute inset-0 w-full"
|
|
style={getRoomBackgroundStyle(room)}
|
|
/>
|
|
<div
|
|
aria-hidden
|
|
className={cn(
|
|
'absolute inset-0 w-full transition-colors',
|
|
isImmersionMode ? 'bg-slate-950/72' : 'bg-slate-950/62',
|
|
)}
|
|
/>
|
|
<div
|
|
aria-hidden
|
|
className={cn(
|
|
'absolute inset-0 w-full transition-opacity',
|
|
isImmersionMode ? 'opacity-48' : 'opacity-35',
|
|
)}
|
|
style={{
|
|
backgroundImage:
|
|
"url('/textures/grain.png'), repeating-linear-gradient(0deg, rgba(255,255,255,0.045) 0 1px, transparent 1px 2px)",
|
|
}}
|
|
/>
|
|
<div
|
|
aria-hidden
|
|
className={cn(
|
|
'absolute inset-0 w-full transition-opacity',
|
|
isImmersionMode ? 'opacity-88' : 'opacity-62',
|
|
)}
|
|
style={{
|
|
background:
|
|
'radial-gradient(120% 90% at 50% 50%, rgba(2,6,23,0) 24%, rgba(2,6,23,0.78) 100%)',
|
|
}}
|
|
/>
|
|
|
|
<div className="relative z-10 flex min-h-screen flex-col pr-14">
|
|
<SpaceChromeWidget
|
|
roomName={room.name}
|
|
vibeLabel={room.vibeLabel}
|
|
isImmersionMode={isImmersionMode}
|
|
onExitRequested={exitImmersionMode}
|
|
/>
|
|
|
|
<main className="flex-1" />
|
|
</div>
|
|
|
|
<SpaceTimerHudWidget
|
|
timerLabel={timerLabel}
|
|
goal={goal}
|
|
isImmersionMode={isImmersionMode}
|
|
/>
|
|
|
|
<SpaceToolsDockWidget
|
|
roomName={room.name}
|
|
activeMembers={room.activeMembers}
|
|
presence={room.presence}
|
|
initialSoundPresetId={initialSoundPresetId}
|
|
isImmersionMode={isImmersionMode}
|
|
onToggleImmersionMode={toggleImmersionMode}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|