diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6b665aa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} diff --git a/img/close.png b/img/close.png new file mode 100644 index 0000000..8ed3142 Binary files /dev/null and b/img/close.png differ diff --git a/img/close1.png b/img/close1.png new file mode 100644 index 0000000..fd23bce Binary files /dev/null and b/img/close1.png differ diff --git a/img/cloudy.png b/img/cloudy.png new file mode 100644 index 0000000..c6d9d7c Binary files /dev/null and b/img/cloudy.png differ diff --git a/img/cloudyBigger.png b/img/cloudyBigger.png new file mode 100644 index 0000000..4222575 Binary files /dev/null and b/img/cloudyBigger.png differ diff --git a/img/cloudyBiggest.png b/img/cloudyBiggest.png new file mode 100644 index 0000000..4ce9402 Binary files /dev/null and b/img/cloudyBiggest.png differ diff --git a/img/rain.png b/img/rain.png new file mode 100644 index 0000000..e3e60f3 Binary files /dev/null and b/img/rain.png differ diff --git a/img/rainBigger.png b/img/rainBigger.png new file mode 100644 index 0000000..b074b14 Binary files /dev/null and b/img/rainBigger.png differ diff --git a/img/rainBiggest.png b/img/rainBiggest.png new file mode 100644 index 0000000..1ddb440 Binary files /dev/null and b/img/rainBiggest.png differ diff --git a/img/sun.png b/img/sun.png new file mode 100644 index 0000000..dce795b Binary files /dev/null and b/img/sun.png differ diff --git a/img/sunBigger.png b/img/sunBigger.png new file mode 100644 index 0000000..e33a838 Binary files /dev/null and b/img/sunBigger.png differ diff --git a/img/sunBiggest.png b/img/sunBiggest.png new file mode 100644 index 0000000..597a5e1 Binary files /dev/null and b/img/sunBiggest.png differ diff --git a/index.html b/index.html index 09c6dc0..b69e00c 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,92 @@ - - + + + - Simple HTML Page - + Weather app + + - + +
+
+ +
+
+
+

+ +
+ + +
+
+
+

+ +
+
+
Wind: + +
+
Humidity: + +
+
+
+
+
+ +
+ +
+
+ \ No newline at end of file diff --git a/src/main.js b/src/main.js index e69de29..c7784db 100644 --- a/src/main.js +++ b/src/main.js @@ -0,0 +1,208 @@ +let daysData = [] +let currentCity +let currentAddress +let isFirstLoad = true + +async function loadWeather(city) { + const apiKey = '42X3PN2U3BCYZV8FFRWCV3UVA' + const resp = await fetch(`https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/${encodeURIComponent(city)}?unitGroup=metric&key=${apiKey}&contentType=json`) + if (!resp.ok) { + alert('City not found') + return + } + const data = await resp.json() + daysData = data.days.slice(0, 10) + + currentCity = data.address + currentAddress = data.resolvedAddress + + isFirstLoad = true + updateDetailedInfo(0) + renderCarousel() + console.log(data) +} + +function updateDetailedInfo(index) { + const detailedCity = document.querySelector('.detailed-city') + const detailedCountry = document.querySelector('.detailed-country') + const detailedTemp = document.querySelector('.detailed-temp') + const detailedDesc = document.querySelector('.detailed-desc') + const windElem = document.querySelector('.wind') + const humidityElem = document.querySelector('.humidity') + const detailedIcon = document.querySelector('.detailed-icon') + + const day = daysData[index] + detailedCity.textContent = currentCity + detailedCountry.textContent = currentAddress + detailedTemp.textContent = Math.round(day.temp) + '°C' + detailedDesc.textContent = day.conditions + windElem.textContent = Math.round(day.windspeed) + 'km/h' + humidityElem.textContent = day.humidity + '%' + createDetailedIconByConditions(day.icon, detailedIcon) + setUpPopupListener(index) +} + +function renderCarousel() { + const carousel = document.querySelector('.carousel') + carousel.innerHTML = '' + daysData.forEach((day, index) => { + const li = document.createElement('li') + li.className = 'carousel-item' + + const title = document.createElement('h5') + title.className = 'carousel-title' + title.textContent = `${index === 0 ? 'Today' : new Date(day.datetime).toLocaleDateString('en-EN', { weekday: 'short' })}` + + const dateStr = day.datetime + const date = new Date(dateStr) + + const formatted = date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short'}) + + const subTitle = document.createElement('span') + subTitle.className = 'carousel-subtitle' + subTitle.textContent = formatted + + + const icon = document.createElement('picture') + icon.className = 'carousel-icon' + + let currentPicture = '' + if (day.icon.includes('rain')) currentPicture = 'rain' + if (day.icon.includes('cloud')) currentPicture = 'cloudy' + if (day.icon.includes('clear')) currentPicture = 'sun' + + const source = document.createElement('source') + source.setAttribute('srcset', `./img/${currentPicture}Bigger.png`) + source.setAttribute('media', '(min-width: 600px)') + + const img = document.createElement('img') + img.setAttribute('src', `./img/${currentPicture}.png`) + + icon.append(source, img) + + const maxTemperature = document.createElement('span') + maxTemperature.className = 'max-temp' + maxTemperature.textContent = Math.round(day.tempmax) + '°C' + + const minTemperature = document.createElement('span') + minTemperature.className = 'min-temp' + minTemperature.textContent = Math.round(day.tempmin) + '°C' + + li.append(title, subTitle, icon, maxTemperature, minTemperature) + + li.addEventListener('click', () => { + updateDetailedInfo(index) + document.querySelectorAll('.carousel-item').forEach(item => { item.classList.remove('current')}) + li.classList.add('current') + }) + + carousel.appendChild(li) + }) + + if (isFirstLoad) { + const firstItem = carousel.querySelector('.carousel-item') + if (firstItem) { + firstItem.classList.add('current') + } + isFirstLoad = false + } +} + +function createDetailedIconByConditions(icon, detailedIcon) { + let currentPicture = '' + if (icon.includes('rain')) currentPicture = 'rain' + if (icon.includes('cloud')) currentPicture = 'cloudy' + if (icon.includes('clear')) currentPicture = 'sun' + + detailedIcon.innerHTML ='' + + const sourceOne = document.createElement('source') + sourceOne.setAttribute('srcset', `./img/${currentPicture}Biggest.png`) + sourceOne.setAttribute('media', '(min-width: 600px)') + + const sourceTwo = document.createElement('source') + sourceTwo.setAttribute('srcset', `./img/${currentPicture}Bigger.png`) + + const img = document.createElement('img') + img.setAttribute('src', `./img/${currentPicture}.png`) + + detailedIcon.append(sourceOne, sourceTwo, img) +} + +const searchInput = document.querySelector('.search-input') +const searchButton = document.querySelector('.search-button') +searchButton.addEventListener('click', () => { + const value = searchInput.value.trim(); + if (value !== '') { + loadWeather(value) + searchInput.value = ''; + } else { + searchInput.focus(); + } +}) +searchInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + searchButton.click(); + } +}) + +function setUpPopupListener(index) { + const detailedTemp = document.querySelector('.detailed-temp') + let popup = document.querySelector(".popup") + let closePopupButton = document.querySelector(".popup-close") + let popupContent = document.querySelector(".popup-content") + + detailedTemp.addEventListener('click', () => { + popup.classList.add('popup-active') + popupContent.classList.add('popup-content-active') + setUpPopupContent(index) + }) + + closePopupButton.addEventListener('click', () => { + popup.classList.remove('popup-active') + popupContent.classList.remove('popup-content-active') + }) +} + +function setUpPopupContent(index) { + const popupCity = document.querySelector('.popup-city') + const popupDate = document.querySelector('.popup-date') + const popupDescription = document.querySelector('.popup-description') + const popupMaxTemp = document.querySelector('.max-real') + const popupMinTemp = document.querySelector('.min-real') + const popupMaxFeel = document.querySelector('.max-feels') + const popupMinFeel = document.querySelector('.min-feels') + const popupSunrise = document.querySelector('.sunrise') + const popupSunset = document.querySelector('.sunset') + const popupVisibility = document.querySelector('.visibility') + const popupPressure = document.querySelector('.pressure') + + const day = daysData[index] + + popupCity.textContent = currentAddress + const dateStr = day.datetime + const date = new Date(dateStr) + + const formatted = date.toLocaleDateString('en-GB', { day: 'numeric', month: 'long' }) + popupDate.textContent = formatted + popupDescription.textContent = day.description + popupMaxTemp.textContent = Math.round(day.tempmax) + '°C' + popupMinTemp.textContent = Math.round(day.tempmin) + '°C' + popupMaxFeel.textContent = Math.round(day.feelslikemax) + '°C' + popupMinFeel.textContent = Math.round(day.feelslikemin) + '°C' + const time = day.sunrise + const [hours, minutes] = time.split(":") + const shortTime = `${hours}:${minutes}` + popupSunrise.textContent = shortTime + + const timeTwo = day.sunset + const [hoursTwo, minutesTwo] = timeTwo.split(":") + const shortTimeTwo = `${hoursTwo}:${minutesTwo}` + popupSunset.textContent = shortTimeTwo + + popupVisibility.textContent = day.visibility + 'km' + popupPressure.textContent = day.pressure + 'hPa' +} + +loadWeather('Kyiv') \ No newline at end of file diff --git a/style/style.css b/style/style.css index e69de29..9c96500 100644 --- a/style/style.css +++ b/style/style.css @@ -0,0 +1,486 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Montserrat", sans-serif; +} + +a { + text-decoration: none; +} + +li { + list-style-type: none; +} + +.container { + width: 100%; + height: 100vh; + display: flex; + justify-content: center; + background-color: rgba(238, 239, 238, 255); + padding: 20px; +} + +.weather-panel { + width: 100%; + height: fit-content; + display: flex; + flex-direction: column; + background-color: white; + border-radius: 5px; +} + +.search { + display: flex; + justify-content: space-between; + align-items: center; + padding: 30px; +} + +.search-input { + width: 80%; + height: 50px; + border-radius: 7px; + padding: 0 10px; + border: 2px solid rgba(245, 245, 246, 255); + color: rgba(173, 173, 173, 255); + font-size: 18px; +} + +.search-input::placeholder { + color: rgba(173, 173, 173, 255); +} + +.search-input:focus { + outline: none; + border-color: rgba(66, 131, 202, 255); +} + +.search-button { + width: 15%; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + background-color: rgba(47, 108, 220, 255); + color: white; + border: none; + cursor: pointer; + font-size: 20px; + padding: 20px 0; + position: relative; +} + +.search-button::after { + content: attr(data-tooltip); + position: absolute; + top: -30px; + left: 9%; + transform: translateY(10px) scale(0.5); + background-color: rgba(78, 164, 250, 255); + color: white; + font-size: 14px; + font-weight: bold; + padding: 5px 15px; + border-radius: 8px; + white-space: nowrap; + opacity: 0; + transition: all 0.3s ease; +} + +.search-button:hover::after { + opacity: 1; + transform: scale(1); +} + +.search-button:hover { + background-color: rgba(30, 90, 170, 255); +} + +.detailed-panel { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + padding: 0px 30px; +} + +.detailed-info { + display: flex; + flex-direction: column; + align-items: start; + gap: 10px; +} + +.detailed-temp{ + font-size: 40px; + font-weight: bold; + cursor: pointer; +} + +.detailed-temp:hover { + color: rgba(78, 164, 250, 255); + transition: color 0.3s ease; +} + +.detailed-city { + font-size: 32px; +} + +.detailed-country { + font-size: 22px; + color: rgba(92, 103, 106, 255); +} + +.detailed-desc{ + font-size: 20px; +} + +.detailed-extra { + font-size: 20px; +} + +.extra{ + display: flex; + flex-direction: column; + align-items: end; + min-width: 211px; +} + +.carousel { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scroll-snap-type: x mandatory; + display: flex; + justify-content: space-between; + align-items: center; + padding: 30px; + >li { + scroll-snap-align: center; + } +} +.carousel::-webkit-scrollbar { + height: 8px; +} + +.carousel::-webkit-scrollbar-track { + background: transparent; +} + +.carousel::-webkit-scrollbar-thumb { + background-color: #dcdfe6; + border-radius: 8px; +} + +.carousel::-webkit-scrollbar-thumb:hover { + background-color: #c0c4cc; +} + +.carousel-item { + flex: 1, 1; + min-width: 150px; + border-radius: 10px; + margin-right: 20px; + padding: 20px 30px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + border: 1px solid rgba(221, 221, 223, 255); + cursor: pointer; +} + +.carousel-item:hover { + background-color: rgba(245, 245, 246, 255); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(201, 201, 203, 255); +} + +.current { + background-color: rgba(245, 245, 246, 255); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(201, 201, 203, 255); +} + +.carousel-item:last-child { + margin-right: 0; +} + +.carousel-title { + font-size: 26px; + font-weight: bold; +} + +.carousel-subtitle { + font-size: 22px; + color: rgba(92, 103, 106, 255); +} + +.max-temp { + font-size: 20px; +} + +.min-temp { + font-size: 18px; + color: rgba(92, 103, 106, 255); +} + +.popup { + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + background-color: rgba(0, 0, 0, 0.6); + visibility: hidden; + opacity: 0; + transition: all 1s 0s; +} + +.popup-active { + visibility: visible; + opacity: 1; +} + +.popup-content { + background: #FFFFFF; + width: 50%; + height: 90%; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.12), 0px 1px 1px rgba(0, 0, 0, 0.14), 0px 2px 1px rgba(0, 0, 0, 0.2); + border-radius: 20px; + padding: 30px; + position: relative; + transition: all 1s 0s; + opacity: 0; + transform: translate(0px, -100%) +} + +.popup-content-active { + opacity: 1; + transform: translate(0px, 0px) +} + +.popup-close { + background-color: white; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); + width: 30px; + height: 30px; + background-image: url('../img/close.png'); + fill: black; + background-position: center; + background-repeat: no-repeat; + position: absolute; + top: 8px; + right: 8px; + cursor: pointer; +} + +.popup-close:hover { + background-image: url('../img/close1.png'); +} + +.popup-close:active { + transform: scale(1.1); +} + +.popup-information { + display: flex; + flex-direction: column; + align-items: center; + gap: 30px; +} + +.popup-city{ + font-size: 26px; + font-weight: bold; +} + +.popup-date { + font-size: 24px; + color: rgba(92, 103, 106, 255); +} + +.popup-description { + font-size: 16px; + font-style: italic; +} + +.popup-temp { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; +} + +.popup-temp-item { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 5px; +} + +.popup-real { + font-size: 18px; + font-weight: bold; +} + +.popup-feels-like { + font-size: 16px; + font-weight: bold; + font-style: italic; + color: rgba(92, 103, 106, 255); +} + +.popup-extra { + font-size: 20px; +} + +@media (max-width: 1024px) { + .popup-city { + font-size: 22px; + } +} + +@media (max-width: 900px) { + .popup-city { + font-size: 18px; + } + + .popup-real { + font-size: 16px; + } + + .popup-feels-like { + font-size: 14px; + } + + .popup-extra { + font-size: 16px; + } + + .popup-description { + font-size: 14px; + } + + .popup-date { + font-size: 16px; + } +} + +@media (max-width: 768px) { + .popup-city { + font-size: 16px; + } +} + +@media (max-width: 600px) { + .search-button { + width: 20%; + } + + .search-input { + width: 75%; + } + + .carousel-item { + min-width: 100px; + } + + .weather-panel .detailed-panel:first-child { + margin-bottom: 20px; + } + + .popup-real { + font-size: 14px; + } + + .popup-feels-like { + font-size: 12px; + } + + .popup-description { + font-size: 12px; + } + + .popup-date { + font-size: 16px; + } + + .popup-extra { + font-size: 16px; + } + + .popup-information { + gap: 20px; + } +} + +@media (max-width: 500px) { + .search-button { + font-size: 18px; + } + + .extra { + margin-top: 20px; + align-items: start; + } +} + +@media (max-width: 425px) { + .search-button { + width: 25%; + } + + .search-input { + width: 70%; + } + + .popup-extra { + font-size: 14px; + } +} + +@media (max-width: 400px) { + .detailed-info { + align-items: start; + } + + .popup-extra { + font-size: 12px; + } +} + +@media (max-width: 375px) { + .search-button { + width: 30%; + } + + .search-input { + width: 65%; + } + + .popup-city { + font-size: 14px; + } + + .popup-date { + font-size: 14px; + } + .popup-real { + font-size: 12px; + } + + .popup-feels-like { + font-size: 10px; + } +} \ No newline at end of file