import React, { useState, useEffect } from 'react'; import { Cloud, Sun, CloudRain, Snowflake, Wind, Droplets, Heart, BookOpen, Save, Star, Zap, Calendar } from 'lucide-react'; const WeatherPet = () => { const [location, setLocation] = useState(''); const [weather, setWeather] = useState(null); const [hourly, setHourly] = useState([]); const [loading, setLoading] = useState(false); const [petMood, setPetMood] = useState('happy'); const [petAnimation, setPetAnimation] = useState(0); const [screen, setScreen] = useState('main'); const [notes, setNotes] = useState(''); const [savedForecasts, setSavedForecasts] = useState([]); const [tempUnit, setTempUnit] = useState('F'); const [windUnit, setWindUnit] = useState('mph'); const [stats, setStats] = useState({ checkStreak: 0, lastCheckDate: null, sunnyDays: 0, rainyDays: 0, snowyDays: 0, totalChecks: 0 }); // Meteor shower data const meteorShowers = [ { name: "Quadrantids", peak: "Jan 3-4", period: "Dec 28 - Jan 12" }, { name: "Lyrids", peak: "Apr 22-23", period: "Apr 16-25" }, { name: "Eta Aquarids", peak: "May 6-7", period: "Apr 19 - May 28" }, { name: "Perseids", peak: "Aug 12-13", period: "Jul 17 - Aug 24" }, { name: "Orionids", peak: "Oct 21-22", period: "Oct 2 - Nov 7" }, { name: "Leonids", peak: "Nov 17-18", period: "Nov 6-30" }, { name: "Geminids", peak: "Dec 13-14", period: "Dec 4-20" } ]; useEffect(() => { loadSavedForecasts(); loadStats(); }, []); const loadStats = async () => { try { const result = await window.storage.get('weatherpet:stats'); if (result) { setStats(JSON.parse(result.value)); } } catch (error) { console.log('No stats yet'); } }; const saveStats = async (newStats) => { try { await window.storage.set('weatherpet:stats', JSON.stringify(newStats)); setStats(newStats); } catch (error) { console.error('Could not save stats'); } }; const updateStats = (weatherCondition) => { const today = new Date().toDateString(); let newStats = { ...stats }; // Update streak if (stats.lastCheckDate === today) { // Already checked today, don't update streak } else if (stats.lastCheckDate === new Date(Date.now() - 86400000).toDateString()) { // Checked yesterday, increase streak newStats.checkStreak += 1; } else { // Streak broken newStats.checkStreak = 1; } newStats.lastCheckDate = today; newStats.totalChecks += 1; // Update weather type counters const condition = weatherCondition.toLowerCase(); if (condition.includes('sunny') || condition.includes('clear')) { newStats.sunnyDays += 1; } else if (condition.includes('rain')) { newStats.rainyDays += 1; } else if (condition.includes('snow')) { newStats.snowyDays += 1; } saveStats(newStats); }; const loadSavedForecasts = async () => { try { const keys = await window.storage.list('forecast:'); if (keys && keys.keys) { const forecasts = []; for (const key of keys.keys) { const result = await window.storage.get(key); if (result) { forecasts.push(JSON.parse(result.value)); } } setSavedForecasts(forecasts.sort((a, b) => b.timestamp - a.timestamp)); } } catch (error) { console.log('No saved forecasts yet'); } }; useEffect(() => { const interval = setInterval(() => { setPetAnimation(prev => (prev + 1) % 2); }, 500); return () => clearInterval(interval); }, []); useEffect(() => { if (!weather) return; const temp = tempUnit === 'F' ? weather.temp : weather.tempC; const condition = weather.condition.toLowerCase(); if (condition.includes('rain')) setPetMood('rainy'); else if (condition.includes('snow')) setPetMood('snowy'); else if (condition.includes('cloud')) setPetMood('cloudy'); else if ((tempUnit === 'F' && temp > 80) || (tempUnit === 'C' && temp > 27)) setPetMood('hot'); else if ((tempUnit === 'F' && temp < 32) || (tempUnit === 'C' && temp < 0)) setPetMood('cold'); else setPetMood('happy'); }, [weather, tempUnit]); const convertTemp = (f) => Math.round((f - 32) * 5 / 9); const mphToKmh = (mph) => Math.round(mph * 1.60934); const mphToKnots = (mph) => Math.round(mph * 0.868976); const fetchWeather = async () => { if (!location) return; setLoading(true); try { // First, geocode the location const geoResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1&language=en&format=json`); const geoData = await geoResponse.json(); if (!geoData.results || geoData.results.length === 0) { alert('Location not found! Try a different city name.'); setLoading(false); return; } const { latitude, longitude, name, country } = geoData.results[0]; // Fetch current weather and hourly forecast const weatherResponse = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,cloud_cover,wind_speed_10m,visibility&hourly=temperature_2m,weather_code&temperature_unit=fahrenheit&wind_speed_unit=mph&timezone=auto&forecast_days=1` ); const weatherData = await weatherResponse.json(); // Convert weather code to condition text const getConditionFromCode = (code) => { if (code === 0) return 'Clear'; if (code <= 3) return 'Partly Cloudy'; if (code <= 48) return 'Foggy'; if (code <= 67) return 'Rainy'; if (code <= 77) return 'Snowy'; if (code <= 82) return 'Showers'; if (code <= 86) return 'Snow Showers'; if (code <= 99) return 'Thunderstorm'; return 'Unknown'; }; const currentCondition = getConditionFromCode(weatherData.current.weather_code); const weather = { location: `${name}, ${country}`, temp: Math.round(weatherData.current.temperature_2m), tempC: Math.round((weatherData.current.temperature_2m - 32) * 5 / 9), condition: currentCondition, humidity: weatherData.current.relative_humidity_2m, windMph: Math.round(weatherData.current.wind_speed_10m), windKmh: Math.round(weatherData.current.wind_speed_10m * 1.60934), feelsLike: Math.round(weatherData.current.apparent_temperature), feelsLikeC: Math.round((weatherData.current.apparent_temperature - 32) * 5 / 9), icon: weatherData.current.weather_code, cloudCover: weatherData.current.cloud_cover, visibility: Math.round(weatherData.current.visibility / 1609.34) // meters to miles }; setWeather(weather); updateStats(currentCondition); // Process hourly data const now = new Date(); const currentHour = now.getHours(); const hourlyForecasts = []; for (let i = 0; i < 24; i++) { const tempF = weatherData.hourly.temperature_2m[i]; hourlyForecasts.push({ time: i, temp: Math.round(tempF), tempC: Math.round((tempF - 32) * 5 / 9), condition: getConditionFromCode(weatherData.hourly.weather_code[i]), icon: weatherData.hourly.weather_code[i] }); } setHourly(hourlyForecasts); } catch (error) { console.error('Weather fetch error:', error); alert('Unable to fetch weather data. Please check your internet connection and try again!'); } setLoading(false); }; const saveCurrentForecast = async () => { if (!weather) return; const forecast = { timestamp: Date.now(), date: new Date().toLocaleDateString(), weather, hourly, notes }; try { await window.storage.set(`forecast:${forecast.timestamp}`, JSON.stringify(forecast)); await loadSavedForecasts(); alert('Weather forecast saved! πŸ’Ύ'); } catch (error) { console.error('Save error:', error); alert('Could not save forecast'); } }; const deleteForecast = async (timestamp) => { try { await window.storage.delete(`forecast:${timestamp}`); await loadSavedForecasts(); } catch (error) { console.error('Delete error:', error); } }; const isGoodStargazing = () => { if (!weather) return false; return weather.cloudCover < 30 && weather.visibility > 6; }; const getUpcomingMeteorShowers = () => { const today = new Date(); const month = today.getMonth(); const day = today.getDate(); return meteorShowers.filter(shower => { const peakMonth = new Date(shower.peak + ", 2025").getMonth(); const peakDay = new Date(shower.peak + ", 2025").getDate(); const daysUntil = (peakMonth - month) * 30 + (peakDay - day); return daysUntil >= -7 && daysUntil <= 30; }); }; const getWeatherIcon = (condition) => { const c = condition?.toLowerCase() || ''; if (c.includes('rain')) return ; if (c.includes('snow')) return ; if (c.includes('cloud')) return ; return ; }; const renderPet = () => { const faces = { happy: ['(β—•β€Ώβ—•)', '(β—•Ο‰β—•)'], rainy: ['(β—•οΈ΅β—•)', '(β—•~β—•)'], snowy: ['(β—•oβ—•)', '(β—•β–½β—•)'], cloudy: ['(β—•_β—•)', '(β—•β€Ώβ—•)'], hot: ['(>_<)', '(β—•β–³β—•)'], cold: ['(β—•οΈΏβ—•)', '(β—•~β—•)'] }; return faces[petMood][petAnimation]; }; const getPetMessage = () => { const messages = { happy: "Perfect weather! β˜€οΈ", rainy: "Cozy rain day! 🌧️", snowy: "Snow day! β›„", cloudy: "Nice & peaceful! ☁️", hot: "Stay cool! πŸ”₯", cold: "Bundle up! ❄️" }; return messages[petMood] || "Check weather!"; }; const renderMainScreen = () => ( <> {/* Pet Display */}
{renderPet()}
{getPetMessage()}
{/* Location Input */}
setLocation(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && fetchWeather()} placeholder="Enter city..." className="w-full px-3 py-2 rounded-lg bg-white bg-opacity-60 text-green-900 text-sm border-2 border-green-700 focus:border-green-900 outline-none placeholder-green-700" />
{/* Action Buttons */}
{/* Weather Display */} {weather ? (
{weather.location}
{getWeatherIcon(weather.condition)} {tempUnit === 'F' ? weather.temp : weather.tempC}Β°{tempUnit}
{weather.condition}
Feels: {tempUnit === 'F' ? weather.feelsLike : weather.feelsLikeC}Β°{tempUnit}
{weather.humidity}%
{windUnit === 'mph' && `${weather.windMph}mph`} {windUnit === 'kmh' && `${weather.windKmh}km/h`} {windUnit === 'knots' && `${mphToKnots(weather.windMph)}kn`}
{isGoodStargazing() ? 'YES' : 'NO'}
{/* Stargazing Alert */} {isGoodStargazing() && (
⭐ Great stargazing tonight! Clear skies! ⭐
)} {/* Hourly Scroll */}
{hourly.map((hour, idx) => (
{hour.time}h
{getWeatherIcon(hour.condition)}
{tempUnit === 'F' ? hour.temp : hour.tempC}Β°
))}
) : (
🌍
Enter a location!
)} {/* Notes */}
πŸ“ Notes