|
| 1 | +/** |
| 2 | + * TideBreath — 潮汐节律(宪法第十条 · 叙事层 C:熵的呼吸) |
| 3 | + * TideBreath — tidal rhythm (constitution §10 · layer C) |
| 4 | + * |
| 5 | + * @ai-context: 实时层的投递机制:番茄钟运行=涨潮(世界收拢变深—— |
| 6 | + * 荧光点光增亮脉动 + 底部潮线升起),停止/休息=退潮(光照回落、潮线沉降)。 |
| 7 | + * 呼吸节律与 pomodoro isRunning 联动,无新增数据依赖。 |
| 8 | + * reduced-motion:跳过脉动,只做强度缓动。 |
| 9 | + * |
| 10 | + * @ai-context: Focus-running = flood tide (light swells, tide line rises); |
| 11 | + * idle = ebb. Pulse skipped under prefers-reduced-motion. |
| 12 | + */ |
| 13 | +import { useMemo, useRef } from 'react'; |
| 14 | +import * as THREE from 'three'; |
| 15 | +import { useFrame } from '@react-three/fiber'; |
| 16 | +import { usePomodoroStore } from '@/features/pomodoro/store/usePomodoroStore'; |
| 17 | + |
| 18 | +/** 涨潮/退潮光照强度目标 / Flood & ebb light intensity targets */ |
| 19 | +const FLOOD_INTENSITY = 0.85; |
| 20 | +const EBB_INTENSITY = 0.5; |
| 21 | + |
| 22 | +export function TideBreath() { |
| 23 | + const isRunning = usePomodoroStore((s) => s.isRunning); |
| 24 | + const lightRef = useRef<THREE.PointLight>(null); |
| 25 | + const tideRef = useRef<THREE.Mesh>(null); |
| 26 | + const elapsed = useRef(0); |
| 27 | + const reduced = useMemo( |
| 28 | + () => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches, |
| 29 | + [], |
| 30 | + ); |
| 31 | + |
| 32 | + useFrame((_, delta) => { |
| 33 | + const safeDelta = Math.min(delta, 0.1); |
| 34 | + elapsed.current += safeDelta; |
| 35 | + |
| 36 | + // 涨潮脉动:呼吸式的强度起伏(reduced-motion 关闭) |
| 37 | + const pulse = isRunning && !reduced ? Math.sin(elapsed.current * 0.9) * 0.12 : 0; |
| 38 | + const target = (isRunning ? FLOOD_INTENSITY : EBB_INTENSITY) + pulse; |
| 39 | + if (lightRef.current) { |
| 40 | + lightRef.current.intensity = THREE.MathUtils.lerp( |
| 41 | + lightRef.current.intensity, target, safeDelta * 2, |
| 42 | + ); |
| 43 | + } |
| 44 | + |
| 45 | + // 潮线:涨潮升起且波动更明显,退潮沉降趋平 |
| 46 | + if (tideRef.current) { |
| 47 | + const mat = tideRef.current.material as THREE.MeshBasicMaterial; |
| 48 | + mat.opacity = THREE.MathUtils.lerp(mat.opacity, isRunning ? 0.16 : 0.05, safeDelta * 2); |
| 49 | + const swell = isRunning && !reduced ? Math.sin(elapsed.current * 0.5) * 0.12 : 0; |
| 50 | + tideRef.current.position.y = -3.5 + (isRunning ? 0.25 : 0) + swell; |
| 51 | + } |
| 52 | + }); |
| 53 | + |
| 54 | + return ( |
| 55 | + <group> |
| 56 | + {/* 潮汐光:与场景主光源(#00BFFF)同族但偏赛博青,涨潮时世界"变深变亮" */} |
| 57 | + <pointLight ref={lightRef} position={[0, 4, 2]} intensity={EBB_INTENSITY} color="#22D3EE" distance={40} /> |
| 58 | + {/* 潮线:海床上的扁平光环,涨落可见 */} |
| 59 | + <mesh ref={tideRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, -3.5, 0]}> |
| 60 | + <ringGeometry args={[3.4, 9, 48]} /> |
| 61 | + <meshBasicMaterial color="#22D3EE" transparent opacity={0.05} side={THREE.DoubleSide} depthWrite={false} /> |
| 62 | + </mesh> |
| 63 | + </group> |
| 64 | + ); |
| 65 | +} |
0 commit comments