feat(space): break와 return 톤 분리

This commit is contained in:
2026-03-14 18:05:59 +09:00
parent a27cce9a67
commit fe67597320
6 changed files with 87 additions and 39 deletions

View File

@@ -43,6 +43,10 @@ Last Updated: 2026-03-14
- tray 폭과 열림 높이를 키워 긴 한국어 카피가 잘리지 않게 조정 - tray 폭과 열림 높이를 키워 긴 한국어 카피가 잘리지 않게 조정
- eyebrow / title / body의 typography hierarchy와 line-height를 재정렬 - eyebrow / title / body의 typography hierarchy와 line-height를 재정렬
- option row spacing, radius, chevron 위치를 보정해 급조된 버튼 묶음 느낌을 완화 - option row spacing, radius, chevron 위치를 보정해 급조된 버튼 묶음 느낌을 완화
- `/space` Pause / Break / Return tone 분리 1차 구현:
- `Return(focus)``Return(break)`가 같은 tray처럼 보이지 않도록 break tray에 emerald tint release tone 도입
- `Goal Complete``잠깐 쉬기` 선택도 같은 break 계열 material로 연결
- timer HUD는 break phase에서 더 가벼운 emerald 계열 glass로 보정해 focus/pause와 구분되게 조정
- Focus Entry Surface / Execution Surface 재정의: - Focus Entry Surface / Execution Surface 재정의:
- `/app`을 planning home이 아니라 hero-first focus entry surface로 재구성 - `/app`을 planning home이 아니라 hero-first focus entry surface로 재구성

View File

@@ -48,6 +48,10 @@ Last Updated: 2026-03-14
- tray 폭과 max-height를 늘려 한국어 제목/설명 잘림을 줄였다. - tray 폭과 max-height를 늘려 한국어 제목/설명 잘림을 줄였다.
- title/body line-height와 spacing을 다시 잡아 임시 패널 느낌을 줄였다. - title/body line-height와 spacing을 다시 잡아 임시 패널 느낌을 줄였다.
- option row의 radius, padding, chevron 정렬을 보정해 더 차분한 recovery panel처럼 읽히게 했다. - option row의 radius, padding, chevron 정렬을 보정해 더 차분한 recovery panel처럼 읽히게 했다.
- `Pause / Break / Return`의 감정 톤 분리를 시작했다.
- `Return(break)`은 focus 복귀 tray와 같은 재질을 쓰지 않고, 더 부드러운 emerald tint release tone으로 분리했다.
- `Goal Complete``잠깐 쉬기` 선택도 같은 release tone으로 연결했다.
- timer HUD도 break phase에서는 더 가벼운 emerald 계열 material로 바뀌어 pause/focus와 다르게 읽히도록 정리 중이다.
- `/app`을 single-goal commitment gate로 다시 줄였다. - `/app`을 single-goal commitment gate로 다시 줄였다.
- 2-step ritual setup을 제거했다. - 2-step ritual setup을 제거했다.
- current session이 있으면 `Resume` UI만 보여주고, `/space`로 이어가기만 제안한다. - current session이 있으면 `Resume` UI만 보여주고, `/space`로 이어가기만 제안한다.

View File

@@ -8,6 +8,7 @@ import {
HUD_FIELD, HUD_FIELD,
HUD_OPTION_CHEVRON, HUD_OPTION_CHEVRON,
HUD_OPTION_ROW, HUD_OPTION_ROW,
HUD_OPTION_ROW_BREAK,
HUD_OPTION_ROW_PRIMARY, HUD_OPTION_ROW_PRIMARY,
HUD_TEXT_LINK, HUD_TEXT_LINK,
HUD_TEXT_LINK_STRONG, HUD_TEXT_LINK_STRONG,
@@ -190,17 +191,17 @@ export const GoalCompleteSheet = ({
type="button" type="button"
onClick={onRest} onClick={onRest}
disabled={isSubmitting} disabled={isSubmitting}
className={HUD_OPTION_ROW} className={cn(HUD_OPTION_ROW, HUD_OPTION_ROW_BREAK)}
> >
<div> <div>
<p className="text-[13px] font-medium tracking-[0.01em] text-white/78"> <p className="text-[13px] font-medium tracking-[0.01em] text-white/78">
{copy.space.goalComplete.restButton} {copy.space.goalComplete.restButton}
</p> </p>
<p className="mt-1 text-[12px] text-white/44"> <p className="mt-1 text-[12px] text-emerald-50/56">
{copy.space.goalComplete.restDescription} {copy.space.goalComplete.restDescription}
</p> </p>
</div> </div>
<span aria-hidden className={HUD_OPTION_CHEVRON}></span> <span aria-hidden className={cn(HUD_OPTION_CHEVRON, 'text-emerald-100/34 group-hover:text-emerald-100/58')}></span>
</button> </button>
<button <button
type="button" type="button"

View File

@@ -5,9 +5,15 @@ import { cn } from '@/shared/lib/cn';
import { import {
HUD_OPTION_CHEVRON, HUD_OPTION_CHEVRON,
HUD_OPTION_ROW, HUD_OPTION_ROW,
HUD_OPTION_ROW_BREAK,
HUD_OPTION_ROW_PRIMARY, HUD_OPTION_ROW_PRIMARY,
HUD_RETURN_BODY,
HUD_RETURN_TITLE,
HUD_TRAY_HAIRLINE_BREAK,
HUD_TRAY_HAIRLINE, HUD_TRAY_HAIRLINE,
HUD_TRAY_LAYER_BREAK,
HUD_TRAY_LAYER, HUD_TRAY_LAYER,
HUD_TRAY_SHELL_BREAK,
HUD_TRAY_SHELL, HUD_TRAY_SHELL,
} from './overlayStyles'; } from './overlayStyles';
@@ -42,43 +48,46 @@ export const ReturnPrompt = ({
)} )}
aria-hidden={!open} aria-hidden={!open}
> >
<section className={HUD_TRAY_SHELL}> <section className={cn(isBreakReturn ? HUD_TRAY_SHELL_BREAK : HUD_TRAY_SHELL)}>
<div aria-hidden className={HUD_TRAY_LAYER} /> <div aria-hidden className={isBreakReturn ? HUD_TRAY_LAYER_BREAK : HUD_TRAY_LAYER} />
<div aria-hidden className={HUD_TRAY_HAIRLINE} /> <div aria-hidden className={isBreakReturn ? HUD_TRAY_HAIRLINE_BREAK : HUD_TRAY_HAIRLINE} />
<div className="relative"> <div className="relative px-6 py-5">
<p className="text-[11px] font-medium tracking-[0.08em] text-white/42"> <p className={cn(
'text-[11px] font-medium tracking-[0.12em]',
isBreakReturn ? 'text-emerald-100/54' : 'text-white/42',
)}>
{copy.space.focusHud.returnPromptEyebrow} {copy.space.focusHud.returnPromptEyebrow}
</p> </p>
<h3 className="mt-1 text-[1rem] font-medium tracking-tight text-white/94"> <h3 className={cn(HUD_RETURN_TITLE, isBreakReturn ? 'text-white/96' : undefined)}>
{isBreakReturn {isBreakReturn
? copy.space.focusHud.returnPromptBreakTitle ? copy.space.focusHud.returnPromptBreakTitle
: copy.space.focusHud.returnPromptFocusTitle} : copy.space.focusHud.returnPromptFocusTitle}
</h3> </h3>
<p className="mt-1 text-[13px] text-white/58"> <p className={cn(HUD_RETURN_BODY, isBreakReturn ? 'text-emerald-50/62' : undefined)}>
{isBreakReturn {isBreakReturn
? copy.space.focusHud.returnPromptBreakDescription ? copy.space.focusHud.returnPromptBreakDescription
: copy.space.focusHud.returnPromptFocusDescription} : copy.space.focusHud.returnPromptFocusDescription}
</p> </p>
<div className="mt-4 space-y-2"> <div className="mt-5 space-y-2.5 border-t pt-4 border-white/8">
{isBreakReturn ? ( {isBreakReturn ? (
<> <>
<button <button
type="button" type="button"
onClick={onRest} onClick={onRest}
disabled={isBusy} disabled={isBusy}
className={cn(HUD_OPTION_ROW, HUD_OPTION_ROW_PRIMARY)} className={cn(HUD_OPTION_ROW, HUD_OPTION_ROW_BREAK)}
> >
<div> <div className="min-w-0">
<p className="text-[13px] font-semibold tracking-[0.01em] text-white/90"> <p className="text-[14px] font-semibold leading-[1.35] tracking-[-0.01em] text-white/92">
{copy.space.focusHud.returnPromptRest} {copy.space.focusHud.returnPromptRest}
</p> </p>
<p className="mt-1 text-[12px] text-white/48"> <p className="mt-1.5 max-w-[20rem] text-[12px] leading-[1.5] text-emerald-50/56">
{copy.space.focusHud.returnPromptRestHint} {copy.space.focusHud.returnPromptRestHint}
</p> </p>
</div> </div>
<span aria-hidden className={HUD_OPTION_CHEVRON}></span> <span aria-hidden className={cn(HUD_OPTION_CHEVRON, 'mt-1 text-emerald-100/34 group-hover:text-emerald-100/58')}></span>
</button> </button>
<button <button
type="button" type="button"
@@ -86,15 +95,15 @@ export const ReturnPrompt = ({
disabled={isBusy} disabled={isBusy}
className={HUD_OPTION_ROW} className={HUD_OPTION_ROW}
> >
<div> <div className="min-w-0">
<p className="text-[13px] font-medium tracking-[0.01em] text-white/78"> <p className="text-[14px] font-medium leading-[1.35] tracking-[-0.01em] text-white/82">
{copy.space.focusHud.returnPromptNext} {copy.space.focusHud.returnPromptNext}
</p> </p>
<p className="mt-1 text-[12px] text-white/44"> <p className="mt-1.5 max-w-[20rem] text-[12px] leading-[1.5] text-white/46">
{copy.space.focusHud.returnPromptNextHint} {copy.space.focusHud.returnPromptNextHint}
</p> </p>
</div> </div>
<span aria-hidden className={HUD_OPTION_CHEVRON}></span> <span aria-hidden className={cn(HUD_OPTION_CHEVRON, 'mt-1')}></span>
</button> </button>
<button <button
type="button" type="button"
@@ -102,15 +111,15 @@ export const ReturnPrompt = ({
disabled={isBusy} disabled={isBusy}
className={HUD_OPTION_ROW} className={HUD_OPTION_ROW}
> >
<div> <div className="min-w-0">
<p className="text-[13px] font-medium tracking-[0.01em] text-white/78"> <p className="text-[14px] font-medium leading-[1.35] tracking-[-0.01em] text-white/82">
{copy.space.focusHud.returnPromptRefocus} {copy.space.focusHud.returnPromptRefocus}
</p> </p>
<p className="mt-1 text-[12px] text-white/44"> <p className="mt-1.5 max-w-[20rem] text-[12px] leading-[1.5] text-white/46">
{copy.space.focusHud.returnPromptRefocusHint} {copy.space.focusHud.returnPromptRefocusHint}
</p> </p>
</div> </div>
<span aria-hidden className={HUD_OPTION_CHEVRON}></span> <span aria-hidden className={cn(HUD_OPTION_CHEVRON, 'mt-1')}></span>
</button> </button>
</> </>
) : ( ) : (
@@ -121,15 +130,15 @@ export const ReturnPrompt = ({
disabled={isBusy} disabled={isBusy}
className={cn(HUD_OPTION_ROW, HUD_OPTION_ROW_PRIMARY)} className={cn(HUD_OPTION_ROW, HUD_OPTION_ROW_PRIMARY)}
> >
<div> <div className="min-w-0">
<p className="text-[13px] font-semibold tracking-[0.01em] text-white/90"> <p className="text-[14px] font-semibold leading-[1.35] tracking-[-0.01em] text-white/92">
{copy.space.focusHud.returnPromptContinue} {copy.space.focusHud.returnPromptContinue}
</p> </p>
<p className="mt-1 text-[12px] text-white/48"> <p className="mt-1.5 max-w-[20rem] text-[12px] leading-[1.5] text-white/48">
{copy.space.focusHud.returnPromptContinueHint} {copy.space.focusHud.returnPromptContinueHint}
</p> </p>
</div> </div>
<span aria-hidden className={HUD_OPTION_CHEVRON}></span> <span aria-hidden className={cn(HUD_OPTION_CHEVRON, 'mt-1')}></span>
</button> </button>
<button <button
type="button" type="button"
@@ -137,15 +146,15 @@ export const ReturnPrompt = ({
disabled={isBusy} disabled={isBusy}
className={HUD_OPTION_ROW} className={HUD_OPTION_ROW}
> >
<div> <div className="min-w-0">
<p className="text-[13px] font-medium tracking-[0.01em] text-white/78"> <p className="text-[14px] font-medium leading-[1.35] tracking-[-0.01em] text-white/82">
{copy.space.focusHud.returnPromptRefocus} {copy.space.focusHud.returnPromptRefocus}
</p> </p>
<p className="mt-1 text-[12px] text-white/44"> <p className="mt-1.5 max-w-[20rem] text-[12px] leading-[1.5] text-white/46">
{copy.space.focusHud.returnPromptRefocusHint} {copy.space.focusHud.returnPromptRefocusHint}
</p> </p>
</div> </div>
<span aria-hidden className={HUD_OPTION_CHEVRON}></span> <span aria-hidden className={cn(HUD_OPTION_CHEVRON, 'mt-1')}></span>
</button> </button>
</> </>
)} )}

View File

@@ -1,11 +1,19 @@
export const HUD_TRAY_SHELL = export const HUD_TRAY_SHELL =
'pointer-events-auto relative mt-3 w-full overflow-hidden rounded-[22px] border border-white/10 bg-[#101318]/30 text-white shadow-[0_12px_28px_rgba(2,6,23,0.14)] backdrop-blur-[8px] backdrop-saturate-125'; 'pointer-events-auto relative mt-3 w-full overflow-hidden rounded-[22px] border border-white/10 bg-[#101318]/30 text-white shadow-[0_12px_28px_rgba(2,6,23,0.14)] backdrop-blur-[8px] backdrop-saturate-125';
export const HUD_TRAY_SHELL_BREAK =
'pointer-events-auto relative mt-3 w-full overflow-hidden rounded-[22px] border border-emerald-200/14 bg-[rgba(10,20,18,0.34)] text-white shadow-[0_12px_28px_rgba(2,6,23,0.12)] backdrop-blur-[8px] backdrop-saturate-125';
export const HUD_TRAY_LAYER = export const HUD_TRAY_LAYER =
'pointer-events-none absolute inset-0 rounded-[22px] bg-[linear-gradient(180deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.025)_24%,rgba(255,255,255,0.01)_100%)]'; 'pointer-events-none absolute inset-0 rounded-[22px] bg-[linear-gradient(180deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.025)_24%,rgba(255,255,255,0.01)_100%)]';
export const HUD_TRAY_LAYER_BREAK =
'pointer-events-none absolute inset-0 rounded-[22px] bg-[linear-gradient(180deg,rgba(110,231,183,0.10)_0%,rgba(255,255,255,0.025)_22%,rgba(255,255,255,0.01)_100%)]';
export const HUD_TRAY_HAIRLINE = 'pointer-events-none absolute inset-x-0 top-0 h-px bg-white/16'; export const HUD_TRAY_HAIRLINE = 'pointer-events-none absolute inset-x-0 top-0 h-px bg-white/16';
export const HUD_TRAY_HAIRLINE_BREAK = 'pointer-events-none absolute inset-x-0 top-0 h-px bg-emerald-200/18';
export const HUD_FIELD = export const HUD_FIELD =
'h-11 w-full rounded-[18px] border border-white/10 bg-black/14 px-3.5 text-[0.98rem] tracking-tight text-white placeholder:text-white/30 focus:border-white/20 focus:bg-black/20 focus:outline-none focus:ring-2 focus:ring-white/8'; 'h-11 w-full rounded-[18px] border border-white/10 bg-black/14 px-3.5 text-[0.98rem] tracking-tight text-white placeholder:text-white/30 focus:border-white/20 focus:bg-black/20 focus:outline-none focus:ring-2 focus:ring-white/8';
@@ -15,6 +23,9 @@ export const HUD_OPTION_ROW =
export const HUD_OPTION_ROW_PRIMARY = export const HUD_OPTION_ROW_PRIMARY =
'border-white/12 bg-black/14 hover:border-white/18 hover:bg-black/18'; 'border-white/12 bg-black/14 hover:border-white/18 hover:bg-black/18';
export const HUD_OPTION_ROW_BREAK =
'border-emerald-200/12 bg-[rgba(16,38,31,0.28)] hover:border-emerald-200/18 hover:bg-[rgba(16,38,31,0.38)]';
export const HUD_OPTION_CHEVRON = export const HUD_OPTION_CHEVRON =
'mt-0.5 shrink-0 text-[13px] text-white/28 transition-colors duration-200 group-hover:text-white/52'; 'mt-0.5 shrink-0 text-[13px] text-white/28 transition-colors duration-200 group-hover:text-white/52';
@@ -27,6 +38,12 @@ export const HUD_PAUSE_TITLE =
export const HUD_PAUSE_BODY = export const HUD_PAUSE_BODY =
'mt-2 max-w-[23rem] text-[13px] leading-[1.6] text-white/58 md:text-[13.5px]'; 'mt-2 max-w-[23rem] text-[13px] leading-[1.6] text-white/58 md:text-[13.5px]';
export const HUD_RETURN_TITLE =
'mt-2 max-w-[24rem] text-[1.08rem] font-medium leading-[1.36] tracking-[-0.02em] text-white/95 md:text-[1.14rem]';
export const HUD_RETURN_BODY =
'mt-2 max-w-[23rem] text-[13px] leading-[1.58] text-white/58';
export const HUD_TEXT_LINK = export const HUD_TEXT_LINK =
'text-[12px] font-medium tracking-[0.08em] text-white/62 underline decoration-white/16 underline-offset-4 transition-all duration-200 hover:text-white/84 hover:decoration-white/28 disabled:cursor-default disabled:text-white/26 disabled:no-underline'; 'text-[12px] font-medium tracking-[0.08em] text-white/62 underline decoration-white/16 underline-offset-4 transition-all duration-200 hover:text-white/84 hover:decoration-white/28 disabled:cursor-default disabled:text-white/26 disabled:no-underline';

View File

@@ -42,6 +42,7 @@ export const SpaceTimerHudWidget = ({
onResetClick, onResetClick,
}: SpaceTimerHudWidgetProps) => { }: SpaceTimerHudWidgetProps) => {
const { isBreatheMode, triggerRestart } = useRestart30s(); const { isBreatheMode, triggerRestart } = useRestart30s();
const isBreakPhase = hasActiveSession && sessionPhase === 'break';
const modeLabel = isBreatheMode const modeLabel = isBreatheMode
? RECOVERY_30S_MODE_LABEL ? RECOVERY_30S_MODE_LABEL
: !hasActiveSession : !hasActiveSession
@@ -63,15 +64,19 @@ export const SpaceTimerHudWidget = ({
className={cn( className={cn(
'relative z-10 flex h-[3.5rem] items-center justify-between gap-6 overflow-hidden rounded-full px-5 transition-colors', 'relative z-10 flex h-[3.5rem] items-center justify-between gap-6 overflow-hidden rounded-full px-5 transition-colors',
isImmersionMode isImmersionMode
? 'border border-white/10 bg-black/20 backdrop-blur-2xl shadow-[0_8px_32px_rgba(0,0,0,0.12)]' ? isBreakPhase
: 'border border-white/15 bg-black/30 backdrop-blur-2xl shadow-[0_8px_32px_rgba(0,0,0,0.16)]', ? 'border border-emerald-200/14 bg-[rgba(10,20,18,0.26)] backdrop-blur-2xl shadow-[0_8px_32px_rgba(0,0,0,0.10)]'
: 'border border-white/10 bg-black/20 backdrop-blur-2xl shadow-[0_8px_32px_rgba(0,0,0,0.12)]'
: isBreakPhase
? 'border border-emerald-200/16 bg-[rgba(10,20,18,0.32)] backdrop-blur-2xl shadow-[0_8px_32px_rgba(0,0,0,0.14)]'
: 'border border-white/15 bg-black/30 backdrop-blur-2xl shadow-[0_8px_32px_rgba(0,0,0,0.16)]',
)} )}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span <span
className={cn( className={cn(
'w-14 text-right text-[10px] font-bold uppercase tracking-[0.15em] opacity-80', 'w-14 text-right text-[10px] font-bold uppercase tracking-[0.15em] opacity-80',
sessionPhase === 'break' ? 'text-emerald-400' : 'text-brand-primary' sessionPhase === 'break' ? 'text-emerald-300' : 'text-brand-primary'
)} )}
> >
{modeLabel} {modeLabel}
@@ -114,13 +119,21 @@ export const SpaceTimerHudWidget = ({
className={cn( className={cn(
'inline-flex h-8 w-8 items-center justify-center rounded-full text-sm transition-all duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 active:scale-95 disabled:cursor-not-allowed disabled:opacity-30', 'inline-flex h-8 w-8 items-center justify-center rounded-full text-sm transition-all duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 active:scale-95 disabled:cursor-not-allowed disabled:opacity-30',
isImmersionMode isImmersionMode
? 'text-white/70 hover:bg-white/10 hover:text-white' ? isBreakPhase
: 'text-white/80 hover:bg-white/15 hover:text-white', ? 'text-white/74 hover:bg-emerald-100/10 hover:text-white'
: 'text-white/70 hover:bg-white/10 hover:text-white'
: isBreakPhase
? 'text-white/82 hover:bg-emerald-100/12 hover:text-white'
: 'text-white/80 hover:bg-white/15 hover:text-white',
isStartAction && isHighlighted isStartAction && isHighlighted
? 'bg-white/10 text-white shadow-sm' ? isBreakPhase
? 'bg-emerald-100/10 text-white shadow-sm'
: 'bg-white/10 text-white shadow-sm'
: '', : '',
isPauseAction && isHighlighted isPauseAction && isHighlighted
? 'bg-white/10 text-white shadow-sm' ? isBreakPhase
? 'bg-emerald-100/10 text-white shadow-sm'
: 'bg-white/10 text-white shadow-sm'
: '', : '',
)} )}
> >