style(space-hud): 30초 복귀 액션을 숨 고르기 톤으로 리브랜딩

맥락:
- /space 하단 HUD의 30초 복귀 액션을 저자극 감성 톤으로 정리해 복귀 행동의 심리적 부담을 낮춘다.

변경사항:
- restart-30s feature에 카피 상수(model/copy)를 추가하고 버튼/모드/안내 문구를 중앙 관리로 전환했다.
- useRestart30s에 더미 상태 전환(isBreatheMode, hintMessage)을 추가해 클릭 후 2초 내 안내 노출과 자동 복귀를 구현했다.
- HUD에서 30초 진입 중 상태 라벨을 BREATHE로 표시하고, 안내 문구를 목표 라인에 저대비로 잠깐 노출하도록 조정했다.
- 30초 액션 버튼을 "숨 고르기 30초" + 🌬️ 형태의 보조 액션 톤으로 변경했다.
- 세션 복구 문서(90_current_state, session_brief)에 이번 작업 상태/리스크를 반영했다.

검증:
- npx tsc --noEmit

세션-상태: /space 30초 복귀 액션 카피 리브랜딩 및 HUD 더미 안내 반영 완료
세션-다음: RoomSheet/도크 인원수 기반 카피를 큐레이션형 표현으로 전환
세션-리스크: HUD 목표 문구와 안내 문구가 교체 노출되어 정보 우선순위 점검 필요
This commit is contained in:
2026-02-27 14:58:35 +09:00
parent 20638b69a4
commit 313ef88961
7 changed files with 98 additions and 19 deletions

View File

@@ -1 +1,3 @@
export * from './model/copy';
export * from './model/useRestart30s';
export * from './ui/Restart30sAction';

View File

@@ -0,0 +1,4 @@
export const RECOVERY_30S_BUTTON_LABEL = '숨 고르기 30초';
export const RECOVERY_30S_MODE_LABEL = 'BREATHE';
export const RECOVERY_30S_TOAST_MESSAGE = '잠깐 숨 고르고, 다시 천천히 시작해요.';
export const RECOVERY_30S_COMPLETE_MESSAGE = '준비됐어요. 집중으로 돌아가요.';

View File

@@ -1,18 +1,62 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { useToast } from '@/shared/ui';
import {
RECOVERY_30S_BUTTON_LABEL,
RECOVERY_30S_TOAST_MESSAGE,
} from './copy';
const MODE_DURATION_MS = 2000;
const HINT_DURATION_MS = 1800;
export const useRestart30s = () => {
const { pushToast } = useToast();
const [isBreatheMode, setBreatheMode] = useState(false);
const [hintMessage, setHintMessage] = useState<string | null>(null);
const resetTimerRef = useRef<number | null>(null);
const hintTimerRef = useRef<number | null>(null);
const clearTimers = () => {
if (resetTimerRef.current !== null) {
window.clearTimeout(resetTimerRef.current);
resetTimerRef.current = null;
}
if (hintTimerRef.current !== null) {
window.clearTimeout(hintTimerRef.current);
hintTimerRef.current = null;
}
};
useEffect(() => {
return () => {
clearTimers();
};
}, []);
const triggerRestart = () => {
clearTimers();
setBreatheMode(true);
setHintMessage(RECOVERY_30S_TOAST_MESSAGE);
pushToast({
title: '30초 리스타트(더미)',
description: '실제 리스타트 동작은 아직 연결되지 않았어요.',
title: RECOVERY_30S_BUTTON_LABEL,
description: RECOVERY_30S_TOAST_MESSAGE,
});
hintTimerRef.current = window.setTimeout(() => {
setHintMessage(null);
}, HINT_DURATION_MS);
resetTimerRef.current = window.setTimeout(() => {
setBreatheMode(false);
}, MODE_DURATION_MS);
};
return {
isBreatheMode,
hintMessage,
triggerRestart,
};
};

View File

@@ -1,31 +1,30 @@
'use client';
import { cn } from '@/shared/lib/cn';
import { useRestart30s } from '../model/useRestart30s';
import { RECOVERY_30S_BUTTON_LABEL } from '../model/copy';
interface Restart30sActionProps {
onTrigger: () => void;
className?: string;
}
export const Restart30sAction = ({ className }: Restart30sActionProps) => {
const { triggerRestart } = useRestart30s();
export const Restart30sAction = ({
onTrigger,
className,
}: Restart30sActionProps) => {
return (
<button
type="button"
onClick={triggerRestart}
onClick={onTrigger}
className={cn(
'inline-flex items-center gap-2 text-xs text-white/66 transition hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-200/80',
'inline-flex items-center gap-1.5 text-xs text-white/66 transition hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-200/80',
className,
)}
>
<span aria-hidden className="text-[13px]">
</span>
<span> </span>
<span className="rounded-full border border-white/25 bg-white/8 px-2 py-0.5 text-[10px] text-white/72">
30
🌬
</span>
<span>{RECOVERY_30S_BUTTON_LABEL}</span>
</button>
);
};