1+ /**
2+ * ChronosCanvas — 时间生物容器
3+ *
4+ * R3F Canvas 包裹 ChronosSphere,按主题自动切换双风格(深潜/极光)。
5+ * 支持 full(沉浸全屏)与 compact(普通视图表盘尺寸)两种模式。
6+ * prefersReducedMotion 时回退 2D 静态形态(颜色随阶段变化,无动画)。
7+ *
8+ * @ai -context: Chronos 时间生物渲染容器,替代 TimerRing / ImmersiveRing 视觉层。
9+ */
10+ import { Canvas } from '@react-three/fiber' ;
11+ import { useReducedMotion } from 'framer-motion' ;
12+ import { useSceneTheme } from '@/lib/3d/hooks/useSceneTheme' ;
13+ import { CHRONOS_STYLES , CHRONOS_PHASES , toChronosPhase , type ChronosPhase } from './chronosStyles' ;
14+ import { ChronosSphere } from './ChronosSphere' ;
15+
16+ interface ChronosCanvasProps {
17+ /** full = 沉浸全屏;compact = 普通视图表盘尺寸 */
18+ mode ?: 'full' | 'compact' ;
19+ phase : 'work' | 'short_break' | 'long_break' ;
20+ isRunning : boolean ;
21+ remainingSeconds : number ;
22+ started : boolean ;
23+ /** 守护灵分心分数 0-100(可选,P2) */
24+ intensity ?: number ;
25+ /** 环境光亮度 0-1(可选,P2 暗环境自发光补偿) */
26+ ambientLight ?: number ;
27+ /** 完成绽放触发(P1) */
28+ bloom ?: boolean ;
29+ onTap ?: ( ) => void ;
30+ /** 中央显示内容(时间字符串) */
31+ timeStr : string ;
32+ /** 中央显示标签(如 专注中) */
33+ label : string ;
34+ }
35+
36+ /** 2D 静态回退形态(reduced motion / 极端低配) */
37+ function ChronosStatic ( { phase, timeStr, label } : {
38+ phase : ChronosPhase ;
39+ timeStr : string ;
40+ label : string ;
41+ } ) {
42+ const phaseColor = CHRONOS_PHASES [ phase ] . body ;
43+ return (
44+ < div className = "relative flex items-center justify-center w-full h-full" >
45+ { /* 静态光环 + 渐变球体 */ }
46+ < div
47+ className = "rounded-full"
48+ style = { {
49+ width : 'min(60%, 240px)' ,
50+ aspectRatio : '1' ,
51+ background : `radial-gradient(circle at 35% 30%, ${ phaseColor } 66 0%, #0C152433 55%, transparent 75%)` ,
52+ border : `2px solid ${ phaseColor } 55` ,
53+ boxShadow : `0 0 40px ${ phaseColor } 33` ,
54+ } }
55+ />
56+ < div className = "absolute inset-0 flex flex-col items-center justify-center pointer-events-none" >
57+ < span className = "font-timer font-light tracking-tight leading-none text-text-primary" style = { { fontSize : 'clamp(2rem, 8vmin, 4.5rem)' , fontVariantNumeric : 'tabular-nums' } } >
58+ { timeStr }
59+ </ span >
60+ < span className = "text-[11px] mt-2 font-medium tracking-[0.15em] uppercase text-text-tertiary" >
61+ { label }
62+ </ span >
63+ </ div >
64+ </ div >
65+ ) ;
66+ }
67+
68+ export function ChronosCanvas ( {
69+ mode = 'full' ,
70+ phase,
71+ isRunning,
72+ remainingSeconds,
73+ started,
74+ intensity,
75+ ambientLight,
76+ bloom,
77+ onTap,
78+ timeStr,
79+ label,
80+ } : ChronosCanvasProps ) {
81+ const theme = useSceneTheme ( ) ;
82+ const reduced = useReducedMotion ( ) ;
83+ const style = CHRONOS_STYLES [ theme ] ;
84+ const chronosPhase = toChronosPhase ( phase , isRunning , remainingSeconds , started ) ;
85+
86+ // 降级:减少动效偏好 → 2D 静态形态
87+ if ( reduced ) {
88+ return < ChronosStatic phase = { chronosPhase } timeStr = { timeStr } label = { label } /> ;
89+ }
90+
91+ return (
92+ < div
93+ className = { mode === 'full'
94+ ? 'absolute inset-0 flex items-center justify-center pointer-events-none'
95+ : 'relative w-[clamp(150px,34vmin,280px)] h-[clamp(150px,34vmin,280px)] pointer-events-none' }
96+ aria-label = { `${ label } ${ timeStr } ` }
97+ role = "timer"
98+ >
99+ < Canvas
100+ camera = { { position : [ 0 , 0 , 4.2 ] , fov : 45 } }
101+ dpr = { [ 1 , 1.5 ] }
102+ gl = { { antialias : true , alpha : true } }
103+ className = { mode === 'full' ? '!absolute inset-0' : '' }
104+ >
105+ < ambientLight intensity = { 0.6 } />
106+ < pointLight position = { [ 3 , 3 , 4 ] } intensity = { 1.2 } />
107+ < pointLight position = { [ - 3 , - 2 , - 3 ] } intensity = { 0.4 } color = { style . emissiveColor } />
108+ < ChronosSphere
109+ phase = { chronosPhase }
110+ style = { style }
111+ intensity = { intensity }
112+ ambientLight = { ambientLight }
113+ bloom = { bloom }
114+ onTap = { onTap }
115+ />
116+ </ Canvas >
117+ { /* 中央数字(HTML 层,与 3D 叠加) */ }
118+ < div className = "absolute inset-0 flex flex-col items-center justify-center pointer-events-none" >
119+ < span
120+ className = "font-timer font-light tracking-tight leading-none text-text-primary"
121+ style = { { fontSize : mode === 'full' ? 'clamp(3rem, 9vmin, 6rem)' : 'clamp(1.75rem, 7vmin, 3.5rem)' , fontVariantNumeric : 'tabular-nums' } }
122+ >
123+ { timeStr }
124+ </ span >
125+ < span className = "text-[11px] mt-2 font-medium tracking-[0.15em] uppercase text-text-tertiary" >
126+ { label }
127+ </ span >
128+ </ div >
129+ </ div >
130+ ) ;
131+ }
0 commit comments