diff --git a/index.html b/index.html new file mode 100644 index 0000000..ca2f9db --- /dev/null +++ b/index.html @@ -0,0 +1,60 @@ + + + + + + Wilsons Weather + + + + + + +
+ + + + +
+
+ Current Weather +
+
+
+ +
+
+ +
+
+
+ Hourly Forecast +
+
+
+ +
+
+
+ +
+
+ Weekly Forecast +
+
+
+ +
+
+
+ + + diff --git a/public/weather-both/cloudy-both.png b/public/weather-both/cloudy-both.png new file mode 100644 index 0000000..e9f9d0a Binary files /dev/null and b/public/weather-both/cloudy-both.png differ diff --git a/public/weather-both/cold-both.png b/public/weather-both/cold-both.png new file mode 100644 index 0000000..b60b024 Binary files /dev/null and b/public/weather-both/cold-both.png differ diff --git a/public/weather-both/dreary-both.png b/public/weather-both/dreary-both.png new file mode 100644 index 0000000..4f20ce1 Binary files /dev/null and b/public/weather-both/dreary-both.png differ diff --git a/public/weather-both/flurries-both.png b/public/weather-both/flurries-both.png new file mode 100644 index 0000000..5b53ae6 Binary files /dev/null and b/public/weather-both/flurries-both.png differ diff --git a/public/weather-both/fog-both.png b/public/weather-both/fog-both.png new file mode 100644 index 0000000..593b09a Binary files /dev/null and b/public/weather-both/fog-both.png differ diff --git a/public/weather-both/freezing-rain-both.png b/public/weather-both/freezing-rain-both.png new file mode 100644 index 0000000..af35ff0 Binary files /dev/null and b/public/weather-both/freezing-rain-both.png differ diff --git a/public/weather-both/hot-both.png b/public/weather-both/hot-both.png new file mode 100644 index 0000000..0f6365e Binary files /dev/null and b/public/weather-both/hot-both.png differ diff --git a/public/weather-both/ice-both.png b/public/weather-both/ice-both.png new file mode 100644 index 0000000..f6c6f2c Binary files /dev/null and b/public/weather-both/ice-both.png differ diff --git a/public/weather-both/rain-and-snow-both.png b/public/weather-both/rain-and-snow-both.png new file mode 100644 index 0000000..a1a05e1 Binary files /dev/null and b/public/weather-both/rain-and-snow-both.png differ diff --git a/public/weather-both/rain-both.png b/public/weather-both/rain-both.png new file mode 100644 index 0000000..3e5b47d Binary files /dev/null and b/public/weather-both/rain-both.png differ diff --git a/public/weather-both/showers-both.png b/public/weather-both/showers-both.png new file mode 100644 index 0000000..e78e2ce Binary files /dev/null and b/public/weather-both/showers-both.png differ diff --git a/public/weather-both/sleet-both.png b/public/weather-both/sleet-both.png new file mode 100644 index 0000000..9b360bd Binary files /dev/null and b/public/weather-both/sleet-both.png differ diff --git a/public/weather-both/snow-both.png b/public/weather-both/snow-both.png new file mode 100644 index 0000000..05f7605 Binary files /dev/null and b/public/weather-both/snow-both.png differ diff --git a/public/weather-both/thunder-storm-both.png b/public/weather-both/thunder-storm-both.png new file mode 100644 index 0000000..3520832 Binary files /dev/null and b/public/weather-both/thunder-storm-both.png differ diff --git a/public/weather-both/windy-both.png b/public/weather-both/windy-both.png new file mode 100644 index 0000000..d22b1e2 Binary files /dev/null and b/public/weather-both/windy-both.png differ diff --git a/public/weather-day/hazy-sunshine-day.png b/public/weather-day/hazy-sunshine-day.png new file mode 100644 index 0000000..09e7448 Binary files /dev/null and b/public/weather-day/hazy-sunshine-day.png differ diff --git a/public/weather-day/intermitten-clouds-day.png b/public/weather-day/intermitten-clouds-day.png new file mode 100644 index 0000000..fb0a1ba Binary files /dev/null and b/public/weather-day/intermitten-clouds-day.png differ diff --git a/public/weather-day/mostly-cloudy-day.png b/public/weather-day/mostly-cloudy-day.png new file mode 100644 index 0000000..a80555c Binary files /dev/null and b/public/weather-day/mostly-cloudy-day.png differ diff --git a/public/weather-day/mostly-cloudy-with-flurries-day.png b/public/weather-day/mostly-cloudy-with-flurries-day.png new file mode 100644 index 0000000..5610b39 Binary files /dev/null and b/public/weather-day/mostly-cloudy-with-flurries-day.png differ diff --git a/public/weather-day/mostly-cloudy-with-showers-day.png b/public/weather-day/mostly-cloudy-with-showers-day.png new file mode 100644 index 0000000..96c79e5 Binary files /dev/null and b/public/weather-day/mostly-cloudy-with-showers-day.png differ diff --git a/public/weather-day/mostly-cloudy-with-snow-day.png b/public/weather-day/mostly-cloudy-with-snow-day.png new file mode 100644 index 0000000..0fa0dcf Binary files /dev/null and b/public/weather-day/mostly-cloudy-with-snow-day.png differ diff --git a/public/weather-day/mostly-cloudy-with-thunder-storms-day.png b/public/weather-day/mostly-cloudy-with-thunder-storms-day.png new file mode 100644 index 0000000..b5be8d3 Binary files /dev/null and b/public/weather-day/mostly-cloudy-with-thunder-storms-day.png differ diff --git a/public/weather-day/mostly-sunny-day.png b/public/weather-day/mostly-sunny-day.png new file mode 100644 index 0000000..b323c8b Binary files /dev/null and b/public/weather-day/mostly-sunny-day.png differ diff --git a/public/weather-day/partly-sunny-day.png b/public/weather-day/partly-sunny-day.png new file mode 100644 index 0000000..b9bfb83 Binary files /dev/null and b/public/weather-day/partly-sunny-day.png differ diff --git a/public/weather-day/partly-sunny-with flurries-day.png b/public/weather-day/partly-sunny-with flurries-day.png new file mode 100644 index 0000000..2fe3017 Binary files /dev/null and b/public/weather-day/partly-sunny-with flurries-day.png differ diff --git a/public/weather-day/partly-sunny-with-showers-day.png b/public/weather-day/partly-sunny-with-showers-day.png new file mode 100644 index 0000000..259278f Binary files /dev/null and b/public/weather-day/partly-sunny-with-showers-day.png differ diff --git a/public/weather-day/partly-sunny-with-thunder-storms-day.png b/public/weather-day/partly-sunny-with-thunder-storms-day.png new file mode 100644 index 0000000..0ce0911 Binary files /dev/null and b/public/weather-day/partly-sunny-with-thunder-storms-day.png differ diff --git a/public/weather-day/sun-day.png b/public/weather-day/sun-day.png new file mode 100644 index 0000000..5127a9d Binary files /dev/null and b/public/weather-day/sun-day.png differ diff --git a/public/weather-night/clear-night.png b/public/weather-night/clear-night.png new file mode 100644 index 0000000..aec8e71 Binary files /dev/null and b/public/weather-night/clear-night.png differ diff --git a/public/weather-night/hazy-moonlight-night.png b/public/weather-night/hazy-moonlight-night.png new file mode 100644 index 0000000..9888912 Binary files /dev/null and b/public/weather-night/hazy-moonlight-night.png differ diff --git a/public/weather-night/intermitten-clouds-night.png b/public/weather-night/intermitten-clouds-night.png new file mode 100644 index 0000000..3844317 Binary files /dev/null and b/public/weather-night/intermitten-clouds-night.png differ diff --git a/public/weather-night/mostly-clear-night.png b/public/weather-night/mostly-clear-night.png new file mode 100644 index 0000000..7279392 Binary files /dev/null and b/public/weather-night/mostly-clear-night.png differ diff --git a/public/weather-night/mostly-cloudy-night.png b/public/weather-night/mostly-cloudy-night.png new file mode 100644 index 0000000..572bfec Binary files /dev/null and b/public/weather-night/mostly-cloudy-night.png differ diff --git a/public/weather-night/mostly-cloudy-with-flurries-night.png b/public/weather-night/mostly-cloudy-with-flurries-night.png new file mode 100644 index 0000000..da96f77 Binary files /dev/null and b/public/weather-night/mostly-cloudy-with-flurries-night.png differ diff --git a/public/weather-night/mostly-cloudy-with-showers-night.png b/public/weather-night/mostly-cloudy-with-showers-night.png new file mode 100644 index 0000000..7c71a5f Binary files /dev/null and b/public/weather-night/mostly-cloudy-with-showers-night.png differ diff --git a/public/weather-night/mostly-cloudy-with-snow-night.png b/public/weather-night/mostly-cloudy-with-snow-night.png new file mode 100644 index 0000000..47a85e7 Binary files /dev/null and b/public/weather-night/mostly-cloudy-with-snow-night.png differ diff --git a/public/weather-night/mostly-cloudy-with-thunderstorms-night.png b/public/weather-night/mostly-cloudy-with-thunderstorms-night.png new file mode 100644 index 0000000..e0dea63 Binary files /dev/null and b/public/weather-night/mostly-cloudy-with-thunderstorms-night.png differ diff --git a/public/weather-night/partly-cloudy-night.png b/public/weather-night/partly-cloudy-night.png new file mode 100644 index 0000000..587a4d0 Binary files /dev/null and b/public/weather-night/partly-cloudy-night.png differ diff --git a/public/weather-night/partly-cloudy-with-showers-night.png b/public/weather-night/partly-cloudy-with-showers-night.png new file mode 100644 index 0000000..5accec9 Binary files /dev/null and b/public/weather-night/partly-cloudy-with-showers-night.png differ diff --git a/public/weather-night/partly-cloudy-with-thunderstorms-night.png b/public/weather-night/partly-cloudy-with-thunderstorms-night.png new file mode 100644 index 0000000..5e678c2 Binary files /dev/null and b/public/weather-night/partly-cloudy-with-thunderstorms-night.png differ diff --git a/scripts/getIp.js b/scripts/getIp.js new file mode 100644 index 0000000..388c4ad --- /dev/null +++ b/scripts/getIp.js @@ -0,0 +1,6 @@ +export async function getIp() { + return await fetch('https://api.ipify.org?format=json') + .then(response => response.json()) + .then(data => console.log(data.ip)) + .catch(error => console.log('Unable to get IP address', error)); +} \ No newline at end of file diff --git a/scripts/mobile.js b/scripts/mobile.js new file mode 100644 index 0000000..e69de29 diff --git a/scripts/script.js b/scripts/script.js new file mode 100644 index 0000000..4e282ec --- /dev/null +++ b/scripts/script.js @@ -0,0 +1,131 @@ +import { updateWeather } from './weather.js'; +import { getIp } from './getIp.js'; +import { updateTime } from './timeDate.js'; +import { updateCurrent } from './updateCurrent.js'; + +// This is unsecure fix at some point!!! +const API_BASE_URL = 'https://weather-app-server-d5459d7e5648.herokuapp.com' + +async function startWeather(latitude, longitude) { + const response = await fetch(`${API_BASE_URL}/?coords=${latitude},${longitude}`); + const weatherData = await response.json() + console.log(weatherData); + return weatherData; +} + +// Create get ip function +async function getIP() { + return await fetch(`${API_BASE_URL}/get-ip`); +} + +async function ipWeather(ip) { + const response = await fetch(`${API_BASE_URL}/ip-weather-data?ip=${ip}`); + const weatherData = await response.json(); + return weatherData; +} + +// Ask for location, if not get ip +if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + async function(position) { + const latitude = position.coords.latitude; + const longitude = position.coords.longitude; + const weatherData = await startWeather(latitude, longitude); + updateWeather(weatherData, 'Your Location'); + updateTime(weatherData); + }, + async function(error) { + // This function is called when an error occurs, such as when the user denies the location permission + console.log("Geolocation permission denied."); + try { + const ip = await getIp(); + const weatherData = await ipWeather(ip); + updateWeather(weatherData, 'Your Location'); + updateTime(weatherData); + } catch (error) { + console.error('Error:', error); + } + } + ); +} else { + console.log("Geolocation is not supported by this browser."); + (async () => { + try { + const ip = await getIp(); + const weatherData = await ipWeather(ip); + updateWeather(weatherData, 'Your Location'); + updateTime(weatherData); + } catch (error) { + console.error('Error:', error); + } + })(); +} + +// Get the form +const searchForm = document.getElementById('search-form'); + +// Get the search bar +const searchBar = document.getElementById('location-search'); + +// Create a dropdown for suggestions +const dropdown = document.createElement('div'); +dropdown.setAttribute('id', 'dropdown'); +searchBar.parentNode.appendChild(dropdown); + +// Stop user from enter invalid address in search bar +searchBar.addEventListener('keydown', function(event) { + if (event.key === 'Enter') { + event.preventDefault(); + } +}); + +// Add event listener +searchBar.addEventListener('input', async function(event) { + const userInput = event.target.value; + + if (userInput.length > 2) { // Trigger autocomplete after 2 char + try { + const response = await fetch(`${API_BASE_URL}/search-locations?input=${encodeURIComponent(userInput)}`); + const data = await response.json(); + dropdown.innerHTML = ''; // Clear previous suggestions + + data.predictions.forEach(item => { + const div = document.createElement('div'); + div.innerHTML = item.description; + div.onclick = function() { + searchBar.value = item.description; + dropdown.innerHTML = ''; // Clear suggestions after selection + + // Trigger form submission event manually + const event = new Event('submit'); + searchForm.dispatchEvent(event); + }; + dropdown.appendChild(div); + }); + } catch (error) { + console.error('Error:', error); + } + } +}); + +searchForm.addEventListener('submit', async function(event) { + event.preventDefault(); + // Get the user input from the search bar + const userSubmission = searchBar.value; + console.log('Form was submitted') + try { + // Clear the search bar + searchBar.value = ''; + + // Send a request to the server with the user input + const response = await fetch(`${API_BASE_URL}/weather-data?input=${encodeURIComponent(userSubmission)}`); + const weatherData = await response.json(); + + // Handle the response data here + updateWeather(weatherData, userSubmission); + updateTime(weatherData); + } catch (error) { + console.error('Error:', error); + } + +}); diff --git a/scripts/timeDate.js b/scripts/timeDate.js new file mode 100644 index 0000000..c100a3b --- /dev/null +++ b/scripts/timeDate.js @@ -0,0 +1,46 @@ +export function updateTime(weatherData) { + // Extract timezone and timestamp from weatherData + const timezone = weatherData.timezone; + const time = weatherData.current.dt; + + // Get first day of the week + const firstDay = weatherData.daily[0].dt; + const lastDay = weatherData.daily[weatherData.daily.length - 1].dt; + + const weekDateOptions = { + month: 'numeric', + day: 'numeric', + timeZone: timezone, + } + + const firstDayDate = new Date(firstDay * 1000).toLocaleDateString('en-US', weekDateOptions); + const lastDayDate = new Date(lastDay * 1000).toLocaleDateString('en-US', weekDateOptions); + + // Convert Unix timestamp to Date object + const currentDateTime = new Date(time * 1000); + + + // Date formatting options + const dateOptions = { + weekday: 'long', + month: 'long', + day: 'numeric', + timeZone: timezone // Set the timezone for formatting + }; + // Format date + const formattedDate = currentDateTime.toLocaleDateString('en-US', dateOptions); + + // Time formatting options + const timeOptions = { + hour: 'numeric', + minute: '2-digit', + timeZone: timezone // Set the timezone for formatting + }; + // Format time + const formattedTime = currentDateTime.toLocaleTimeString('en-US', timeOptions); + + // Example: Update date and time in HTML + document.getElementById('currentTime').textContent = formattedTime; + document.getElementById('currentDate').textContent = formattedDate; + document.getElementById('currentWeek').textContent = `${firstDayDate} - ${lastDayDate}`; +} \ No newline at end of file diff --git a/scripts/unitConversions.js b/scripts/unitConversions.js new file mode 100644 index 0000000..257e56e --- /dev/null +++ b/scripts/unitConversions.js @@ -0,0 +1,24 @@ +export function kelvinToFahrenheit(kelvin) { + const fahrenheit = Math.round((kelvin - 273.15) * 9/5 + 32); + return fahrenheit; +} + +export function kelvinToCelsius(kelvin) { + const celsius = Math.round(kelvin - 273.15); + return celsius; +} + +export function capitalizeFirstLetterOfEachWord(str) { + // Split the string into words + const words = str.split(' '); + + // Capitalize the first letter of each word + const capitalizedWords = words.map(word => { + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }); + + // Join the words back into a single string + const capitalizedStr = capitalizedWords.join(' '); + + return capitalizedStr; +} \ No newline at end of file diff --git a/scripts/updateCurrent.js b/scripts/updateCurrent.js new file mode 100644 index 0000000..a36b516 --- /dev/null +++ b/scripts/updateCurrent.js @@ -0,0 +1,34 @@ +import { kelvinToFahrenheit, kelvinToCelsius, capitalizeFirstLetterOfEachWord } from './unitConversions.js'; + +export function updateCurrent(weatherData, weatherCodeToImageMap, userInput) { + const current = weatherData.current; + const temperature = kelvinToFahrenheit(current.temp); + const description = capitalizeFirstLetterOfEachWord(current.weather[0].description); + const time = current.dt; + const sunrise = current.sunrise; + const sunset = current.sunset; + const id = current.weather[0].id; + let image = weatherCodeToImageMap[id]; + if (typeof image !== 'string' && time > sunrise && time < sunset) { + image = image[0]; + } else { + image = image[1]; + } + const location = userInput.split(','); + + // Get the town from userInput + // There's two possible scenarios for the userInput + // 1. A string from a verified google address + // 2. Your location + // For your location set userInput to false + let currentHTML = ` +
+
${location[0]}
+
${temperature}°
+
${description}
+
+ `; + + const currentForecastDiv = document.querySelector('.current-body'); + currentForecastDiv.innerHTML = currentHTML +} \ No newline at end of file diff --git a/scripts/updateHourly.js b/scripts/updateHourly.js new file mode 100644 index 0000000..cc4c704 --- /dev/null +++ b/scripts/updateHourly.js @@ -0,0 +1,90 @@ +import { kelvinToFahrenheit, kelvinToCelsius, capitalizeFirstLetterOfEachWord } from './unitConversions.js'; + +export function updateHourly(weatherData, weatherCodeToImageMap) { + const hourly = weatherData.hourly; + const daily = weatherData.daily; + const current = weatherData.current; + // Get the time of sunset and sun rise for both the current and next day + const todayDaily = daily[0]; + const tomorrowDaily = daily[1]; + const firstSunrise = todayDaily.sunrise; + const firstSunset = todayDaily.sunset; + const secondSunrise = tomorrowDaily.sunrise; + const secondSunset = tomorrowDaily.sunset; + const timezone = weatherData.timezone; + + let riseSet = [secondSunset, secondSunrise, firstSunset, firstSunrise]; + + let hourlyHTML = ''; + + hourly.forEach((forecast, index) => { + // Check to see if the current time is before for after first sunrise + if (index === 0 && forecast.dt >= firstSunrise) { + riseSet.pop(); + } + + // Convert temperatures + const temperature = kelvinToFahrenheit(forecast.temp); + const feelsLike = kelvinToFahrenheit(forecast.feels_like); + + // Get weather array + const weatherArray = forecast.weather[0] + + // Get the weather description and code + const weatherCode = weatherArray.id; + let image = weatherCodeToImageMap[weatherCode]; + if (typeof image !== "string" && (riseSet.length%2 === 0)) { + image = image[1]; + } else if (typeof image !== "string" && (riseSet.length%2 !== 0)) { + image = image[0]; + } + + const description = capitalizeFirstLetterOfEachWord(weatherArray.description); + + // Get other weather data + const windSpeed = forecast.wind_speed; + const windDegrees = forecast.wind_deg; + const uvi = forecast.uvi; + + const hourOptions = { + hour: 'numeric', + timeZone: timezone, + } + + // Append to hourlyHTML string + hourlyHTML += ` +
+

${new Date(forecast.dt * 1000).toLocaleTimeString('en-US', hourOptions)}

+ Weather icon +

${kelvinToFahrenheit(forecast.temp)}°F

+
`; + + + const currentSetRiseUnix = riseSet[riseSet.length - 1]; + const currentSetRiseHour = new Date(currentSetRiseUnix * 1000) + const currentForecastHour = new Date(forecast.dt * 1000); + + const riseSetOptions = { + hour: 'numeric', + minute: '2-digit', + timeZone: timezone, + } + + // Check for sunrise or sunset + if (currentSetRiseHour.getUTCHours() === currentForecastHour.getUTCHours()) { + // Insert if true + hourlyHTML += ` +
+

${new Date(currentSetRiseUnix * 1000).toLocaleTimeString('en-US', riseSetOptions)}

+ Weather icon +

${(riseSet.length%2 === 0) ? "Sunrise" : "Sunset"}

+
+ `; + riseSet.pop() + } + }); + + // Update the DOM + const hourlyForecastDiv = document.querySelector('.hourly-body'); + hourlyForecastDiv.innerHTML = hourlyHTML; +} \ No newline at end of file diff --git a/scripts/updateWeekly.js b/scripts/updateWeekly.js new file mode 100644 index 0000000..6784663 --- /dev/null +++ b/scripts/updateWeekly.js @@ -0,0 +1,80 @@ +import { kelvinToFahrenheit, kelvinToCelsius, capitalizeFirstLetterOfEachWord } from './unitConversions.js'; + +export function updateWeekly(weatherData, weatherCodeToImageMap) { + const daily = weatherData.daily; + + let weeklyHTML = ''; + const timezone = weatherData.timezone; + // Date formatting options + const dateOptions = { + month: 'numeric', + day: 'numeric', + timeZone: timezone + }; + + for (let i = 0; i < daily.length; i++) { + // Get the the day object + let currentDay = daily[i]; + + let date = new Date(currentDay.dt * 1000); + + let day = date.toLocaleDateString('en-US', {weekday: 'long', timezone}) + let dayMonth = date.toLocaleDateString('en-US', dateOptions); + let weatherCode = currentDay.weather[0].id; + let image = weatherCodeToImageMap[weatherCode]; + + if (typeof image !== "string") { + image = image[0]; + } + + let high = kelvinToFahrenheit(currentDay.temp.max); + let low = kelvinToFahrenheit(currentDay.temp.min); + let description = capitalizeFirstLetterOfEachWord(currentDay.weather[0].description); + let sunrise = new Date(currentDay.sunrise * 1000).toLocaleTimeString('en-US', {hour: 'numeric', minute: '2-digit', timeZone: timezone}); + let sunset = new Date(currentDay.sunset * 1000).toLocaleTimeString('en-US', {hour: 'numeric', minute: '2-digit', timeZone: timezone}); + let moonPhase = currentDay.moon_phase; + let dayTemp = kelvinToFahrenheit(currentDay.temp.day); + let nightTemp = kelvinToFahrenheit(currentDay.temp.night); + let eveTemp = kelvinToFahrenheit(currentDay.temp.eve); + let mornTemp = kelvinToFahrenheit(currentDay.temp.morn); + + weeklyHTML += ` +
+
+

${(i===0) ? 'Today' : day}

+

${dayMonth}

+
+
+

H: ${high}

+

L: ${low}

+
+
+ Weather icon + ${description} +
+
+

Day Avg: ${dayTemp}

+

Night Avg: ${nightTemp}

+
+
+
+ + + + ${sunrise} +
+
+ + + + ${sunset} +
+
+ +
+ `; + + } + const weeklyForecastDiv = document.querySelector('.weekly-body'); + weeklyForecastDiv.innerHTML = weeklyHTML; +} diff --git a/scripts/weather.js b/scripts/weather.js new file mode 100644 index 0000000..152b225 --- /dev/null +++ b/scripts/weather.js @@ -0,0 +1,88 @@ +import { kelvinToFahrenheit, kelvinToCelsius, capitalizeFirstLetterOfEachWord } from './unitConversions.js'; +import { updateHourly } from './updateHourly.js'; +import { updateWeekly } from './updateWeekly.js'; +import { updateCurrent } from './updateCurrent.js'; + +const weatherCodeToImageMap = { + // Thunderstorms - typically use both day and night images + 200: "/client/public/weather-both/thunder-storm-both.png", + 201: "/client/public/weather-both/thunder-storm-both.png", + 202: "/client/public/weather-both/thunder-storm-both.png", + 210: "/client/public/weather-both/thunder-storm-both.png", + 211: "/client/public/weather-both/thunder-storm-both.png", + 212: "/client/public/weather-both/thunder-storm-both.png", + 221: "/client/public/weather-both/thunder-storm-both.png", + 230: "/client/public/weather-both/thunder-storm-both.png", + 231: "/client/public/weather-both/thunder-storm-both.png", + 232: "/client/public/weather-both/thunder-storm-both.png", + + // Drizzle - typically use both day and night images + 300: "/client/public/weather-both/showers-both.png", + 301: "/client/public/weather-both/showers-both.png", + 302: "/client/public/weather-both/showers-both.png", + 310: "/client/public/weather-both/showers-both.png", + 311: "/client/public/weather-both/showers-both.png", + 312: "/client/public/weather-both/showers-both.png", + 313: "/client/public/weather-both/showers-both.png", + 314: "/client/public/weather-both/showers-both.png", + 321: "/client/public/weather-both/showers-both.png", + + // Rain - typically use both day and night images + 500: "/client/public/weather-both/rain-both.png", + 501: "/client/public/weather-both/rain-both.png", + 502: "/client/public/weather-both/rain-both.png", + 503: "/client/public/weather-both/rain-both.png", + 504: "/client/public/weather-both/rain-both.png", + 511: "/client/public/weather-both/freezing-rain-both.png", + 520: "/client/public/weather-both/showers-both.png", + 521: "/client/public/weather-both/showers-both.png", + 522: "/client/public/weather-both/showers-both.png", + 531: "/client/public/weather-both/showers-both.png", + + // Snow - typically use both day and night images + 600: "/client/public/weather-both/snow-both.png", + 601: "/client/public/weather-both/snow-both.png", + 602: "/client/public/weather-both/snow-both.png", + 611: "/client/public/weather-both/sleet-both.png", + 612: "/client/public/weather-both/sleet-both.png", + 613: "/client/public/weather-both/sleet-both.png", + 615: "/client/public/weather-both/rain-and-snow-both.png", + 616: "/client/public/weather-both/rain-and-snow-both.png", + 620: "/client/public/weather-both/snow-both.png", + 621: "/client/public/weather-both/snow-both.png", + 622: "/client/public/weather-both/snow-both.png", + + // Atmosphere - typically use both day and night images + 701: "/client/public/weather-both/fog-both.png", + 711: "/client/public/weather-both/smoke-both.png", + 721: "/client/public/weather-both/haze-both.png", + 731: "/client/public/weather-both/dust-both.png", + 741: "/client/public/weather-both/fog-both.png", + 751: "/client/public/weather-both/sand-both.png", + 761: "/client/public/weather-both/dust-both.png", + 762: "/client/public/weather-both/ash-both.png", + 771: "/client/public/weather-both/windy-both.png", + + // Clear - use night or day images + 800: ["/client/public/weather-day/sun-day.png", "/client/public/weather-night/clear-night.png"], + + // Clouds + 801: ["/client/public/weather-day/mostly-sunny-day.png", "/client/public/weather-night/mostly-clear-night.png"], + 802: ["/client/public/weather-day/partly-sunny-day.png", "/client/public/weather-night/partly-cloudy-night.png"], + 803: ["/client/public/weather-day/intermitten-clouds-day.png", "/client/public/weather-night/intermitten-clouds-night.png"], + 804: ["/client/public/weather-day/mostly-cloudy-day.png", "/client/public/weather-night/mostly-cloudy-night.png"], + }; + +// TODO: create a boolean function for weather it's day, night or in between + +export function updateWeather(weatherData, userInput) { + + // Current + updateCurrent(weatherData, weatherCodeToImageMap, userInput); + + // Hourly + updateHourly(weatherData, weatherCodeToImageMap); + + // Weekly + updateWeekly(weatherData, weatherCodeToImageMap); +}; \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..113b388 --- /dev/null +++ b/styles.css @@ -0,0 +1,382 @@ +body { + margin: 0; + padding: 0; + /* width: auto; + height: auto; */ + background-color: #D0E4F5; + font-family: 'Roboto', sans-serif; +} + +/* Main section */ +.grid-main { + display: grid; + grid-template-columns: [one] 1fr [two] minmax(200px, 60vw) [three] 1fr [four]; + grid-template-rows: [one] 100px [two] auto [three] auto [four] auto [five] 100px [six]; + grid-template-areas: '. nav .' + '. current .' + '. hourly .' + '. weekly .' + 'footer footer footer'; + grid-row-gap: 20px; +} + +.title-text { + color: #ffffff; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75), 0px 2px 4px rgba(0, 0, 0, 0.198); +} + +.body-text { + color: #ffffff; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75), 0px 2px 4px rgba(0, 0, 0, 0.507); + font-weight: bold; +} + +.title-text-setrise { + color: #ffffff; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.488), 0px 2px 4px rgba(0, 0, 0, 0.427); + white-space: nowrap; /* Prevents the text from wrapping */ + overflow: hidden; /* Hides the text that overflows the container */ + text-overflow: ellipsis; /* Adds an ellipsis when the text overflows */ + min-width: 100px; +} + +.header { + z-index: 1; + display: flex; + justify-content: space-between; + font-size: 18px; + padding: 10px; + border-bottom: 1px solid #65656526; + color: #ffffff; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75), 0px 2px 4px rgba(0, 0, 0, 0.198); + font-weight: bold; +} + + + +/* Navbar Section */ +.navbar { + grid-area: 1/1/2/4; + width: 100%; + background-color: rgba(255, 255, 255, 0.5); +} + +.navbar-content { + z-index: 1; + display: flex; + grid-area: nav; + justify-content: space-between; + align-items: center; +} + +.btn { + display: block; +} + +.btn { + display: flex; + justify-content: center; + align-items: center; + padding-left: 60px; +} + +.btn .fas { + color: #ffffff; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); + font-size: 30px; +} + +.logo { + display: flex; + align-items: center; +} + +#search-form { + width: 400px; + padding-left: 25px; +} + +#location-search { + width: 100%; + padding: 8px 10px; + box-sizing: border-box; + border: 2px solid #ccc; + border-radius: 4px; + font-size: 16px; +} + +#dropdown { + list-style-type: none; + padding: 0; + margin: 0; + background: rgba(255, 255, 255, 0.738); + position: absolute; + width: 400px; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + border-radius: 0 0 4px 4px; +} + +#dropdown div { + padding: 8px 10px; + cursor: pointer; + border-bottom: 1px solid #ccc; +} + +#dropdown div:last-child{ + border-bottom: none; +} + +#dropdown div:hover { + background-color: #d3d3d35c; +} + +.navbar-content img { + padding: 0; + margin-left: -25px; + width: auto; + height: 60px; +} + +.navbar-content h1 { + padding: 0; + margin-left: -5px; +} + +/* Current Section */ +.current { + grid-area: current; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 10px; +} + +.current-body { + display: flex; + justify-content: center; + align-items: center; + padding-top: 10px; + padding-bottom: 20px; +} + +.current-left { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.current-temp { + font-size: 50px; +} + +.current-place { + font-size: 20px; +} + +.current-description { + font-size: 17px; +} + + +/* Hourly Section */ +.hourly { + grid-area: hourly; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 10px; +} + +.hourly-body { + display: flex; + overflow-x: auto; + +} + +.scroll-hour { + margin-bottom: 20px; + padding: 10px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #ffffff; /* Pure white text */ + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75), 0px 2px 4px rgba(0, 0, 0, 0.198); + font-weight: bold; + height: 100%; +} + +/* Week forecast */ + +.weekly { + grid-area: weekly; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 10px; +} + +.weekly-body { + +} + +.day { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + border-bottom: 1px solid #65656568; + padding: 10px; +} + +.row { + display: flex; + float: row; + justify-content: space-around; + align-items: center; + row-gap: 20px; +} + +.day p { + margin: 0; + padding: 0; +} + +.day img { + display: flex; + align-items: center; + justify-content: center; + height: auto; + flex-shrink: 0; + width: 75px; +} + +.day-date { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex-shrink: 0; + width: 75px; +} + +.day-high-low { + display: flex; + flex-direction: column; + flex-shrink: 0; + width: 50px; +} + +.day-description { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex-shrink: 0; + width: 175px; + margin-left: -20px; +} + +.day-sun-set-rise { + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +.day-night { + display: flex; + flex-direction: column; + flex-shrink: 0; + width: 100px; +} + +.shadow { + filter: drop-shadow(2px 2px 2px gray); +} + +.sunrise-color { + color: #FFA500; +} + +.sunset-color { + color: #272056; +} + +.footer { + display: flex; + justify-content: center; + align-items: center; +} + +/* Effects */ +.glass-effect { + border-radius: 10px; /* Rounded corners */ + border: 1px solid rgba(255, 255, 255, 0.702); /* Subtle white border */ + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Soft shadow for depth */ +} + + +/* Phones */ +@media (max-width: 600px) { + .grid-main { + grid-template-rows: [one] auto [two] auto [three] auto [four] auto [five] 100px [six]; + grid-template-columns: [one] 1fr [two] minmax(200px, auto) [three] 1fr [four]; + } + + .day-description { + flex-shrink: 1; + } + + .current, .hourly, .weekly { + margin-left: 10px; + margin-right: 10px; + } + + .navbar-content { + flex-direction: column; + justify-content: center; + padding: 20px; + } + + .day-night { + display: none; + } + #search-form, #dropdown { + width: 100%; + } +} + +/* Tablets */ +@media (max-width: 768px) { + .grid-main { + grid-template-columns: [one] 1fr [two] minmax(6fr, 80vw) [three] 1fr [four]; + } + #search-form, #dropdown { + width: 300px; + } +} + +/* desktops */ +@media (max-width: 992px) { + .grid-main { + grid-template-columns: [one] 1fr [two] minmax(500px, 80vw) [three] 1fr [four]; + } + + #search-form, #dropdown { + width: 250px; + } + + .day-night { + display: none; + } + +} + +/* desktops */ +@media (min-width: 992px) { + .grid-main { + grid-template-columns: [one] 1fr [two] minmax(700px, 6fr) [three] 1fr [four]; + } +} + +/* Extra large devices */ +@media (min-width: 1201px) { + .grid-main { + grid-template-columns: [one] 1fr [two] 1000px [three] 1fr [four]; + } +} \ No newline at end of file