|
1 | | -const API_KEY = "f45d51406f914de6899151327251010"; |
2 | | -const BASE = "https://api.weatherapi.com/v1/current.json"; // Use HTTPS to avoid browser blocks |
| 1 | +let currentUnit = 'celsius'; |
| 2 | +let currentWeatherData = null; |
| 3 | +let currentForecastData = null; |
| 4 | +const API_KEY = 'bd5e378503939ddaee76f12ad7a97608'; |
| 5 | +const WEATHER_API_URL = 'https://api.openweathermap.org/data/2.5/weather'; |
| 6 | +const FORECAST_API_URL = 'https://api.openweathermap.org/data/2.5/forecast'; |
3 | 7 |
|
4 | | -const $ = (id) => document.getElementById(id); |
5 | | -const searchBtn = $("search"); |
6 | | -const qInput = $("q"); |
7 | | -const loadingEl = $("loading"); |
8 | | -const errorEl = $("error"); |
| 8 | +// Weather condition to icon mapping |
| 9 | +const weatherIcons = { |
| 10 | + 'Clear': '☀️', |
| 11 | + 'Clouds': '☁️', |
| 12 | + 'Rain': '🌧️', |
| 13 | + 'Drizzle': '🌦️', |
| 14 | + 'Thunderstorm': '⛈️', |
| 15 | + 'Snow': '🌨️', |
| 16 | + 'Mist': '🌫️', |
| 17 | + 'Fog': '🌫️', |
| 18 | + 'Haze': '🌫️' |
| 19 | +}; |
9 | 20 |
|
10 | | -async function fetchWeather(q) { |
11 | | - errorEl.style.display = "none"; |
12 | | - loadingEl.style.display = "block"; |
| 21 | +// Gradient colors for different weather conditions |
| 22 | +const weatherGradients = { |
| 23 | + 'Clear': 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 50%, #ea580c 100%)', |
| 24 | + 'Clouds': 'linear-gradient(135deg, #6b7280 0%, #4b5563 50%, #374151 100%)', |
| 25 | + 'Rain': 'linear-gradient(135deg, #3b82f6 0%, #2563eb 50%, #1e40af 100%)', |
| 26 | + 'Drizzle': 'linear-gradient(135deg, #3b82f6 0%, #2563eb 50%, #1e40af 100%)', |
| 27 | + 'Thunderstorm': 'linear-gradient(135deg, #1f2937 0%, #374151 50%, #4b5563 100%)', |
| 28 | + 'Snow': 'linear-gradient(135deg, #e5e7eb 0%, #d1d5db 50%, #9ca3af 100%)', |
| 29 | + 'default': 'linear-gradient(135deg, #1e3a8a 0%, #7c3aed 50%, #ec4899 100%)' |
| 30 | +}; |
13 | 31 |
|
14 | | - try { |
15 | | - const url = `${BASE}?key=${API_KEY}&q=${encodeURIComponent(q)}&aqi=yes`; |
16 | | - const res = await fetch(url); |
| 32 | +// DOM elements |
| 33 | +const elements = { |
| 34 | + loading: document.getElementById('loading'), |
| 35 | + errorMessage: document.getElementById('errorMessage'), |
| 36 | + cityInput: document.getElementById('cityInput'), |
| 37 | + searchBtn: document.getElementById('searchBtn'), |
| 38 | + cityName: document.getElementById('cityName'), |
| 39 | + dateTime: document.getElementById('dateTime'), |
| 40 | + weatherIcon: document.getElementById('weatherIcon'), |
| 41 | + temperature: document.getElementById('temperature'), |
| 42 | + tempToggle: document.getElementById('tempToggle'), |
| 43 | + weatherDescription: document.getElementById('weatherDescription'), |
| 44 | + feelsLike: document.getElementById('feelsLike'), |
| 45 | + humidity: document.getElementById('humidity'), |
| 46 | + windSpeed: document.getElementById('windSpeed'), |
| 47 | + pressure: document.getElementById('pressure'), |
| 48 | + visibility: document.getElementById('visibility'), |
| 49 | + uvIndex: document.getElementById('uvIndex'), |
| 50 | + forecastContainer: document.getElementById('forecastContainer') |
| 51 | +}; |
17 | 52 |
|
18 | | - if (!res.ok) throw new Error(`HTTP ${res.status}`); |
| 53 | +// Utility functions |
| 54 | +function showLoading() { |
| 55 | + elements.loading.style.display = 'flex'; |
| 56 | +} |
| 57 | + |
| 58 | +function hideLoading() { |
| 59 | + elements.loading.style.display = 'none'; |
| 60 | +} |
| 61 | + |
| 62 | +function showError(message) { |
| 63 | + elements.errorMessage.textContent = message; |
| 64 | + elements.errorMessage.style.display = 'block'; |
| 65 | + setTimeout(() => { |
| 66 | + elements.errorMessage.style.display = 'none'; |
| 67 | + }, 5000); |
| 68 | +} |
| 69 | + |
| 70 | +function getWeatherIcon(condition) { |
| 71 | + return weatherIcons[condition] || '🌤️'; |
| 72 | +} |
| 73 | + |
| 74 | +function convertTemp(temp, unit) { |
| 75 | + if (unit === 'fahrenheit') { |
| 76 | + return Math.round((temp * 9/5) + 32); |
| 77 | + } |
| 78 | + return Math.round(temp); |
| 79 | +} |
19 | 80 |
|
20 | | - const data = await res.json(); |
21 | | - render(data); |
22 | | - } catch (err) { |
23 | | - showError(err.message); |
24 | | - } finally { |
25 | | - loadingEl.style.display = "none"; |
26 | | - } |
| 81 | +function formatDate() { |
| 82 | + const now = new Date(); |
| 83 | + const options = { |
| 84 | + weekday: 'long', |
| 85 | + year: 'numeric', |
| 86 | + month: 'long', |
| 87 | + day: 'numeric', |
| 88 | + hour: '2-digit', |
| 89 | + minute: '2-digit' |
| 90 | + }; |
| 91 | + return now.toLocaleDateString('en-US', options); |
27 | 92 | } |
28 | 93 |
|
29 | | -function render(data) { |
30 | | - if (!data || !data.location) return showError("Invalid response"); |
| 94 | +function updateBackground(weatherCondition) { |
| 95 | + const gradient = weatherGradients[weatherCondition] || weatherGradients.default; |
| 96 | + document.body.style.background = gradient; |
| 97 | + document.body.style.backgroundSize = '400% 400%'; |
| 98 | +} |
31 | 99 |
|
32 | | - $("location").textContent = `${data.location.name}, ${data.location.country}`; |
33 | | - $("localtime").textContent = data.location.localtime || "--"; |
34 | | - $("temp").textContent = `${data.current.temp_c}°C`; |
35 | | - $("condition").textContent = data.current.condition.text; |
36 | | - $("humidity").textContent = `${data.current.humidity}%`; |
37 | | - $("wind").textContent = `${data.current.wind_kph} kph`; |
38 | | - $("feelslike").textContent = `${data.current.feelslike_c}°C`; |
| 100 | +// API functions |
| 101 | +async function fetchWeather(city) { |
| 102 | + try { |
| 103 | + const response = await fetch(`${WEATHER_API_URL}?q=${city}&appid=${API_KEY}&units=metric`); |
| 104 | + if (!response.ok) { |
| 105 | + throw new Error(`Weather data not found for "${city}". Please check the city name and try again.`); |
| 106 | + } |
| 107 | + const data = await response.json(); |
| 108 | + return data; |
| 109 | + } catch (error) { |
| 110 | + throw new Error(error.message || 'Failed to fetch weather data. Please try again.'); |
| 111 | + } |
| 112 | +} |
39 | 113 |
|
40 | | - const iconUrl = data.current.condition.icon.startsWith("//") |
41 | | - ? "https:" + data.current.condition.icon |
42 | | - : data.current.condition.icon; |
| 114 | +async function fetchForecast(city) { |
| 115 | + try { |
| 116 | + const response = await fetch(`${FORECAST_API_URL}?q=${city}&appid=${API_KEY}&units=metric`); |
| 117 | + if (!response.ok) { |
| 118 | + throw new Error('Failed to fetch forecast data'); |
| 119 | + } |
| 120 | + const data = await response.json(); |
| 121 | + return data; |
| 122 | + } catch (error) { |
| 123 | + throw new Error('Failed to fetch forecast data. Please try again.'); |
| 124 | + } |
| 125 | +} |
43 | 126 |
|
44 | | - $("icon").src = iconUrl; |
45 | | - $("icon").alt = data.current.condition.text; |
| 127 | +// Display functions |
| 128 | +function displayWeather(data) { |
| 129 | + currentWeatherData = data; |
| 130 | + |
| 131 | + elements.cityName.textContent = `${data.name}, ${data.sys.country}`; |
| 132 | + elements.dateTime.textContent = formatDate(); |
| 133 | + |
| 134 | + const condition = data.weather[0].main; |
| 135 | + elements.weatherIcon.textContent = getWeatherIcon(condition); |
| 136 | + |
| 137 | + const temp = convertTemp(data.main.temp, currentUnit); |
| 138 | + const unit = currentUnit === 'celsius' ? '°C' : '°F'; |
| 139 | + elements.temperature.textContent = `${temp}${unit}`; |
| 140 | + |
| 141 | + elements.weatherDescription.textContent = data.weather[0].description; |
| 142 | + |
| 143 | + // Update details |
| 144 | + const feelsLikeTemp = convertTemp(data.main.feels_like, currentUnit); |
| 145 | + elements.feelsLike.textContent = `${feelsLikeTemp}${unit}`; |
| 146 | + elements.humidity.textContent = `${data.main.humidity}%`; |
| 147 | + elements.windSpeed.textContent = `${Math.round(data.wind.speed * 10) / 10} m/s`; |
| 148 | + elements.pressure.textContent = `${data.main.pressure} hPa`; |
| 149 | + elements.visibility.textContent = data.visibility ? `${Math.round(data.visibility / 1000)} km` : 'N/A'; |
| 150 | + |
| 151 | + // UV Index is not available in the free API, so we'll show N/A |
| 152 | + elements.uvIndex.textContent = 'N/A'; |
| 153 | + |
| 154 | + // Update background based on weather |
| 155 | + updateBackground(condition); |
46 | 156 | } |
47 | 157 |
|
48 | | -function showError(msg) { |
49 | | - errorEl.style.display = "block"; |
50 | | - errorEl.className = "error"; |
51 | | - errorEl.textContent = msg; |
| 158 | +function displayForecast(data) { |
| 159 | + currentForecastData = data; |
| 160 | + elements.forecastContainer.innerHTML = ''; |
| 161 | + |
| 162 | + // Group forecast data by day (every 8th item represents a new day as data is every 3 hours) |
| 163 | + const dailyForecasts = []; |
| 164 | + for (let i = 0; i < data.list.length; i += 8) { |
| 165 | + if (dailyForecasts.length >= 5) break; |
| 166 | + dailyForecasts.push(data.list[i]); |
| 167 | + } |
| 168 | + |
| 169 | + dailyForecasts.forEach((forecast, index) => { |
| 170 | + const forecastCard = document.createElement('div'); |
| 171 | + forecastCard.className = 'glass-card forecast-card'; |
| 172 | + |
| 173 | + const date = new Date(forecast.dt * 1000); |
| 174 | + const dayName = index === 0 ? 'Today' : date.toLocaleDateString('en-US', { weekday: 'long' }); |
| 175 | + |
| 176 | + const condition = forecast.weather[0].main; |
| 177 | + const icon = getWeatherIcon(condition); |
| 178 | + const highTemp = convertTemp(forecast.main.temp_max, currentUnit); |
| 179 | + const lowTemp = convertTemp(forecast.main.temp_min, currentUnit); |
| 180 | + const unit = currentUnit === 'celsius' ? '°C' : '°F'; |
| 181 | + |
| 182 | + forecastCard.innerHTML = ` |
| 183 | + <div class="forecast-day">${dayName}</div> |
| 184 | + <div class="forecast-icon">${icon}</div> |
| 185 | + <div class="forecast-description">${forecast.weather[0].description}</div> |
| 186 | + <div class="forecast-temps"> |
| 187 | + <span class="high-temp">${highTemp}${unit}</span> |
| 188 | + <span class="low-temp">${lowTemp}${unit}</span> |
| 189 | + </div> |
| 190 | + `; |
| 191 | + |
| 192 | + elements.forecastContainer.appendChild(forecastCard); |
| 193 | + }); |
52 | 194 | } |
53 | 195 |
|
54 | | -searchBtn.addEventListener("click", () => |
55 | | - fetchWeather(qInput.value || "London") |
56 | | -); |
57 | | -qInput.addEventListener("keydown", (e) => { |
58 | | - if (e.key === "Enter") fetchWeather(qInput.value || "London"); |
| 196 | +function updateTemperatureDisplay() { |
| 197 | + if (currentWeatherData) { |
| 198 | + displayWeather(currentWeatherData); |
| 199 | + } |
| 200 | + if (currentForecastData) { |
| 201 | + displayForecast(currentForecastData); |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +// Main functions |
| 206 | +async function loadWeatherData(city) { |
| 207 | + showLoading(); |
| 208 | + elements.errorMessage.style.display = 'none'; |
| 209 | + |
| 210 | + try { |
| 211 | + const [weatherData, forecastData] = await Promise.all([ |
| 212 | + fetchWeather(city), |
| 213 | + fetchForecast(city) |
| 214 | + ]); |
| 215 | + |
| 216 | + displayWeather(weatherData); |
| 217 | + displayForecast(forecastData); |
| 218 | + } catch (error) { |
| 219 | + showError(error.message); |
| 220 | + } finally { |
| 221 | + hideLoading(); |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +function handleSearch() { |
| 226 | + const city = elements.cityInput.value.trim(); |
| 227 | + if (city) { |
| 228 | + loadWeatherData(city); |
| 229 | + elements.cityInput.value = ''; |
| 230 | + } |
| 231 | +} |
| 232 | + |
| 233 | +function toggleTemperatureUnit() { |
| 234 | + currentUnit = currentUnit === 'celsius' ? 'fahrenheit' : 'celsius'; |
| 235 | + elements.tempToggle.textContent = currentUnit === 'celsius' ? '°F' : '°C'; |
| 236 | + updateTemperatureDisplay(); |
| 237 | +} |
| 238 | + |
| 239 | +// Update time every minute |
| 240 | +function updateTime() { |
| 241 | + elements.dateTime.textContent = formatDate(); |
| 242 | +} |
| 243 | + |
| 244 | +// Event listeners |
| 245 | +elements.searchBtn.addEventListener('click', handleSearch); |
| 246 | +elements.cityInput.addEventListener('keypress', (e) => { |
| 247 | + if (e.key === 'Enter') { |
| 248 | + handleSearch(); |
| 249 | + } |
59 | 250 | }); |
| 251 | +elements.tempToggle.addEventListener('click', toggleTemperatureUnit); |
60 | 252 |
|
61 | | -fetchWeather(qInput.value || "London"); |
| 253 | +// Initialize app |
| 254 | +document.addEventListener('DOMContentLoaded', () => { |
| 255 | + // Load default city (London) |
| 256 | + loadWeatherData('London'); |
| 257 | + |
| 258 | + // Update time every minute |
| 259 | + setInterval(updateTime, 60000); |
| 260 | + updateTime(); |
| 261 | +}); |
0 commit comments