From 6b25a18d5a7868ae27f07cd3a9e28c67002fe7b1 Mon Sep 17 00:00:00 2001 From: corpi Date: Mon, 16 Mar 2026 13:49:01 +0900 Subject: [PATCH] =?UTF-8?q?feat(stats):=20observatory=20tone=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20review=20=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/90_current_state.md | 4 +- .../current/14_weekly_review_reframe_spec.md | 2 + docs/session_brief.md | 4 +- .../stats-overview/ui/StatsOverviewWidget.tsx | 198 +++++++++++------- 4 files changed, 131 insertions(+), 77 deletions(-) diff --git a/docs/90_current_state.md b/docs/90_current_state.md index 8fb00c6..79661f0 100644 --- a/docs/90_current_state.md +++ b/docs/90_current_state.md @@ -131,8 +131,8 @@ Last Updated: 2026-03-16 - 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`과 같은 제품군으로 보이게 정리했다 + - 중앙 hero summary, snapshot signal board, start/recovery/completion observatory panel, carry-forward closure stage를 같은 glass family로 통일했다 + - carry-forward ritual에 맞는 atmosphere 배경을 얇게 투영하되, `/app`의 entry ritual을 복제하지 않고 통계 화면 고유의 observatory 톤으로 분리했다 - `/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 fa7bcc7..3bddb03 100644 --- a/docs/screens/stats/current/14_weekly_review_reframe_spec.md +++ b/docs/screens/stats/current/14_weekly_review_reframe_spec.md @@ -154,6 +154,8 @@ Weekly Review는 `/stats` 안에서 아래 5개 구역으로 재구성한다. - 상단은 quiet accessory만 두고, hero summary가 화면의 중심이 된다 - snapshot metrics는 작은 glass tile rail로, start/recovery/completion은 같은 family의 review panel로 통일한다 - 마지막 carry-forward CTA는 별도 버튼 묶음이 아니라 다음 세션 entry로 자연스럽게 이어지는 closure stage여야 한다 +- `/app`의 entry ritual과는 다르게, `/stats`는 `observatory / signal board` 감각으로 읽혀야 한다 + - 즉 같은 glass family는 유지하되, 입력 ritual을 닮은 중앙 구조를 복제하지 않는다 ### Section A. Weekly Snapshot diff --git a/docs/session_brief.md b/docs/session_brief.md index 53c56ce..06c7fb8 100644 --- a/docs/session_brief.md +++ b/docs/session_brief.md @@ -108,8 +108,8 @@ Last Updated: 2026-03-16 - 기존 `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`의 위계와 재질을 다시 맞췄다. + - dark atmosphere 배경 위에 중앙 hero summary, snapshot signal board, review panel, carry-forward closure stage를 같은 glass family로 재구성했다. + - `/app`의 premium immersive tone과 같은 제품군은 유지하되, 입력 ritual을 닮지 않는 stats 고유의 observatory 톤으로 분리했다. - `/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 fbf7e99..8cb0518 100644 --- a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx +++ b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx @@ -9,10 +9,10 @@ import { useFocusStats } from '@/features/stats'; import { copy } from '@/shared/i18n'; import { cn } from '@/shared/lib/cn'; -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 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; @@ -24,7 +24,7 @@ const reviewStageSceneByPreset = (presetId: string) => { return getSceneById(DEFAULT_STATS_SCENE_ID) ?? SCENE_THEMES[0]; }; -const StatusAccessory = ({ +const AccessoryPill = ({ label, subtle = false, }: { @@ -36,7 +36,7 @@ const StatusAccessory = ({ className={cn( 'inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-[10px] font-semibold uppercase tracking-[0.2em] backdrop-blur-md', subtle - ? 'border-white/8 bg-white/[0.05] text-white/44' + ? 'border-white/8 bg-white/[0.05] text-white/46' : 'border-white/12 bg-white/[0.07] text-white/64', )} > @@ -46,7 +46,7 @@ const StatusAccessory = ({ ); }; -const SnapshotMetric = ({ +const SnapshotCell = ({ label, value, hint, @@ -56,15 +56,15 @@ const SnapshotMetric = ({ hint: string; }) => { return ( -
+

{label}

-

{value}

+

{value}

{hint}

); }; -const ReviewSection = ({ +const InsightBoard = ({ title, summary, metrics, @@ -79,38 +79,75 @@ const ReviewSection = ({ note?: string; accentClass: string; }) => { + const heroMetric = metrics[0]; + const supportMetrics = metrics.slice(1); + return ( -
-
+
+
+

- Weekly Review + Review Signal

-

+

{title}

{summary}

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

+ {heroMetric.label} +

+
+

+ {heroMetric.value} +

+
+

+ {heroMetric.hint} +

+ + ) : ( +

아직 보여줄 지표가 충분하지 않아요.

+ )}
- ) : null} + +
+ {supportMetrics.length > 0 ? ( + supportMetrics.map((metric) => ( +
+
+
+

+ {metric.label} +

+

+ {metric.value} +

+
+
+

{metric.hint}

+
+ )) + ) : ( +
+

보조 지표는 데이터가 쌓이면 함께 보입니다.

+
+ )} +
+
{note ? ( -

+

{note}

) : null} @@ -140,8 +177,9 @@ export const StatsOverviewWidget = () => { style={getSceneStageBackgroundStyle(activeScene, sceneAssetMap?.[activeScene.id])} />
-
-
+
+
+
@@ -174,69 +212,83 @@ export const StatsOverviewWidget = () => {
-
-
-
- - +
+
+
+
+ +
+
+ + +
+ +
+

+ 집중 리듬 요약 +

+

+ {review.snapshotSummary} +

+

+ {syncLabel} +

+
+
-
-

- {review.snapshotTitle} -

-

- {review.snapshotSummary} -

-

- {syncLabel} -

+
+
+
+

+ Snapshot Signals +

+
+ {review.snapshotMetrics.map((metric) => ( + + ))} +
+
-
- -
- {review.snapshotMetrics.map((metric) => ( - - ))}
-
- + -
-
- + -
-
+
+
@@ -247,25 +299,25 @@ export const StatsOverviewWidget = () => {

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

-

+

{review.carryForward.keepDoing}

- {isPro ? : null} + {isPro ? : null}
-
+

다음 주에 바꿔볼 것

-

+

{review.carryForward.tryNext}

-
+

Atmosphere

@@ -273,15 +325,15 @@ export const StatsOverviewWidget = () => { {review.carryForward.presetLabel}

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

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