diff --git a/docs/foundation/08_premium_uiux_guideline.md b/docs/foundation/08_premium_uiux_guideline.md
index 94e987b..9cdaf01 100644
--- a/docs/foundation/08_premium_uiux_guideline.md
+++ b/docs/foundation/08_premium_uiux_guideline.md
@@ -21,6 +21,11 @@
- **필수 속성:** `backdrop-blur-xl` 또는 `backdrop-blur-2xl`을 반드시 사용합니다.
- **테두리와 명암:** 투박한 solid border 대신, `border-white/5` ~ `border-white/10` 수준의 매우 얇고 투명한 테두리를 사용합니다. 깊이감을 위해 다중 그림자(`shadow-2xl` 등)와 미세한 그라데이션(`bg-[linear-gradient(...)]`)을 조합합니다.
+### 1-4. 무대와 대기실의 분리 (Stage vs. Lobby Separation)
+- **세션(Space) 외의 화면(`/app`, `/stats`, `/settings` 등)은 실제 집중 배경(Atmosphere)을 그대로 띄우지 않습니다.**
+- 대기실(Lobby) 역할을 하는 화면에 너무 구체적인 풍경이나 영상이 띄워져 있으면, 사용자가 이미 집중 세션에 들어왔다고 착각하거나 인지적 피로감을 느낄 수 있습니다.
+- **해결책:** 대기실 화면의 배경은 집중할 때 볼 풍경을 블러 처리(`blur-3xl`)하거나, 매우 어둡고 깊이 있는 추상적 그라데이션(예: `bg-black`에 은은한 틴트)으로 처리하여 **"아직 무대에 오르기 전(또는 내려온 후)"** 이라는 심리적 분리감을 명확히 주어야 합니다.
+
---
## 2. 레이아웃 & 컴포넌트 배치 원칙 (Layout & Placement)
diff --git a/docs/screens/app/current/19_app_atmosphere_entry_spec.md b/docs/screens/app/current/19_app_atmosphere_entry_spec.md
index 2333405..f52ac3b 100644
--- a/docs/screens/app/current/19_app_atmosphere_entry_spec.md
+++ b/docs/screens/app/current/19_app_atmosphere_entry_spec.md
@@ -210,7 +210,7 @@ Last Updated: 2026-03-16
- 필수
- 숫자만
- 권장 범위:
- - 최소 10분
+ - 최소 5분
- 최대 180분
- helper:
- `이 목표를 끝내는 데 걸릴 것 같은 시간을 적어요.`
diff --git a/src/widgets/focus-dashboard/model/atmosphereEntry.ts b/src/widgets/focus-dashboard/model/atmosphereEntry.ts
index 82166dc..083fc61 100644
--- a/src/widgets/focus-dashboard/model/atmosphereEntry.ts
+++ b/src/widgets/focus-dashboard/model/atmosphereEntry.ts
@@ -158,11 +158,15 @@ export const parseDurationMinutes = (value: string) => {
return null;
}
- return Math.max(10, Math.min(180, parsed));
+ if (parsed < 5) {
+ return null;
+ }
+
+ return Math.min(180, parsed);
};
export const sanitizeDurationDraft = (value: string) => {
- const digitsOnly = value.replace(/[^\d]/g, '');
+ const digitsOnly = value.replace(/[^\d]/g, '').slice(0, 3);
if (!digitsOnly) {
return '';
}
@@ -172,7 +176,7 @@ export const sanitizeDurationDraft = (value: string) => {
return '';
}
- return String(Math.max(10, Math.min(180, parsed)));
+ return String(Math.min(180, parsed));
};
export const getTimerPresetMetaById = (timerPresetId: string) => {
diff --git a/src/widgets/focus-dashboard/ui/AppAtmosphereEntryShell.tsx b/src/widgets/focus-dashboard/ui/AppAtmosphereEntryShell.tsx
index 8c4daca..7a5c7b9 100644
--- a/src/widgets/focus-dashboard/ui/AppAtmosphereEntryShell.tsx
+++ b/src/widgets/focus-dashboard/ui/AppAtmosphereEntryShell.tsx
@@ -57,13 +57,13 @@ export const AppAtmosphereEntryShell = ({
{/* Main Focus Entry Ritual */}
{/* Inline Accessories (No overlap guarantee) */}
-
+
{errorAccessory}
{!errorAccessory && topAccessory}
-
-
+
-
-
-
-
- {durationSuggestions.map((minutes) => {
- const isSelected = durationDraft === String(minutes);
- return (
-
- );
- })}
-
-
-
- onDurationChange(event.target.value)}
- onKeyDown={(event) => {
- if (event.key === 'Enter' && !event.shiftKey) {
- event.preventDefault();
- onStartSession();
- }
- }}
- inputMode="numeric"
- placeholder="Custom"
- className="w-16 bg-transparent text-right text-lg font-medium text-white outline-none placeholder:text-white/30"
- />
- min
-
+
+
+
+ {/* Primary Action: Massive Custom Timer Input */}
+
+ onDurationChange(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key === 'Enter' && !event.shiftKey) {
+ event.preventDefault();
+ onStartSession();
+ }
+ }}
+ inputMode="numeric"
+ placeholder="0"
+ className="w-24 bg-transparent text-center text-6xl font-light tracking-tighter text-white outline-none placeholder:text-white/10 transition-all focus:w-32 md:text-7xl"
+ />
+
+ min
+
-
{durationHelper}
+
+ {/* Secondary Action: Subtle Quick Select Pills */}
+
+ {durationSuggestions.map((minutes) => {
+ const isSelected = durationDraft === String(minutes);
+ return (
+
+ );
+ })}
+
+
+
{durationHelper}
diff --git a/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx b/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx
index a4cf99c..3939fab 100644
--- a/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx
+++ b/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx
@@ -140,6 +140,15 @@ export const FocusDashboardWidget = () => {
() => getAtmosphereOptionById(selectedAtmosphereId),
[selectedAtmosphereId],
);
+ const rawDurationValue = useMemo(() => {
+ const digitsOnly = durationDraft.replace(/[^\d]/g, '');
+ if (!digitsOnly) {
+ return null;
+ }
+
+ const parsed = Number(digitsOnly);
+ return Number.isFinite(parsed) ? parsed : null;
+ }, [durationDraft]);
const parsedDurationMinutes = parseDurationMinutes(durationDraft);
const resolvedTimerPreset = useMemo(() => {
const targetMinutes =
@@ -178,9 +187,11 @@ export const FocusDashboardWidget = () => {
isPro && reviewEntryPresetConfig ? `추천 ritual · ${reviewEntryPresetConfig.label}` : null;
const reviewTeaserTitle = isPro ? entryCopy.reviewTitlePro : entryCopy.reviewTitle;
const durationHelper =
- parsedDurationMinutes === null
- ? 'Please enter the estimated duration in minutes.'
- : entryCopy.durationHelper;
+ rawDurationValue !== null && rawDurationValue < 5
+ ? 'Please enter at least 5 minutes.'
+ : parsedDurationMinutes === null
+ ? 'Please enter the estimated duration in minutes.'
+ : entryCopy.durationHelper;
const hasCurrentSession = Boolean(currentSession);
useEffect(() => {
@@ -301,13 +312,13 @@ export const FocusDashboardWidget = () => {
{/* Immersive Overlay Gradients */}
-
-
+
+
{/* Header */}
diff --git a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx
index 8cb0518..0b17001 100644
--- a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx
+++ b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx
@@ -9,11 +9,6 @@ import { useFocusStats } from '@/features/stats';
import { copy } from '@/shared/i18n';
import { cn } from '@/shared/lib/cn';
-const panelClass =
- 'relative overflow-hidden rounded-[2rem] border border-white/10 bg-[linear-gradient(160deg,rgba(8,12,18,0.46)_0%,rgba(8,12,18,0.2)_58%,rgba(8,12,18,0.52)_100%)] shadow-[0_24px_80px_rgba(3,7,18,0.28)] backdrop-blur-[24px]';
-const innerTileClass =
- 'rounded-[1.4rem] border border-white/10 bg-[linear-gradient(145deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.04)_100%)] px-4 py-4 backdrop-blur-xl';
-
const DEFAULT_STATS_SCENE_ID = getSceneById('forest')?.id ?? SCENE_THEMES[0].id;
const reviewStageSceneByPreset = (presetId: string) => {
@@ -24,138 +19,6 @@ const reviewStageSceneByPreset = (presetId: string) => {
return getSceneById(DEFAULT_STATS_SCENE_ID) ?? SCENE_THEMES[0];
};
-const AccessoryPill = ({
- label,
- subtle = false,
-}: {
- label: string;
- subtle?: boolean;
-}) => {
- return (
-
-
- {label}
-
- );
-};
-
-const SnapshotCell = ({
- label,
- value,
- hint,
-}: {
- label: string;
- value: string;
- hint: string;
-}) => {
- return (
-
-
{label}
-
{value}
-
{hint}
-
- );
-};
-
-const InsightBoard = ({
- title,
- summary,
- metrics,
- availability,
- note,
- accentClass,
-}: {
- title: string;
- summary: string;
- metrics: Array<{ id: string; label: string; value: string; hint: string }>;
- availability: 'ready' | 'limited';
- note?: string;
- accentClass: string;
-}) => {
- const heroMetric = metrics[0];
- const supportMetrics = metrics.slice(1);
-
- return (
-
-
-
-
-
-
-
- Review Signal
-
-
- {title}
-
-
{summary}
-
- {availability === 'limited' ?
: null}
-
-
-
-
- {heroMetric ? (
- <>
-
- {heroMetric.label}
-
-
-
- {heroMetric.value}
-
-
-
- {heroMetric.hint}
-
- >
- ) : (
-
아직 보여줄 지표가 충분하지 않아요.
- )}
-
-
-
- {supportMetrics.length > 0 ? (
- supportMetrics.map((metric) => (
-
-
-
-
- {metric.label}
-
-
- {metric.value}
-
-
-
-
{metric.hint}
-
- ))
- ) : (
-
-
보조 지표는 데이터가 쌓이면 함께 보입니다.
-
- )}
-
-
-
- {note ? (
-
- {note}
-
- ) : null}
-
-
- );
-};
-
export const StatsOverviewWidget = () => {
const { stats } = copy;
const { isPro } = usePlanTier();
@@ -171,181 +34,214 @@ export const StatsOverviewWidget = () => {
const carryForwardCtaLabel = isPro ? stats.reviewCarryCtaPro : review.carryForward.ctaLabel;
return (
-
+
+ {/* Immersive Background */}
-
-
-
-
+ {/* Premium Cinematic Overlays */}
+
+
+
-