import React, { useState, useEffect, useRef, useMemo } from 'react'; const VIEW_WIDTH = 1000; const VIEW_HEIGHT = 600; const CYCLE_DURATION = 24000; // 24 seconds for a full 24h cycle const FPS = 60; // Utility for color interpolation const interpolateColor = (color1, color2, factor) => { const result = color1.slice(1).match(/.{2}/g).map((hex, i) => { return Math.round(parseInt(hex, 16) + factor * (parseInt(color2.slice(1).match(/.{2}/g)[i], 16) - parseInt(hex, 16))); }); return `#${result.map(val => val.toString(16).padStart(2, '0')).join('')}`; }; // Color Palettes const COLORS = { night: '#0f172a', // Deep Slate dawn: '#2e1065', // Deep Violet sunrise: '#f97316', // Orange day: '#0ea5e9', // Sky Blue sunset: '#be185d', // Pink dusk: '#1e1b4b', // Indigo }; export const DayNightCycleView = () => { const [time, setTime] = useState(6); // Start at 6:00 AM const lastTimeRef = useRef(Date.now()); const rafRef = useRef(); // Generate Stars once const stars = useMemo(() => { return Array.from({ length: 100 }, () => ({ x: Math.random() * VIEW_WIDTH, y: Math.random() * (VIEW_HEIGHT * 0.7), r: Math.random() * 1.5 + 0.5, blinkSpeed: Math.random() * 0.05 + 0.01, phase: Math.random() * Math.PI * 2 })); }, []); // Generate Mountains once const mountains = useMemo(() => { const points1 = []; const points2 = []; let y1 = 450; let y2 = 500; for (let x = 0; x <= VIEW_WIDTH; x += 50) { y1 += (Math.random() - 0.5) * 80; y2 += (Math.random() - 0.5) * 50; y1 = Math.max(300, Math.min(550, y1)); y2 = Math.max(400, Math.min(600, y2)); points1.push(`${x},${y1}`); points2.push(`${x},${y2}`); } return { back: `0,600 ${points1.join(' ')} ${VIEW_WIDTH},600`, front: `0,600 ${points2.join(' ')} ${VIEW_WIDTH},600` }; }, []); // Animation Loop useEffect(() => { const animate = () => { const now = Date.now(); const delta = now - lastTimeRef.current; // Update time (0 - 24 hours) // 24 seconds real time = 24 hours sim time // So 1000ms real = 1 hour sim const timeIncrement = (delta / 1000); setTime(prev => { let newTime = prev + timeIncrement; if (newTime >= 24) newTime -= 24; return newTime; }); lastTimeRef.current = now; rafRef.current = requestAnimationFrame(animate); }; rafRef.current = requestAnimationFrame(animate); return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }; }, []); // Derived Values based on 'time' const isDay = time > 6 && time < 18; const sunAngle = ((time - 6) / 24) * 360; // 6am = 0deg, 6pm = 180deg (roughly) // Refined Orbit Logic: 0deg is top (noon), 180 is bottom (midnight) // 12:00 -> 0 deg (Top) // 18:00 -> 90 deg (Right) // 00:00 -> 180 deg (Bottom) // 06:00 -> 270 deg (Left) const rotationAngle = (time / 24) * 360 + 180; // Calculate Sky Gradient const getSkyColors = (t) => { // Night: 0-4, Dawn: 4-6, Day: 6-17, Sunset: 17-19, Night: 19-24 if (t >= 0 && t < 4) return [COLORS.night, COLORS.night]; if (t >= 4 && t < 6) return [interpolateColor(COLORS.night, COLORS.dawn, (t-4)/2), interpolateColor(COLORS.night, COLORS.sunrise, (t-4)/2)]; if (t >= 6 && t < 7) return [interpolateColor(COLORS.dawn, COLORS.day, (t-6)), interpolateColor(COLORS.sunrise, '#87ceeb', (t-6))]; // Transition to bright day if (t >= 7 && t < 17) return [COLORS.day, '#bae6fd']; if (t >= 17 && t < 19) return [interpolateColor(COLORS.day, COLORS.dusk, (t-17)/2), interpolateColor('#bae6fd', COLORS.sunset, (t-17)/2)]; if (t >= 19 && t < 21) return [interpolateColor(COLORS.dusk, COLORS.night, (t-19)/2), interpolateColor(COLORS.sunset, COLORS.night, (t-19)/2)]; return [COLORS.night, COLORS.night]; }; const [skyTop, skyBottom] = getSkyColors(time); // Star Opacity (visible at night, fade during twilight) let starOpacity = 0; if (time < 5 || time > 20) starOpacity = 1; else if (time >= 5 && time < 7) starOpacity = 1 - (time - 5) / 2; else if (time >= 19 && time <= 20) starOpacity = (time - 19); // Formatted Time String const hours = Math.floor(time); const minutes = Math.floor((time - hours) * 60); const timeString = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; // Solar/Lunar Data for Tech UI const solarIntensity = isDay ? Math.sin(((time - 6) / 12) * Math.PI) * 100 : 0; const systemStatus = time > 6 && time < 18 ? "日间模式 (DAY)" : "夜间模式 (NIGHT)"; return ( {/* 1. Sky Background */} {/* 2. Stars */} {stars.map((star, i) => ( ))} {/* 3. Celestial Orbit System */} {/* Orbit Path Guide (Tech visual) */} {/* Rotating Group */} {/* SUN */} {/* Sun Ray Lines */} {[0, 45, 90, 135, 180, 225, 270, 315].map(deg => ( ))} {/* MOON */} {/* 4. Landscape */} {/* 5. Tech UI Overlay */} {/* Grid Overlay */} {/* Top Left: System Status */} 环境监测系统 (ENV-SYS) 状态: {systemStatus} 能量输入: {solarIntensity.toFixed(1)}% {/* Top Right: Clock */} 系统时间 (TIME) {timeString} {/* Center: Compass/Horizon Guide */} 天顶 (ZENITH) {/* Bottom Left: Data Stream */} 经度: 116.40° E 纬度: 39.90° N 大气透明度: {(85 + Math.sin(time)*10).toFixed(1)}% {/* Decorative Border */} ); };