Yfaite commited on
Commit
d12c2ff
·
verified ·
1 Parent(s): 8f6186d

Do the app from scratch

Browse files
Files changed (1) hide show
  1. index.html +58 -133
index.html CHANGED
@@ -3,10 +3,9 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Cloudy With a Chance of Data</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
  <style>
11
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
12
  body {
@@ -39,13 +38,11 @@
39
  </head>
40
  <body class="py-8 px-4">
41
  <div class="max-w-4xl mx-auto">
42
- <!-- Header -->
43
  <header class="text-center mb-8">
44
- <h1 class="text-4xl font-bold text-gray-800 mb-2">Cloudy With a Chance of Data</h1>
45
- <p class="text-gray-600">Your personal weather oracle</p>
46
  </header>
47
 
48
- <!-- Search Bar -->
49
  <div class="flex mb-8 justify-center">
50
  <div class="relative w-full max-w-md">
51
  <input
@@ -63,78 +60,63 @@
63
  </div>
64
  </div>
65
 
66
- <!-- Current Weather -->
67
  <div id="current-weather" class="weather-card p-6 mb-8 text-center hidden">
68
  <div class="flex justify-between items-center mb-4">
69
  <div class="text-left">
70
- <h2 id="location" class="text-2xl font-bold text-gray-800">New York, US</h2>
71
- <p id="date" class="text-gray-600">Friday, 15 October</p>
72
- </div>
73
- <div id="weather-icon" class="w-20 h-20">
74
- <!-- Weather icon will be inserted here -->
75
  </div>
 
76
  </div>
77
  <div class="flex justify-around items-center">
78
  <div>
79
- <p id="temp" class="text-5xl font-bold text-gray-800">22°C</p>
80
- <p id="weather-desc" class="text-gray-600">Partly Cloudy</p>
81
  </div>
82
  <div class="grid grid-cols-2 gap-4 text-sm">
83
  <div class="flex items-center">
84
  <i data-feather="wind" class="mr-2 text-blue-500"></i>
85
- <span id="wind-speed">5 km/h</span>
86
  </div>
87
  <div class="flex items-center">
88
  <i data-feather="droplet" class="mr-2 text-blue-500"></i>
89
- <span id="humidity">65%</span>
90
  </div>
91
  <div class="flex items-center">
92
  <i data-feather="compass" class="mr-2 text-blue-500"></i>
93
- <span id="pressure">1012 hPa</span>
94
  </div>
95
  <div class="flex items-center">
96
  <i data-feather="eye" class="mr-2 text-blue-500"></i>
97
- <span id="visibility">10 km</span>
98
  </div>
99
  </div>
100
  </div>
101
  </div>
102
 
103
- <!-- Forecast -->
104
  <div id="forecast-container" class="hidden">
105
  <h2 class="text-xl font-semibold text-gray-800 mb-4">5-Day Forecast</h2>
106
- <div id="forecast" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
107
- <!-- Forecast items will be inserted here -->
108
- </div>
109
  </div>
110
 
111
- <!-- Loading State -->
112
  <div id="loading" class="text-center hidden">
113
  <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mb-2"></div>
114
  <p>Fetching weather data...</p>
115
  </div>
116
 
117
- <!-- Error State -->
118
  <div id="error" class="text-center hidden">
119
  <i data-feather="alert-circle" class="text-red-500 w-12 h-12 mx-auto mb-2"></i>
120
- <p id="error-message" class="text-red-500">Location not found. Please try another city.</p>
121
- <div class="mt-4 text-sm text-gray-600">
122
- <p>Search tips:</p>
123
- <ul class="list-disc list-inside">
124
- <li>Check your spelling</li>
125
- <li>Try a nearby major city</li>
126
- <li>Include country code (e.g. "Paris, FR")</li>
127
- </ul>
128
- </div>
129
  </div>
130
- </div>
131
 
132
  <script>
133
  feather.replace();
134
- // Initialize autocomplete
135
  const locationInput = document.getElementById('location-input');
136
  const searchBtn = document.getElementById('search-btn');
137
-
 
138
  // Debounce function to limit API calls
139
  function debounce(func, delay) {
140
  let timeout;
@@ -146,74 +128,14 @@
146
  };
147
  }
148
 
149
- // Fetch location suggestions
150
- async function fetchSuggestions(query) {
151
- if (!query || query.length < 2) return;
152
- try {
153
- const response = await fetch(`https://api.openweathermap.org/geo/1.0/direct?q=${query}&limit=5&appid=3fd7e0e3b5f5bf7f3a6cee1c6af5f8e0`);
154
- if (!response.ok) return;
155
- const data = await response.json();
156
- return data;
157
- } catch (error) {
158
- console.error('Error fetching suggestions:', error);
159
- }
160
- }
161
-
162
- // Show suggestions dropdown
163
- function showSuggestions(suggestions) {
164
- const dropdown = document.createElement('div');
165
- dropdown.className = 'absolute z-10 w-full mt-1 bg-white rounded-md shadow-lg max-h-60 overflow-auto';
166
- dropdown.id = 'suggestions-dropdown';
167
-
168
- suggestions.forEach(item => {
169
- const suggestion = document.createElement('div');
170
- suggestion.className = 'px-4 py-2 hover:bg-gray-100 cursor-pointer';
171
- suggestion.textContent = `${item.name}, ${item.country}`;
172
- suggestion.addEventListener('click', () => {
173
- locationInput.value = `${item.name}, ${item.country}`;
174
- dropdown.remove();
175
- fetchWeather();
176
- });
177
- dropdown.appendChild(suggestion);
178
- });
179
-
180
- // Remove existing dropdown if any
181
- const existing = document.getElementById('suggestions-dropdown');
182
- if (existing) existing.remove();
183
-
184
- locationInput.parentNode.appendChild(dropdown);
185
- }
186
-
187
- // Handle input changes with debounce
188
- locationInput.addEventListener('input', debounce(async (e) => {
189
- const query = e.target.value.trim();
190
- if (query.length >= 2) {
191
- const suggestions = await fetchSuggestions(query);
192
- if (suggestions && suggestions.length) {
193
- showSuggestions(suggestions);
194
- }
195
- }
196
- }, 300));
197
-
198
- // Handle click outside to close dropdown
199
- document.addEventListener('click', (e) => {
200
- if (!e.target.closest('#suggestions-dropdown') && e.target !== locationInput) {
201
- const dropdown = document.getElementById('suggestions-dropdown');
202
- if (dropdown) dropdown.remove();
203
- }
204
- });
205
-
206
  // Handle search button and enter key
207
  searchBtn.addEventListener('click', fetchWeather);
208
- locationInput.addEventListener('keydown', function(e) {
209
- if (e.key === 'Enter') {
210
- const dropdown = document.getElementById('suggestions-dropdown');
211
- if (dropdown) dropdown.remove();
212
- fetchWeather();
213
- }
214
  });
215
- async function fetchWeather() {
216
- const location = document.getElementById('location-input').value.trim();
 
217
  if (!location) return;
218
 
219
  // Show loading state
@@ -221,74 +143,76 @@ async function fetchWeather() {
221
  document.getElementById('current-weather').classList.add('hidden');
222
  document.getElementById('forecast-container').classList.add('hidden');
223
  document.getElementById('error').classList.add('hidden');
 
224
  try {
225
- // Fetch current weather
226
  // First get coordinates for location
227
- // Use OpenWeatherMap geocoding API which is more reliable
228
  const geoResponse = await fetch(`https://api.openweathermap.org/geo/1.0/direct?q=${location}&limit=1&appid=3fd7e0e3b5f5bf7f3a6cee1c6af5f8e0`);
229
  if (!geoResponse.ok) throw new Error('Location service unavailable');
230
  const geoData = await geoResponse.json();
231
 
232
  if (!geoData || geoData.length === 0) {
233
- throw new Error('City not found. Try a different spelling or nearby location.');
234
  }
 
235
  const city = geoData[0];
236
  const { lat: latitude, lon: longitude } = city;
237
- const locationName = `${city.name}${city.state ? ', ' + city.state : ''}, ${city.country}`;
238
- // Fetch current weather and forecast in one call
239
- // Use OpenWeatherMap API for weather data
240
  const weatherResponse = await fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&exclude=minutely,hourly,alerts&units=metric&appid=3fd7e0e3b5f5bf7f3a6cee1c6af5f8e0`);
241
- if (!weatherResponse.ok) throw new Error('Weather service unavailable');
242
  const weatherData = await weatherResponse.json();
243
 
244
- // Validate we got data
245
- if (!weatherData.current_weather || !weatherData.daily) {
246
- throw new Error('Incomplete weather data received');
247
- }
248
-
249
- // Update UI with current weather
250
  updateCurrentWeather(weatherData);
251
-
252
- // Update UI with forecast
253
  updateForecast(weatherData);
254
- // Hide loading, show weather
 
255
  document.getElementById('loading').classList.add('hidden');
256
  document.getElementById('current-weather').classList.remove('hidden');
257
  document.getElementById('forecast-container').classList.remove('hidden');
258
  } catch (error) {
259
- console.error('Error fetching weather:', error);
260
  document.getElementById('loading').classList.add('hidden');
261
  document.getElementById('error').classList.remove('hidden');
262
  document.getElementById('error-message').textContent = error.message;
263
- }
264
  }
 
265
  function updateCurrentWeather(data) {
266
  const current = data.current;
267
  const date = new Date(current.dt * 1000);
 
268
  document.getElementById('location').textContent = locationName;
269
- document.getElementById('date').textContent = date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
270
- document.getElementById('temp').textContent = `${Math.round(current.temp)}°C`;
 
 
 
 
271
 
272
- const weatherDesc = current.weather[0].description;
273
- document.getElementById('weather-desc').textContent = weatherDesc.charAt(0).toUpperCase() + weatherDesc.slice(1);
274
  document.getElementById('wind-speed').textContent = `${current.wind_speed} km/h`;
275
  document.getElementById('humidity').textContent = `${current.humidity}%`;
276
  document.getElementById('pressure').textContent = `${current.pressure} hPa`;
277
  document.getElementById('visibility').textContent = `${current.visibility / 1000} km`;
278
 
279
- // Update weather icon with OpenWeatherMap icon
280
  const weatherIcon = document.getElementById('weather-icon');
281
- weatherIcon.innerHTML = `<img src="https://openweathermap.org/img/wn/${current.weather[0].icon}@2x.png" alt="${weatherDesc}">`;
282
- }
 
283
  function updateForecast(data) {
284
  const forecastContainer = document.getElementById('forecast');
285
  forecastContainer.innerHTML = '';
 
286
  for (let i = 0; i < 5; i++) {
287
  const forecast = data.daily[i];
288
  const date = new Date(forecast.dt * 1000);
289
  const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
290
  const weatherDesc = forecast.weather[0].description;
291
- const forecastItem = document.createElement('div');
 
292
  forecastItem.className = 'weather-card p-4 text-center forecast-item';
293
  forecastItem.innerHTML = `
294
  <p class="font-medium text-gray-800">${dayName}</p>
@@ -298,16 +222,17 @@ const forecastItem = document.createElement('div');
298
  <div class="flex justify-center gap-4 mt-2">
299
  <span class="font-bold text-gray-800">${Math.round(forecast.temp.max)}°</span>
300
  <span class="text-gray-600">${Math.round(forecast.temp.min)}°</span>
301
- </div>
302
- `;
303
  forecastContainer.appendChild(forecastItem);
304
  }
305
- // Example: Load default weather for London on page load (more reliable)
306
- // Load default weather for London on page load
 
307
  window.addEventListener('load', () => {
308
  locationInput.value = 'London, UK';
309
  fetchWeather();
310
  });
311
- </script>
312
  </body>
313
- </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Weather Dashboard</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
 
9
  <style>
10
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
11
  body {
 
38
  </head>
39
  <body class="py-8 px-4">
40
  <div class="max-w-4xl mx-auto">
 
41
  <header class="text-center mb-8">
42
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">Weather Dashboard</h1>
43
+ <p class="text-gray-600">Get real-time weather updates</p>
44
  </header>
45
 
 
46
  <div class="flex mb-8 justify-center">
47
  <div class="relative w-full max-w-md">
48
  <input
 
60
  </div>
61
  </div>
62
 
 
63
  <div id="current-weather" class="weather-card p-6 mb-8 text-center hidden">
64
  <div class="flex justify-between items-center mb-4">
65
  <div class="text-left">
66
+ <h2 id="location" class="text-2xl font-bold text-gray-800">Location</h2>
67
+ <p id="date" class="text-gray-600">Date</p>
 
 
 
68
  </div>
69
+ <div id="weather-icon" class="w-20 h-20"></div>
70
  </div>
71
  <div class="flex justify-around items-center">
72
  <div>
73
+ <p id="temp" class="text-5xl font-bold text-gray-800">0°C</p>
74
+ <p id="weather-desc" class="text-gray-600">Weather</p>
75
  </div>
76
  <div class="grid grid-cols-2 gap-4 text-sm">
77
  <div class="flex items-center">
78
  <i data-feather="wind" class="mr-2 text-blue-500"></i>
79
+ <span id="wind-speed">0 km/h</span>
80
  </div>
81
  <div class="flex items-center">
82
  <i data-feather="droplet" class="mr-2 text-blue-500"></i>
83
+ <span id="humidity">0%</span>
84
  </div>
85
  <div class="flex items-center">
86
  <i data-feather="compass" class="mr-2 text-blue-500"></i>
87
+ <span id="pressure">0 hPa</span>
88
  </div>
89
  <div class="flex items-center">
90
  <i data-feather="eye" class="mr-2 text-blue-500"></i>
91
+ <span id="visibility">0 km</span>
92
  </div>
93
  </div>
94
  </div>
95
  </div>
96
 
 
97
  <div id="forecast-container" class="hidden">
98
  <h2 class="text-xl font-semibold text-gray-800 mb-4">5-Day Forecast</h2>
99
+ <div id="forecast" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4"></div>
 
 
100
  </div>
101
 
 
102
  <div id="loading" class="text-center hidden">
103
  <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mb-2"></div>
104
  <p>Fetching weather data...</p>
105
  </div>
106
 
 
107
  <div id="error" class="text-center hidden">
108
  <i data-feather="alert-circle" class="text-red-500 w-12 h-12 mx-auto mb-2"></i>
109
+ <p id="error-message" class="text-red-500">Location not found</p>
 
 
 
 
 
 
 
 
110
  </div>
111
+ </div>
112
 
113
  <script>
114
  feather.replace();
115
+
116
  const locationInput = document.getElementById('location-input');
117
  const searchBtn = document.getElementById('search-btn');
118
+ let locationName = '';
119
+
120
  // Debounce function to limit API calls
121
  function debounce(func, delay) {
122
  let timeout;
 
128
  };
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  // Handle search button and enter key
132
  searchBtn.addEventListener('click', fetchWeather);
133
+ locationInput.addEventListener('keydown', (e) => {
134
+ if (e.key === 'Enter') fetchWeather();
 
 
 
 
135
  });
136
+
137
+ async function fetchWeather() {
138
+ const location = locationInput.value.trim();
139
  if (!location) return;
140
 
141
  // Show loading state
 
143
  document.getElementById('current-weather').classList.add('hidden');
144
  document.getElementById('forecast-container').classList.add('hidden');
145
  document.getElementById('error').classList.add('hidden');
146
+
147
  try {
 
148
  // First get coordinates for location
 
149
  const geoResponse = await fetch(`https://api.openweathermap.org/geo/1.0/direct?q=${location}&limit=1&appid=3fd7e0e3b5f5bf7f3a6cee1c6af5f8e0`);
150
  if (!geoResponse.ok) throw new Error('Location service unavailable');
151
  const geoData = await geoResponse.json();
152
 
153
  if (!geoData || geoData.length === 0) {
154
+ throw new Error('City not found');
155
  }
156
+
157
  const city = geoData[0];
158
  const { lat: latitude, lon: longitude } = city;
159
+ locationName = `${city.name}${city.state ? ', ' + city.state : ''}, ${city.country}`;
160
+
161
+ // Fetch weather data
162
  const weatherResponse = await fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&exclude=minutely,hourly,alerts&units=metric&appid=3fd7e0e3b5f5bf7f3a6cee1c6af5f8e0`);
163
+ if (!weatherResponse.ok) throw new Error('Weather service unavailable');
164
  const weatherData = await weatherResponse.json();
165
 
166
+ // Update UI
 
 
 
 
 
167
  updateCurrentWeather(weatherData);
 
 
168
  updateForecast(weatherData);
169
+
170
+ // Hide loading, show weather
171
  document.getElementById('loading').classList.add('hidden');
172
  document.getElementById('current-weather').classList.remove('hidden');
173
  document.getElementById('forecast-container').classList.remove('hidden');
174
  } catch (error) {
175
+ console.error('Error:', error);
176
  document.getElementById('loading').classList.add('hidden');
177
  document.getElementById('error').classList.remove('hidden');
178
  document.getElementById('error-message').textContent = error.message;
179
+ }
180
  }
181
+
182
  function updateCurrentWeather(data) {
183
  const current = data.current;
184
  const date = new Date(current.dt * 1000);
185
+
186
  document.getElementById('location').textContent = locationName;
187
+ document.getElementById('date').textContent = date.toLocaleDateString('en-US', {
188
+ weekday: 'long',
189
+ year: 'numeric',
190
+ month: 'long',
191
+ day: 'numeric'
192
+ });
193
 
194
+ document.getElementById('temp').textContent = `${Math.round(current.temp)}°C`;
195
+ document.getElementById('weather-desc').textContent = current.weather[0].description;
196
  document.getElementById('wind-speed').textContent = `${current.wind_speed} km/h`;
197
  document.getElementById('humidity').textContent = `${current.humidity}%`;
198
  document.getElementById('pressure').textContent = `${current.pressure} hPa`;
199
  document.getElementById('visibility').textContent = `${current.visibility / 1000} km`;
200
 
 
201
  const weatherIcon = document.getElementById('weather-icon');
202
+ weatherIcon.innerHTML = `<img src="https://openweathermap.org/img/wn/${current.weather[0].icon}@2x.png" alt="${current.weather[0].description}">`;
203
+ }
204
+
205
  function updateForecast(data) {
206
  const forecastContainer = document.getElementById('forecast');
207
  forecastContainer.innerHTML = '';
208
+
209
  for (let i = 0; i < 5; i++) {
210
  const forecast = data.daily[i];
211
  const date = new Date(forecast.dt * 1000);
212
  const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
213
  const weatherDesc = forecast.weather[0].description;
214
+
215
+ const forecastItem = document.createElement('div');
216
  forecastItem.className = 'weather-card p-4 text-center forecast-item';
217
  forecastItem.innerHTML = `
218
  <p class="font-medium text-gray-800">${dayName}</p>
 
222
  <div class="flex justify-center gap-4 mt-2">
223
  <span class="font-bold text-gray-800">${Math.round(forecast.temp.max)}°</span>
224
  <span class="text-gray-600">${Math.round(forecast.temp.min)}°</span>
225
+ </div>
226
+ `;
227
  forecastContainer.appendChild(forecastItem);
228
  }
229
+ }
230
+
231
+ // Load default weather on page load
232
  window.addEventListener('load', () => {
233
  locationInput.value = 'London, UK';
234
  fetchWeather();
235
  });
236
+ </script>
237
  </body>
238
+ </html>