diff --git a/docs/90_current_state.md b/docs/90_current_state.md index 85b0ab0..8fb00c6 100644 --- a/docs/90_current_state.md +++ b/docs/90_current_state.md @@ -129,6 +129,10 @@ Last Updated: 2026-03-16 - `snapshot + start quality + recovery quality + completion quality + carry forward` 구조를 반영 - 기존 `focus-summary` 응답을 주간 review view model로 변환해서 사용 - recovery는 서버의 `pause 뒤 복귀` 집계를 사용하고, `자리 비움 뒤 복귀`만 limited note로 남긴다 +- `/stats` immersive review stage polish: + - `/stats`를 밝은 대시보드 카드 반복 화면에서 dark immersive review stage로 재구성했다 + - 중앙 hero summary, snapshot metric rail, start/recovery/completion panel, carry-forward closure stage를 같은 glass family로 통일했다 + - carry-forward ritual에 맞는 atmosphere 배경을 얇게 투영해 `/app`과 같은 제품군으로 보이게 정리했다 - `/app -> /stats` primary entry의 1차 연결: - current session이 없고 최근 7일 데이터가 충분할 때 `/app`의 quiet secondary review dock에서 `Weekly Review` entry를 노출한다 - current session이 있으면 `/app` 자체가 `/space`로 이동하므로, `/app` review entry는 no-session entry shell 안에서만 다룬다 diff --git a/docs/screens/stats/current/14_weekly_review_reframe_spec.md b/docs/screens/stats/current/14_weekly_review_reframe_spec.md index 919f831..fa7bcc7 100644 --- a/docs/screens/stats/current/14_weekly_review_reframe_spec.md +++ b/docs/screens/stats/current/14_weekly_review_reframe_spec.md @@ -147,6 +147,14 @@ VibeRoom의 review는 아래처럼 포지셔닝해야 한다. Weekly Review는 `/stats` 안에서 아래 5개 구역으로 재구성한다. +### 현재 visual shell 원칙 + +- `/stats`는 밝은 factual dashboard가 아니라 dark immersive review stage로 간다 +- 배경은 carry-forward ritual과 연결되는 atmosphere를 얇게 투영한다 +- 상단은 quiet accessory만 두고, hero summary가 화면의 중심이 된다 +- snapshot metrics는 작은 glass tile rail로, start/recovery/completion은 같은 family의 review panel로 통일한다 +- 마지막 carry-forward CTA는 별도 버튼 묶음이 아니라 다음 세션 entry로 자연스럽게 이어지는 closure stage여야 한다 + ### Section A. Weekly Snapshot 목적: diff --git a/docs/session_brief.md b/docs/session_brief.md index ab3e7ac..53c56ce 100644 --- a/docs/session_brief.md +++ b/docs/session_brief.md @@ -107,6 +107,9 @@ Last Updated: 2026-03-16 - hero snapshot, start quality, recovery quality, completion quality, carry forward 구조를 사용한다. - 기존 `focus-summary` 응답은 review view model로 변환해서 쓴다. - recovery는 서버의 `pause 뒤 복귀` 집계를 사용하고, `away recovery`만 limited state로 남긴다. +- `/stats` immersive review stage polish를 반영했다. + - dark atmosphere 배경 위에 중앙 hero summary, snapshot metric rail, review panel, carry-forward closure stage를 같은 glass family로 재구성했다. + - `/app`의 premium immersive tone과 같은 제품군으로 읽히도록 `/stats`의 위계와 재질을 다시 맞췄다. - `/app`에서 `/stats`로 들어가는 primary path 1차가 생겼다. - current session이 없을 때는 quiet review dock에서 `/stats`로 진입할 수 있다. - review entry는 main start CTA보다 항상 낮은 강조를 유지한다. diff --git a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx index dc692ce..fbf7e99 100644 --- a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx +++ b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx @@ -1,12 +1,52 @@ 'use client'; import Link from 'next/link'; +import { useMemo } from 'react'; +import { useMediaCatalog, getSceneStageBackgroundStyle } from '@/entities/media'; import { usePlanTier } from '@/entities/plan'; +import { getSceneById, SCENE_THEMES } from '@/entities/scene'; import { useFocusStats } from '@/features/stats'; import { copy } from '@/shared/i18n'; import { cn } from '@/shared/lib/cn'; -const ReviewMetric = ({ +const glassPanelClass = + '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 metricTileClass = + 'rounded-[1.45rem] 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) => { + if (presetId.startsWith('forest')) { + return getSceneById('forest') ?? SCENE_THEMES[0]; + } + + return getSceneById(DEFAULT_STATS_SCENE_ID) ?? SCENE_THEMES[0]; +}; + +const StatusAccessory = ({ + label, + subtle = false, +}: { + label: string; + subtle?: boolean; +}) => { + return ( + + + {label} + + ); +}; + +const SnapshotMetric = ({ label, value, hint, @@ -16,10 +56,10 @@ const ReviewMetric = ({ hint: string; }) => { return ( -
-

{label}

-

{value}

-

{hint}

+
+

{label}

+

{value}

+

{hint}

); }; @@ -30,35 +70,36 @@ const ReviewSection = ({ metrics, availability, note, - toneClass, + accentClass, }: { title: string; summary: string; metrics: Array<{ id: string; label: string; value: string; hint: string }>; availability: 'ready' | 'limited'; note?: string; - toneClass: string; + accentClass: string; }) => { return ( -
-
-
+
+
+
-
-

{title}

-

{summary}

+
+

+ Weekly Review +

+

+ {title} +

+

{summary}

- {availability === 'limited' ? ( - - Limited - - ) : null} + {availability === 'limited' ? : null}
{metrics.length > 0 ? (
{metrics.map((metric) => ( - +

{note}

) : null} @@ -81,143 +122,179 @@ const ReviewSection = ({ export const StatsOverviewWidget = () => { const { stats } = copy; const { isPro } = usePlanTier(); + const { sceneAssetMap } = useMediaCatalog(); const { review, isLoading, error, source, refetch } = useFocusStats(); + + const activeScene = useMemo( + () => reviewStageSceneByPreset(review.carryForward.presetId), + [review.carryForward.presetId], + ); + const sourceLabel = source === 'api' ? stats.sourceApi : stats.sourceMock; + const syncLabel = error ? error : isLoading ? stats.loading : stats.synced; const carryForwardCtaLabel = isPro ? stats.reviewCarryCtaPro : review.carryForward.ctaLabel; return ( -
-
-
-
-

- {review.periodLabel} -

-

+
+
+
+
+
+ +
+
+

+ Weekly Review +

+ {isPro ? ( + + PRO + + ) : null} +
+ +
+ + + {copy.common.hub} + +
+
+ +
+
+
+ + +
+ +
+

{review.snapshotTitle} +

+

+ {review.snapshotSummary}

+

+ {syncLabel} +

+
-
- - - {copy.common.hub} - -
-

+
+ {review.snapshotMetrics.map((metric) => ( + + ))} +
-
-
-
-
-

- {source === 'api' ? stats.sourceApi : stats.sourceMock} -

-

- {review.snapshotSummary} -

-

- {error ? error : isLoading ? stats.loading : stats.synced} -

+
+ + + +
+ +
+ + +
+
+ +
+
+
+

+ Carry Forward +

+

+ 다음 세션에 그대로 가져갈 흐름 +

+

+ {review.carryForward.keepDoing} +

+
+ + {isPro ? : null}
-
- {review.snapshotMetrics.map((metric) => ( - - ))} -
-
-
- -
- - - -
- -
- - -
-
-
-

- {review.periodLabel} -

-

- {stats.reviewCarryKeepTitle} -

-

- {review.carryForward.keepDoing} -

- -
-

- {stats.reviewCarryTryTitle} -

-

+

+
+

+ 다음 주에 바꿔볼 것 +

+

{review.carryForward.tryNext}

- {isPro ? ( -
-

- {stats.reviewCarryPresetLabel} -

-

- {review.carryForward.presetLabel} -

-
- ) : null} +
+

+ Atmosphere +

+

+ {review.carryForward.presetLabel} +

+

+ 지금 가장 무리 없이 다시 들어갈 수 있는 기본 흐름입니다. +

+
+
+ +
+

+ review는 지난 시간을 요약하는 화면이 아니라, 다음 세션을 더 가볍게 열기 위한 + 출발점입니다. +

{carryForwardCtaLabel}
-
-
-
-
+
+
+
+
); };