diff --git a/README.md b/README.md index f8b15f4cb..ce701f764 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ # Weather App -Replace this readme with your own information about your project. +Completed my first project, which involved integrating data from an open-source API into a weather application. This project provides a simple yet functional weather forecast for Dubai, for the upcoming five days. The forecast updates dynamically as new information is fetched from the API. In addition to the basic weather data, the app includes precise details about sunrise and sunset times for each day. From a design perspective, I incorporated an intuitive feature that allows users to toggle between a day and night mode, altering the visual theme to reflect different times of the day. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. - -## The problem - -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +## The Problem +I began the project by focusing on the design, utilising HTML and CSS to create a visually appealing layout. Once the core design was in place, I worked on making it responsive, ensuring it would display well across various devices. The most challenging part came when I transitioned into adding dynamic elements using JavaScript. Since I was new to working with APIs, fetching data from the open-source API was a learning curve. The biggest hurdle I encountered was correctly positioning and displaying the fetched data within the HTML structure. Replacing static elements with live API data proved to be quite tricky, as I had to ensure the content was dynamically updated while maintaining the integrity of the design. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +https://nightcast.netlify.app diff --git a/assets/design-1/Group16.png b/assets/design-1/Group16.png deleted file mode 100644 index c2d0e3f5a..000000000 Binary files a/assets/design-1/Group16.png and /dev/null differ diff --git a/assets/design-1/Group34.png b/assets/design-1/Group34.png deleted file mode 100644 index f7e90e552..000000000 Binary files a/assets/design-1/Group34.png and /dev/null differ diff --git a/assets/design-1/Group36.png b/assets/design-1/Group36.png deleted file mode 100644 index ce9c147cf..000000000 Binary files a/assets/design-1/Group36.png and /dev/null differ diff --git a/assets/design-1/Group37.png b/assets/design-1/Group37.png deleted file mode 100644 index ce9c147cf..000000000 Binary files a/assets/design-1/Group37.png and /dev/null differ diff --git a/assets/design-1/Group38.png b/assets/design-1/Group38.png deleted file mode 100644 index ce9c147cf..000000000 Binary files a/assets/design-1/Group38.png and /dev/null differ diff --git a/assets/design-1/cloudy.png b/assets/design-1/cloudy.png new file mode 100644 index 000000000..0e36315ff Binary files /dev/null and b/assets/design-1/cloudy.png differ diff --git a/assets/design-1/day-sky.jpg b/assets/design-1/day-sky.jpg new file mode 100644 index 000000000..7e83e3c2e Binary files /dev/null and b/assets/design-1/day-sky.jpg differ diff --git a/assets/design-1/day-weather-card.jpg b/assets/design-1/day-weather-card.jpg new file mode 100644 index 000000000..9e442391f Binary files /dev/null and b/assets/design-1/day-weather-card.jpg differ diff --git a/assets/design-1/default-icon.png b/assets/design-1/default-icon.png new file mode 100644 index 000000000..29fcf2724 Binary files /dev/null and b/assets/design-1/default-icon.png differ diff --git a/assets/design-1/moon-icon.png b/assets/design-1/moon-icon.png new file mode 100644 index 000000000..556a90272 Binary files /dev/null and b/assets/design-1/moon-icon.png differ diff --git a/assets/design-1/night-sky.jpg b/assets/design-1/night-sky.jpg new file mode 100644 index 000000000..efaf016f4 Binary files /dev/null and b/assets/design-1/night-sky.jpg differ diff --git a/assets/design-1/night-weather-card.jpeg b/assets/design-1/night-weather-card.jpeg new file mode 100644 index 000000000..34c584922 Binary files /dev/null and b/assets/design-1/night-weather-card.jpeg differ diff --git a/assets/design-1/rain.png b/assets/design-1/rain.png new file mode 100644 index 000000000..e45cf0513 Binary files /dev/null and b/assets/design-1/rain.png differ diff --git a/assets/design-1/search-icon.png b/assets/design-1/search-icon.png new file mode 100644 index 000000000..05120233b Binary files /dev/null and b/assets/design-1/search-icon.png differ diff --git a/assets/design-1/storm.png b/assets/design-1/storm.png new file mode 100644 index 000000000..d09dbddb5 Binary files /dev/null and b/assets/design-1/storm.png differ diff --git a/assets/design-1/sun-icon.png b/assets/design-1/sun-icon.png new file mode 100644 index 000000000..956ed9319 Binary files /dev/null and b/assets/design-1/sun-icon.png differ diff --git a/assets/design-1/sunny.png b/assets/design-1/sunny.png new file mode 100644 index 000000000..7b7fb8f0f Binary files /dev/null and b/assets/design-1/sunny.png differ diff --git a/assets/design-1/website-icon.png b/assets/design-1/website-icon.png new file mode 100644 index 000000000..5d00ff160 Binary files /dev/null and b/assets/design-1/website-icon.png differ diff --git a/assets/design-2/noun_Cloud_1188486.svg b/assets/design-2/noun_Cloud_1188486.svg deleted file mode 100644 index c2375e901..000000000 --- a/assets/design-2/noun_Cloud_1188486.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/design-2/noun_Sunglasses_2055147.svg b/assets/design-2/noun_Sunglasses_2055147.svg deleted file mode 100644 index a1fcd7e8b..000000000 --- a/assets/design-2/noun_Sunglasses_2055147.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/design-2/noun_Umbrella_2030530.svg b/assets/design-2/noun_Umbrella_2030530.svg deleted file mode 100644 index 8a414b15f..000000000 --- a/assets/design-2/noun_Umbrella_2030530.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/index.html b/index.html new file mode 100644 index 000000000..ee127aa5b --- /dev/null +++ b/index.html @@ -0,0 +1,90 @@ + + + + + + Weather App + + + + + + + +
+ +
+ +
+
+ +
+ + +
+ +
+
+
+

Dubai

+
+
+

16°C

+
+
+

+

+

+
+
+ +
+

Weekly Forecast

+
+
+
+ cloudy +
+

Monday

+

23°C

+
+
+
+ sunny +
+

Tuesday

+

20°C

+
+
+
+ cloudy with sun +
+

Wednesday

+

17°C

+
+
+
+ storm +
+

Thursday

+

16°C

+
+
+
+ rainy +
+

Friday

+

16°C

+
+
+
+
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 000000000..83958c031 --- /dev/null +++ b/script.js @@ -0,0 +1,148 @@ +// Wait for the DOM to fully load before selecting elements +document.addEventListener('DOMContentLoaded', () => { + // DOM Selectors + const toggle = document.getElementById('theme-toggle'); + const weatherCard = document.querySelector('.weather-card'); + const searchButton = document.getElementById('search-btn'); + const searchBarContainer = document.querySelector('.search-bar-container'); + const searchInput = document.getElementById('search-bar'); + const location = document.querySelector(".location h1"); + const currentTemp = document.querySelector(".current-temp h2"); + const sunsetTime = document.getElementById("sunset-time"); + const sunriseTime = document.getElementById("sunrise-time"); + const weatherDescription = document.getElementById("weather-description"); + const forecastItems = document.querySelectorAll(".forecast-item"); + + const apiKey = "6f10170466235746161a1b24e2d289bd"; + + // The toggle from night to day + toggle.addEventListener('change', () => { + if (toggle.checked) { + document.body.style.backgroundImage = "url('assets/design-1/day-sky.jpg')"; + weatherCard.classList.add('day-mode'); + } else { + document.body.style.backgroundImage = "url('assets/design-1/night-sky.jpg')"; + weatherCard.classList.remove('day-mode'); + } + }); + + // The search functionality + searchButton.addEventListener('click', () => { + searchBarContainer.classList.toggle('active'); + if (searchBarContainer.classList.contains('active')) { + searchInput.focus(); + } + }); + + // Function to fetch weather data for a given city + const fetchWeatherByCity = (city) => { + const apiURL = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric`; + + fetch(apiURL) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + updateHtml(data); // Update the HTML with the fetched data + }) + .catch((error) => { + console.error("Error fetching the weather data:", error); + alert('Unable to fetch data for the provided city. Please try another city.'); + }); + }; + + // Event listener for search input (trigger on 'Enter' key press) + searchInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + const city = event.target.value.trim(); + if (city) { + fetchWeatherByCity(city); // Fetch weather for the searched city + } else { + alert('Please enter a valid city name'); + } + } + }); + + // Initial fetch for default city (Dubai) + fetchWeatherByCity("Dubai"); + + // Function to format time for sunrise and sunset + const formatTime = (timestamp) => { + const date = new Date(timestamp * 1000); // Convert from seconds to milliseconds + return date.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + hour12: false + }); + }; + + // Function to update the HTML with weather data + const updateHtml = (data) => { + // Update the location, temperature, and details in the weather card + location.textContent = data.city.name; + currentTemp.textContent = `${data.list[0].main.temp.toFixed(1)}°C`; + + const sunset = formatTime(data.city.sunset); + const sunrise = formatTime(data.city.sunrise); + + sunsetTime.textContent = `Sunset: ${sunset}`; + sunriseTime.textContent = `Sunrise: ${sunrise}`; + + const weatherCondition = data.list[0].weather[0].main; + switch (weatherCondition) { + case "Clear": + weatherDescription.textContent = "A clear and sunny day ahead."; + break; + case "Clouds": + weatherDescription.textContent = "Clouds are covering the sky."; + break; + case "Rain": + weatherDescription.textContent = "Expect some rain showers."; + break; + case "Thunderstorm": + weatherDescription.textContent = "Thunderstorms expected, stay indoors!"; + break; + default: + weatherDescription.textContent = "Weather condition unknown, stay prepared."; + } + + updateWeeklyForecast(data.list); + }; + + // Function to update the weekly forecast + const updateWeeklyForecast = (forecastList) => { + const dailyForecast = forecastList.filter(forecast => { + const forecastDate = new Date(forecast.dt * 1000); + return forecastDate.getHours() === 12; // Picked 12:00 PM + }).slice(0, 5); // Get the first 5 days + + dailyForecast.forEach((forecast, index) => { + const forecastTemp = forecast.main.temp.toFixed(1); // Round to 1 decimal place + const forecastWeather = forecast.weather[0].main; + const dayName = new Date(forecast.dt * 1000).toLocaleDateString("en-US", { weekday: "long" }); + + forecastItems[index].querySelector("p:nth-of-type(1)").textContent = dayName; + forecastItems[index].querySelector("p:nth-of-type(2)").textContent = `${forecastTemp}°C`; + + const icon = forecastItems[index].querySelector("img"); + switch (forecastWeather) { + case "Clear": + icon.src = "assets/design-1/sunny.png"; + break; + case "Clouds": + icon.src = "assets/design-1/cloudy.png"; + break; + case "Rain": + icon.src = "assets/design-1/rain.png"; + break; + case "Thunderstorm": + icon.src = "assets/design-1/storm.png"; + default: + icon.src = "assets/design-1/default-icon.png"; + } + }); + }; +}); diff --git a/style.css b/style.css new file mode 100644 index 000000000..61314f243 --- /dev/null +++ b/style.css @@ -0,0 +1,528 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Roboto', sans-serif; +} + +body { + background: url('assets/design-1/night-sky.jpg') no-repeat center center/cover; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + transition: background 0.5s ease; /* Transition for body background */ +} + +.app { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + height: 100%; + width: 100%; + padding-bottom: 10px; /* Reduce bottom padding */ +} + +/* Search button styling */ +.search-button { + position: absolute; + top: 20px; + right: 20px; + z-index: 20; + display: flex; + align-items: center; + justify-content: flex-end; +} + +#search-btn { + background-color: rgba(255, 255, 255, 0.1); /* Light transparent background */ + border: 2px solid rgba(255, 255, 255, 0.3); /* Light border */ + border-radius: 50%; /* Circular shape */ + width: 50px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.3s ease; +} + +#search-btn img { + width: 24px; + height: 24px; +} + +#search-btn:hover { + background-color: rgba(255, 255, 255, 0.2); /* Slightly more visible on hover */ + border-color: rgba(255, 255, 255, 0.5); +} + +/* Search bar container */ +.search-bar-container { + position: absolute; + top: 0; + right: 60px; /* Positioned next to the search button */ + width: 0; /* Start with a width of 0 */ + height: 50px; + overflow: hidden; /* Hide the search bar initially */ + background-color: rgba(255, 255, 255, 0.2); + border-radius: 25px; + display: flex; + align-items: center; + padding-left: 10px; + transition: width 0.3s ease; /* Smooth transition for the width */ +} + +/* Search bar input */ +#search-bar { + width: 100%; + height: 40px; + border: none; + background: transparent; + outline: none; + color: white; + font-size: 1rem; + padding: 0 10px; +} + +/* When search bar is active */ +.search-bar-container.active { + width: 200px; /* Final width when the search bar slides open */ +} + + + +/* Toggle switch styling */ +.toggle-container { + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); + width: 60px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; +} + +.toggle-checkbox { + display: none; +} + +.toggle-label { + position: relative; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.2); + border-radius: 30px; + cursor: pointer; + display: flex; + align-items: center; + padding: 0 5px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); + overflow: hidden; +} + +.icon { + position: absolute; + width: 25px; + height: 25px; + background-size: contain; + background-repeat: no-repeat; + transition: transform 0.5s ease, opacity 0.5s ease; +} + +.sun-icon { + left: 5px; + background-image: url('assets/design-1/sun-icon.png'); /* Replace with the sun icon */ +} + +.moon-icon { + right: 5px; + opacity: 0; + background-image: url('assets/design-1/moon-icon.png'); /* Replace with the moon icon */ +} + +/* When checked, show moon icon and hide sun icon */ +.toggle-checkbox:checked + .toggle-label .sun-icon { + opacity: 0; + transform: translateX(25px); +} + +.toggle-checkbox:checked + .toggle-label .moon-icon { + opacity: 1; + transform: translateX(-25px); +} + +/* Default weather card (night mode) */ +.weather-card { + background: url('assets/design-1/night-weather-card.jpeg') no-repeat center center/cover; + background-size: cover; + border-radius: 20px; + padding: 20px; + width: 300px; + text-align: center; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + margin-top: 80px; + transition: background 0.5s ease; /* Smooth transition for card background */ + color: rgb(241, 213, 254); +} + +/* Day mode weather card */ +.weather-card.day-mode { + background: url('assets/design-1/day-weather-card.jpg') no-repeat center center/cover; /* Day weather card background */ + background-size: cover; + color: #D0A481; +} + +.weather-card.day-mode .weather-description { + color: #D0A481; +} + +.weather-card.day-mode h1, +.weather-card.day-mode h2 { + color:#f1dfce; +} + +.location h1 { + font-size: 1.5rem; + font-weight: 600; +} + +.current-temp h2 { + font-size: 3.5rem; + margin: 10px 0; +} + +.details p { + font-size: 1rem; + padding: 10px; +} + +.other-info { + display: flex; + justify-content: de-around; + margin-top: 20px; +} + +.info-item p { + margin: 5px 0; + font-size: 0.9rem; +} + +.weather-description { + font-size: 1rem; + margin-bottom: 10px; + color: #c0a2cd; +} + +/* Layout and styling for the weekly forecast */ +.weekly-forecast { + text-align: center; + margin-top: 30px; + color: #ffffff; + margin-top: 10px; /* Reduce the top margin */ +} + +.weekly-forecast h3 { + font-size: 1.2rem; + margin-bottom: 20px; +} + +/* Flexbox layout for forecast items */ +.forecast-items { + display: flex; + justify-content: space-around; /* Spread items evenly */ + margin: 0 auto; + max-width: 800px; /* Constrain the width */ + padding-bottom: 10px; /* Reduce padding between items */ +} + +/* Styling for each forecast item */ +.forecast-item { + text-align: center; + font-size: 1rem; + flex: 1; + padding: 10px; +} + +/* Circular icon for each forecast with an image inside */ +.forecast-icon { + width: 60px; + height: 60px; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.2); /* Light background for icon */ + margin: 0 auto 10px auto; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Soft shadow for icons */ +} + +/* Make the icons fit inside the circular container */ +.forecast-icon img { + width: 40px; + height: 40px; + object-fit: contain; +} + +/* Text styles */ +.forecast-item p { + margin: 5px 0; + font-size: 0.9rem; +} + + +@media (max-width: 470px) { + + .toggle-container { + position: relative; + } + + .forecast-items { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: auto auto; + grid-gap: 5px; + justify-items: center; + margin-top: 10px; + } +} + +/* Prevent horizontal scroll */ +html, body { + overflow-x: hidden; /* Disable horizontal scroll */ +} + + + +/* Here I made the design according to the design 1 +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Roboto', sans-serif; +} + +body, html { + width: 100vw; + height: 100vh; + background-color: #f0f0f0; + display: flex; + justify-content: center; + align-items: center; +} + +.weather-card { + background: linear-gradient(90deg, #1b1947 50%, #4f5ea6 50%); + border-radius: 0; + width: 100vw; + height: 100vh; + box-shadow: none; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + transition: all 0.5s ease; + overflow: hidden; +} + +.weather-info { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + height: 80vh; + position: relative; + z-index: 1200; +} + + +.sunrise { + font-size: 16px; + color: white; + position: absolute; + bottom: 40px; + left: 20px; + z-index: 1300; +} + +.sunset { + font-size: 16px; + color: white; + position: absolute; + bottom: 40px; + right: 20px; + z-index: 1300; +} + +.temp-info { + position: relative; + z-index: 1000; +} + +.temp-info h1 { + font-size: 64px; + color: white; + margin-bottom: 20px; +} + +.city-name { + font-size: 24px; + color: white; + margin-bottom: 10px; +} + +.temp-info p { + font-size: 16px; + color: white; +} + +.sun-image { + position: absolute; + top: 20px; + right: 20px; + width: 100px; + height: 100px; + z-index: 900; +} + +.moon-image { + position: absolute; + top: 20px; + left: 20px; + width: 100px; + height: 100px; + z-index: 900; + +.toggle-button { + background: #6c63ff; + border: none; + border-radius: 50%; + padding: 20px; + cursor: pointer; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + position: absolute; + bottom: 20px; + right: 20px; + z-index: 1100; + transition: bottom 0.5s ease; +} + +.toggle-button .arrow { + color: white; + font-size: 24px; + transform: rotate(0); + transition: transform 0.3s ease; +} + +.weather-card.open .arrow { + transform: rotate(90deg); +} + + +.weather-card.open .toggle-button { + bottom: 40vh; +} + +.week-forecast { + background-color: white; + border-radius: 20px 20px 0 0; + padding: 20px; + position: absolute; + bottom: 0; + width: 100%; + max-height: 0; + overflow: hidden; + transition: max-height 0.5s ease; + z-index: 500; +} + +.weather-card.open .week-forecast { + max-height: 40vh; +} + +.week-forecast ul { + list-style-type: none; + padding: 0; +} + +.week-forecast li { + font-size: 18px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid #ddd; +} + +.week-forecast li:last-child { + border-bottom: none; +} + +.day { + font-weight: bold; +} + +.icon { + font-size: 20px; + margin-right: 10px; +} + +@media (max-width: 1200px) and (min-width: 550px) { + .weather-info { + padding-left: 10px; + padding-top: 5px; + width: 60%; + } +} + +@media (max-width: 550px) { + .weather-card { + flex-direction: column; + } + + .weather-info { + width: 100%; + padding-left: 20px; + padding-top: 20px; + } + + .temp-info h1 { + font-size: 64px; + margin-bottom: 5px; + } + + .city-name { + font-size: 24px; + margin-bottom: 10px; + } + + .temp-info p { + font-size: 14px; + margin-bottom: 8px; + } + + .toggle-button { + padding: 15px; + bottom: 15px; + right: 15px; + } + + .toggle-button .arrow { + font-size: 18px; + } + + .week-forecast li { + font-size: 16px; + padding: 8px 0; + } + + .icon { + font-size: 18px; + } +} +* \ No newline at end of file