Skip to content

Commit dd34528

Browse files
committed
feat(pomodoro): Chronos 液态金属形态与完整状态指示系统
- 严格遵循设计方案:球体由光子/微粒/液态金属三重构成 - 液态金属主体:高细分球体 + PMREM 环境映射(RoomEnvironment 本地生成零网络)+ 多层波状液态流动 + computeVertexNormals - 光子内层:球体内部发光光点(AdditiveBlending)随呼吸脉动 - 微粒外层:球壳微粒聚散 + 绽放爆发 - 深浅双风格差异化材质:deep-sea=深色液态水银(金属度0.95/粗糙度0.08/反射1.2/青绿辉光),aurora-dome=极光亮银(金属度0.82/粗糙度0.28/反射0.65/紫粉辉光) - 完整状态指示系统:6 阶段状态徽章(沉睡🌙/呼吸💨/专注🎯/短休☕/长休🌊/即将完成🔥)+ 交互提示文案,替代静态 label - 状态机映射:未开始→沉睡、暂停→呼吸、最后5分钟→即将完成、短休/长休独立状态,阶段切换 lerp 平滑 - 2D 降级回退同样显示状态徽章与提示 - 验证:tsc -b 0 错误、lint 干净、vitest 90/90
1 parent b153149 commit dd34528

5 files changed

Lines changed: 187 additions & 109 deletions

File tree

‎client/src/features/pomodoro/components/ImmersiveTimer.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ export default function ImmersiveTimer({
259259
onTap={handleChronosTap}
260260
onLongPress={handleChronosLongPress}
261261
timeStr={timeStr}
262-
label={label}
263262
/>
264263
) : (
265264
<ImmersiveRing progress={progress} timeStr={timeStr} label={label} breatheAnimation={breatheAnimation} />

‎client/src/features/pomodoro/components/chronos/ChronosCanvas.tsx‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import { Canvas } from '@react-three/fiber';
1111
import { useReducedMotion } from 'framer-motion';
1212
import { useSceneTheme } from '@/lib/3d/hooks/useSceneTheme';
13-
import { CHRONOS_STYLES, CHRONOS_PHASES, toChronosPhase, type ChronosPhase } from './chronosStyles';
13+
import { CHRONOS_STYLES, CHRONOS_PHASES, CHRONOS_STATE_LABELS, toChronosPhase, type ChronosPhase } from './chronosStyles';
1414
import { ChronosSphere } from './ChronosSphere';
1515

1616
interface ChronosCanvasProps {
@@ -31,17 +31,15 @@ interface ChronosCanvasProps {
3131
onLongPress?: () => void;
3232
/** 中央显示内容(时间字符串) */
3333
timeStr: string;
34-
/** 中央显示标签(如 专注中) */
35-
label: string;
3634
}
3735

3836
/** 2D 静态回退形态(reduced motion / 极端低配) */
39-
function ChronosStatic({ phase, timeStr, label }: {
37+
function ChronosStatic({ phase, timeStr }: {
4038
phase: ChronosPhase;
4139
timeStr: string;
42-
label: string;
4340
}) {
4441
const phaseColor = CHRONOS_PHASES[phase].body;
42+
const state = CHRONOS_STATE_LABELS[phase];
4543
return (
4644
<div className="relative flex items-center justify-center w-full h-full">
4745
{/* 静态光环 + 渐变球体 */}
@@ -59,9 +57,10 @@ function ChronosStatic({ phase, timeStr, label }: {
5957
<span className="font-timer font-light tracking-tight leading-none text-text-primary" style={{ fontSize: 'clamp(2rem, 8vmin, 4.5rem)', fontVariantNumeric: 'tabular-nums' }}>
6058
{timeStr}
6159
</span>
62-
<span className="text-[11px] mt-2 font-medium tracking-[0.15em] uppercase text-text-tertiary">
63-
{label}
60+
<span className="text-[11px] mt-2 font-medium tracking-[0.15em] uppercase" style={{ color: phaseColor }}>
61+
{state.icon} {state.name}
6462
</span>
63+
<span className="text-[10px] mt-1 text-text-tertiary/60">{state.hint}</span>
6564
</div>
6665
</div>
6766
);
@@ -79,24 +78,26 @@ export function ChronosCanvas({
7978
onTap,
8079
onLongPress,
8180
timeStr,
82-
label,
8381
}: ChronosCanvasProps) {
8482
const theme = useSceneTheme();
8583
const reduced = useReducedMotion();
8684
const style = CHRONOS_STYLES[theme];
8785
const chronosPhase = toChronosPhase(phase, isRunning, remainingSeconds, started);
86+
// 状态指示:完整状态机(沉睡/呼吸/专注/短休/长休/即将完成),优先于静态 label
87+
const state = CHRONOS_STATE_LABELS[chronosPhase];
88+
const stateColor = CHRONOS_PHASES[chronosPhase].body;
8889

8990
// 降级:减少动效偏好 → 2D 静态形态
9091
if (reduced) {
91-
return <ChronosStatic phase={chronosPhase} timeStr={timeStr} label={label} />;
92+
return <ChronosStatic phase={chronosPhase} timeStr={timeStr} />;
9293
}
9394

9495
return (
9596
<div
9697
className={mode === 'full'
9798
? 'absolute inset-0 flex items-center justify-center pointer-events-none'
9899
: 'relative w-[clamp(150px,34vmin,280px)] h-[clamp(150px,34vmin,280px)] pointer-events-none'}
99-
aria-label={`${label} ${timeStr}`}
100+
aria-label={`${state.name} ${timeStr}`}
100101
role="timer"
101102
>
102103
<Canvas
@@ -119,17 +120,21 @@ export function ChronosCanvas({
119120
onLongPress={onLongPress}
120121
/>
121122
</Canvas>
122-
{/* 中央数字(HTML 层,与 3D 叠加) */}
123+
{/* 中央状态区:时间 + 状态名 + 交互提示(HTML 层,与 3D 叠加) */}
123124
<div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
124125
<span
125126
className="font-timer font-light tracking-tight leading-none text-text-primary"
126127
style={{ fontSize: mode === 'full' ? 'clamp(3rem, 9vmin, 6rem)' : 'clamp(1.75rem, 7vmin, 3.5rem)', fontVariantNumeric: 'tabular-nums' }}
127128
>
128129
{timeStr}
129130
</span>
130-
<span className="text-[11px] mt-2 font-medium tracking-[0.15em] uppercase text-text-tertiary">
131-
{label}
131+
<span
132+
className="text-[11px] mt-2 font-medium tracking-[0.15em] uppercase transition-colors duration-500"
133+
style={{ color: stateColor }}
134+
>
135+
{state.icon} {state.name}
132136
</span>
137+
<span className="text-[10px] mt-1 text-text-tertiary/60">{state.hint}</span>
133138
</div>
134139
</div>
135140
);

0 commit comments

Comments
 (0)