import { useEffect, useRef, useState } from 'react'; import { Box, IconButton } from '@mui/material'; import { Close } from '@mui/icons-material'; import { useThemeStore } from '../stores/themeStore'; interface ScreensaverProps { onClose: () => void; } // Color palette from ColorPicker for complementary color selection const colorPalette = [ '#673ab7', '#9c27b0', '#e1bee7', // Purples '#1976d2', '#03dac6', '#3f51b5', // Blues '#2e7d32', '#4caf50', '#009688', // Greens '#d32f2f', '#ff5722', '#ff9800', // Reds/Oranges '#424242', '#607d8b', '#795548', // Grays ]; // Get complementary colors based on primary color function getComplementaryColors(primaryColor: string): string[] { const primaryIndex = colorPalette.findIndex(color => color === primaryColor); if (primaryIndex === -1) { // If primary color is not in palette, use default complementary colors return ['#ff5722', '#4caf50']; // Orange and Green } // Choose colors that are visually distinct from primary const complementaryIndices = [ (primaryIndex + 6) % colorPalette.length, // Opposite side (primaryIndex + 9) % colorPalette.length, // Further opposite ]; return complementaryIndices.map(index => colorPalette[index]); } // Check if current time is in night hours (midnight to 6am) function isNightTime(): boolean { const hour = new Date().getHours(); return hour >= 0 && hour < 6; } // Get fade opacity based on time (0 = completely off, 1 = full brightness) function getNightFadeOpacity(): number { if (!isNightTime()) return 1; const hour = new Date().getHours(); const minute = new Date().getMinutes(); // Calculate fade based on how far into the night we are // 15-minute fade out starting at midnight, 15-minute fade in starting at 5:45am const totalMinutes = hour * 60 + minute; const fadeOutEnd = 15; // 12:15am const fadeInStart = 5 * 60 + 45; // 5:45am const fadeInEnd = 6 * 60; // 6:00am if (totalMinutes <= fadeOutEnd) { // Fading out: midnight to 12:15am (15 minutes) const fadeProgress = totalMinutes / fadeOutEnd; return 1 - fadeProgress; // 1 to 0 } else if (totalMinutes >= fadeInStart) { // Fading in: 5:45am to 6:00am (15 minutes) const fadeProgress = (totalMinutes - fadeInStart) / (fadeInEnd - fadeInStart); return fadeProgress; // 0 to 1 } else { // Completely off: 12:15am to 5:45am return 0; } } // Draw 24-hour clock function drawClock( ctx: CanvasRenderingContext2D, width: number, height: number ) { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0'); const timeString = `${hours}:${minutes}:${seconds}`; // Position clock in center const centerX = width / 2; const centerY = height / 2; // Draw clock background ctx.save(); ctx.globalAlpha = 0.3; ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(centerX - 120, centerY - 60, 240, 120); ctx.restore(); // Draw time text ctx.save(); ctx.fillStyle = 'white'; ctx.font = 'bold 48px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(timeString, centerX, centerY); ctx.restore(); // Draw date const dateString = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); ctx.save(); ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.font = '16px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(dateString, centerX, centerY + 50); ctx.restore(); } // Create gradient pattern function createGradientPattern( ctx: CanvasRenderingContext2D, width: number, height: number, colors: string[], time: number ) { const gradient1 = ctx.createRadialGradient( width * 0.3 + Math.sin(time * 0.001) * 100, height * 0.3 + Math.cos(time * 0.001) * 100, 0, width * 0.3 + Math.sin(time * 0.001) * 100, height * 0.3 + Math.cos(time * 0.001) * 100, width * 0.8 ); gradient1.addColorStop(0, `${colors[0]}80`); gradient1.addColorStop(0.5, `${colors[1]}40`); gradient1.addColorStop(1, `${colors[2]}20`); ctx.fillStyle = gradient1; ctx.fillRect(0, 0, width, height); const gradient2 = ctx.createRadialGradient( width * 0.7 + Math.cos(time * 0.002) * 150, height * 0.7 + Math.sin(time * 0.002) * 150, 0, width * 0.7 + Math.cos(time * 0.002) * 150, height * 0.7 + Math.sin(time * 0.002) * 150, width * 0.6 ); gradient2.addColorStop(0, `${colors[2]}60`); gradient2.addColorStop(0.7, `${colors[0]}30`); gradient2.addColorStop(1, 'transparent'); ctx.fillStyle = gradient2; ctx.fillRect(0, 0, width, height); } // Draw animated shapes function drawShapes( ctx: CanvasRenderingContext2D, width: number, height: number, colors: string[], time: number ) { const numShapes = 8; for (let i = 0; i < numShapes; i++) { const x = width * 0.5 + Math.sin(time * 0.001 + i * 0.8) * (width * 0.3); const y = height * 0.5 + Math.cos(time * 0.001 + i * 0.8) * (height * 0.3); const size = 50 + Math.sin(time * 0.002 + i) * 30; const colorIndex = i % colors.length; const opacity = 0.3 + Math.sin(time * 0.003 + i) * 0.2; ctx.save(); ctx.globalAlpha = opacity; ctx.fillStyle = colors[colorIndex]; if (i % 3 === 0) { // Circles ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } else if (i % 3 === 1) { // Squares ctx.fillRect(x - size/2, y - size/2, size, size); } else { // Triangles ctx.beginPath(); ctx.moveTo(x, y - size/2); ctx.lineTo(x - size/2, y + size/2); ctx.lineTo(x + size/2, y + size/2); ctx.closePath(); ctx.fill(); } ctx.restore(); } } // Draw line art function drawLineArt( ctx: CanvasRenderingContext2D, width: number, height: number, colors: string[], time: number ) { const numLines = 12; for (let i = 0; i < numLines; i++) { const x1 = Math.sin(time * 0.0005 + i * 0.5) * width; const y1 = Math.cos(time * 0.0005 + i * 0.5) * height; const x2 = Math.sin(time * 0.0005 + i * 0.5 + Math.PI) * width; const y2 = Math.cos(time * 0.0005 + i * 0.5 + Math.PI) * height; const colorIndex = i % colors.length; const opacity = 0.2 + Math.sin(time * 0.001 + i) * 0.1; ctx.save(); ctx.globalAlpha = opacity; ctx.strokeStyle = colors[colorIndex]; ctx.lineWidth = 2 + Math.sin(time * 0.002 + i) * 1; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); ctx.restore(); } } export function Screensaver({ onClose }: ScreensaverProps) { const canvasRef = useRef(null); const animationRef = useRef(); const { primaryColor } = useThemeStore(); const [lastClearTime, setLastClearTime] = useState(Date.now()); const [interactionEnabled, setInteractionEnabled] = useState(false); useEffect(() => { console.log('Screensaver: useEffect triggered'); const canvas = canvasRef.current; if (!canvas) { console.error('Screensaver: Canvas ref is null'); return; } const ctx = canvas.getContext('2d'); if (!ctx) { console.error('Screensaver: Could not get 2D context'); return; } console.log('Screensaver: Canvas and context initialized'); // Set canvas size const resizeCanvas = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; console.log('Screensaver: Canvas resized to', canvas.width, 'x', canvas.height); }; resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Get complementary colors const complementaryColors = getComplementaryColors(primaryColor); const colors = [primaryColor, ...complementaryColors]; console.log('Screensaver: Using colors', colors); let startTime = Date.now(); let clearInterval = 0; const animate = () => { const currentTime = Date.now(); const elapsed = currentTime - startTime; // Clear screen every 3 minutes (180000ms) like Windows 98 if (currentTime - lastClearTime > 180000) { ctx.clearRect(0, 0, canvas.width, canvas.height); setLastClearTime(currentTime); clearInterval = 0; } // Clear screen every few seconds for variety if (clearInterval > 5000) { ctx.clearRect(0, 0, canvas.width, canvas.height); clearInterval = 0; } // Create gradient background createGradientPattern(ctx, canvas.width, canvas.height, colors, elapsed); // Draw shapes drawShapes(ctx, canvas.width, canvas.height, colors, elapsed); // Draw line art drawLineArt(ctx, canvas.width, canvas.height, colors, elapsed); // Apply night fade if needed const nightFadeOpacity = getNightFadeOpacity(); if (nightFadeOpacity < 1) { ctx.save(); ctx.globalAlpha = 1 - nightFadeOpacity; ctx.fillStyle = 'black'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); } // Draw clock drawClock(ctx, canvas.width, canvas.height); clearInterval += 16; // ~60fps animationRef.current = requestAnimationFrame(animate); }; console.log('Screensaver: Starting animation'); animate(); return () => { console.log('Screensaver: Cleaning up'); window.removeEventListener('resize', resizeCanvas); if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [primaryColor, lastClearTime]); // Enable interaction detection after a delay to prevent immediate closing useEffect(() => { const timer = setTimeout(() => { console.log('Screensaver: Enabling interaction detection'); setInteractionEnabled(true); }, 1000); // 1 second delay return () => clearTimeout(timer); }, []); // Close on any key press or mouse movement (only when interaction is enabled) useEffect(() => { if (!interactionEnabled) return; const handleInteraction = () => { console.log('Screensaver: User interaction detected, closing'); onClose(); }; window.addEventListener('keydown', handleInteraction); window.addEventListener('mousemove', handleInteraction); window.addEventListener('click', handleInteraction); window.addEventListener('touchstart', handleInteraction); return () => { window.removeEventListener('keydown', handleInteraction); window.removeEventListener('mousemove', handleInteraction); window.removeEventListener('click', handleInteraction); window.removeEventListener('touchstart', handleInteraction); }; }, [onClose, interactionEnabled]); console.log('Screensaver: Rendering component'); return ( ); }