diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1170717..0000000 --- a/.gitignore +++ /dev/null @@ -1,136 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* diff --git a/cloudy.png b/cloudy.png new file mode 100644 index 0000000..4f162e4 Binary files /dev/null and b/cloudy.png differ diff --git a/fog.png b/fog.png new file mode 100644 index 0000000..f67ec82 Binary files /dev/null and b/fog.png differ diff --git a/index.html b/index.html index 09c6dc0..92c1821 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,98 @@ + - Simple HTML Page - - + + Weather Forecast App + +
+
+ + +
+ +
+
+
+

Kyiv

+

Ukraine

+
+ +
+

23°C

+

Clear

+
+
+ +
+ sunny +
+ +
+
+

Wind: 13 km/h

+

Humidity: 56%

+
+
+
+ + + +
+ View Weather Report +
+
+ + + \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..453b7f8 --- /dev/null +++ b/main.js @@ -0,0 +1,119 @@ +const API_KEY = 'FF7ZYNQQ68ZK9BB677FAW6CTX'; +const BASE_URL = 'https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/'; + +const searchBtn = document.querySelector('.search-btn'); +const cityInput = document.getElementById('cityInput'); +const cityElem = document.querySelector('.city'); +const countryElem = document.querySelector('.country'); +const tempElem = document.querySelector('.temp h1'); +const conditionElem = document.querySelector('.condition'); +const windElem = document.querySelector('.extra-info p:nth-child(1)'); +const humidityElem = document.querySelector('.extra-info p:nth-child(2)'); +const forecastCarousel = document.querySelector('.forecast-carousel'); +const weatherIcon = document.querySelector('.current-weather-icon img'); + +// Створення та приховування попапу +const popup = document.createElement('div'); +popup.classList.add('popup'); +document.body.appendChild(popup); +popup.style.display = 'none'; + +async function fetchWeather(city) { + const url = `${BASE_URL}${city}?unitGroup=metric&key=${API_KEY}&contentType=json&include=days`; + + // Відправляється асинхронний запит на сервер, отримується відповідь і перетворюється в об'єкт JSON. + const res = await fetch(url); + const data = await res.json(); + forecastData = data.days.slice(0, 10); // Зберігається тільки перших 10 днів прогнозу + + // Збереження прогнозу і назви міста + localStorage.setItem('weatherForecast', JSON.stringify(forecastData)); + localStorage.setItem('forecastCity', data.resolvedAddress); + + updateMainWeather(data); + updateForecast(); +} + +function updateMainWeather(data, dayIndex = 0) { + const day = forecastData[dayIndex]; // Вибирається день (за замовчуванням -- сьогодні) + cityElem.textContent = data.resolvedAddress.split(',')[0]; + countryElem.textContent = data.resolvedAddress.split(',')[1]?.trim() || ''; + tempElem.textContent = `${Math.round(day.temp)}°C`; + conditionElem.textContent = day.conditions; + windElem.textContent = `Wind: ${day.windspeed} km/h`; + humidityElem.textContent = `Humidity: ${day.humidity}%`; + weatherIcon.src = getIcon(day.icon); +} + +function updateForecast() { + forecastCarousel.innerHTML = ''; + forecastData.forEach((day, index) => { + const forecastDay = document.createElement('div'); + forecastDay.classList.add('forecast-day'); + forecastDay.innerHTML = ` +

${index === 0 ? 'Today' : new Date(day.datetime).toLocaleDateString('en-US', { weekday: 'short' })}

+
${day.icon}
+

${Math.round(day.temp)}°C

+

${Math.round(day.tempmin)}°C

+ `; + + // Оновлює головну панель з погодою на вибраний день + forecastDay.addEventListener('click', () => { + updateMainWeather({ resolvedAddress: `${cityElem.textContent}, ${countryElem.textContent}` }, index); + }); + + // Клік по температурі відкриває попап з деталями дня + forecastDay.querySelector('.temp-click').addEventListener('click', (e) => { + e.stopPropagation(); + const idx = e.target.dataset.index; + showPopup(forecastData[idx]); + }); + + forecastCarousel.appendChild(forecastDay); + }); +} + +function getIcon(condition) { + const icons = { + 'clear-day': 'sunny.png', + 'rain': 'rainy.png', + 'cloudy': 'cloudy.png', + 'partly-cloudy-day': 'cloudy.png', + 'snow': 'snow.png', + 'fog': 'fog.png', + 'wind': 'windy.png' + }; + return icons[condition] || 'sunny.png'; +} + +// Відображення попапу з деталями про день +function showPopup(day) { + popup.innerHTML = ` + + `; + popup.style.display = 'block'; +} + +searchBtn.addEventListener('click', () => { + const city = cityInput.value.trim(); + if (city) fetchWeather(city); +}); + +cityInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter'){ + const city = cityInput.value.trim(); + if (city) fetchWeather(city); + } +}); + +// Load default city +fetchWeather('Kyiv'); \ No newline at end of file diff --git a/rainy.png b/rainy.png new file mode 100644 index 0000000..e758f99 Binary files /dev/null and b/rainy.png differ diff --git a/report.html b/report.html new file mode 100644 index 0000000..43a60ea --- /dev/null +++ b/report.html @@ -0,0 +1,56 @@ + + + + + + Weather Report + + + + + + + +
+

Звіт про погоду

+
+ ← Back to Weather App +
+ + + + + \ No newline at end of file diff --git a/snow.png b/snow.png new file mode 100644 index 0000000..ad94c7f Binary files /dev/null and b/snow.png differ diff --git a/src/main.js b/src/main.js deleted file mode 100644 index e69de29..0000000 diff --git a/style.css b/style.css new file mode 100644 index 0000000..b067c21 --- /dev/null +++ b/style.css @@ -0,0 +1,343 @@ +:root { + --background-color: #f0f0f0; + + --box-background-color: #ffffff; + --box-shadow-color: #0000001a; + + --cityInput-border-color: #cccccc; + --cityInput-border-color-focus: #306cdb; + --cityInput-text-color-focus: #000000; + + --search-btn-background-color: #306cdb; + --search-btn-text-color: #ffffff; + --search-btn-background-color-hover: #477de2; + + --country-text-color: #2b3a40; + --extra-info-text-color: #000000; + + --forecast-day-border-color: #cccccc; + --forecast-day-background-color: #ffffff; + + --popup-background-color: #ffffff; + --popup-border-color: #cccccc; + --popup-content-btn-background-color: #306cdb; + --popup-content-btn-text-color: #ffffff; + --popup-content-btn-background-color-hover: #477de2; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +body { + background: var(--background-color); + display: flex; + justify-content: center; + padding: 20px; +} + +/* -------------------------------------- */ +.box { + background: var(--box-background-color); + padding: 20px; + border-radius: 10px; + max-width: 500px; + width: 100%; + box-shadow: 0 0 20px var(--box-shadow-color); +} + +/* SEARCH ROW---------------------------- */ +.search-row { + display: flex; + gap: 5px; + margin-bottom: 20px; +} + +#cityInput { + flex: 1; + padding: 0 10px; + border: 1px solid var(--cityInput-border-color); + border-radius: 8px; + transition: border-color 0.2s; +} + +#cityInput:focus { + outline: none; + border-color: var(--cityInput-border-color-focus); + color: var(--cityInput-text-color-focus); +} + +.search-btn { + padding: 8px 20px; + background: var(--search-btn-background-color); + color: var(--search-btn-text-color); + border: none; + border-radius: 8px; + cursor: pointer; +} + +.search-btn:hover { + background: var(--search-btn-background-color-hover); +} + +/* MAIN ABOUT WEATHER-------------------- */ +.weather-main { + display: grid; + grid-template-columns: 1fr auto; + grid-template-areas: + "left icon" + "left right"; + gap: 20px; + align-items: start; +} + +.left-panel { + grid-area: left; +} + +.city { + font-size: 24px; + font-weight: 500; +} + +.country { + color: var(--country-text-color); + font-size: 16px; +} + +.temp h1 { + font-size: 36px; + font-weight: 500; +} + +.condition { + margin-bottom: 5px; + font-size: 16px; +} + +.right-panel { + grid-area: right; + margin-bottom: 15px; +} + +.current-weather-icon img { + width: 80px; + height: 80px; + float: right; + object-fit: contain; +} + +.current-weather-icon { + grid-area: icon; +} + +.right-panel, +.current-weather-icon img { + margin-right: 50px; +} + +.extra-info { + display: flex; + flex-direction: column; + gap: 5px; + font-size: 14px; + font-weight: 400; + color: var(--extra-info-text-color); + align-items: left; +} + +/* CAROUSEL------------------------------ */ +.forecast-carousel { + display: flex; + flex-direction: row; + gap: 20px; + overflow-x: auto; /*дозволяє горизонтальну прокрутку, якщо вміст ширший за контейнер*/ + scroll-snap-type: x mandatory; /*snap (прилипання) працює по осі X*/ + scroll-padding: 1rem; + position: relative; + block-size: auto; + padding-bottom: 12px; +} + +.forecast-day { + scroll-snap-align: start; /*цей елемент має "прилипнути" до початку області прокрутки (start — це край каруселі)*/ + flex: 0 0 calc(100% / 4 - 1rem); /*показує 4 елементи на екрані */ + border: 1px solid var(--forecast-day-border-color); + background-color: var(--forecast-day-background-color); + padding: 10px; + border-radius: 10px; + text-align: center; + min-width: 0; /* без цього елементи можуть не стискатися як треба, і прокрутка буде працювати неправильно */ +} + +.forecast-day .icon img { + width: 35px; + height: 35px; + object-fit: contain; + margin: 5px; +} + +.low-temp { + color: #666; + font-size: 14px; +} + +/* POPUP----------------------------- */ +.popup { + position: fixed; + top: 20%; + left: 50%; + transform: translateX(-50%); + background-color: var(--popup-background-color); + border: 1px solid var(--popup-border-color); + padding: 20px; + z-index: 9999; + box-shadow: 0 0 15px var(--box-shadow-color); + border-radius: 10px; + max-width: 300px; +} + +.popup-content-btn { + margin-top: 10px; + padding: 6px 12px; + background: var(--popup-content-btn-background-color); + color: var(--popup-content-btn-text-color); + border: none; + border-radius: 4px; + cursor: pointer; +} + +.popup-content-btn:hover { + background-color: var(--popup-content-btn-background-color-hover); +} + +/* REPORT----------------------------- */ +.report { + text-align: center; + margin-top: 20px; +} + +.report-backward { + display: inline-block; + margin-top: 10px; + padding: 8px 16px; + background-color: var(--search-btn-background-color); + color: white; + border-radius: 8px; + text-decoration: none; + font-weight: 500; +} + +.report-backward:hover { + background-color: var(--search-btn-background-color-hover); +} + +#reportTitle{ + text-align: center; +} + +#pivot-table-container{ + margin-top:20px; +} + +/* MEDIA----------------------------- */ +@media (max-width: 768px) { + body { + background: var(--box-background-color); + } + + .box { + box-shadow: none; + } + + .city { + font-size: 30px; + font-weight: 600; + } + + .country { + font-size: 18px; + } + + .temp h1 { + font-size: 38px; + } + + .condition { + font-size: 18px; + } + + .weather-main { + display: grid; + grid-template-columns: 1fr auto; + grid-template-areas: + "left icon" + "left ." + "right ."; + gap: 10px; + } + + .left-panel { + align-items: flex-start; + } + + .right-panel { + align-items: flex-start; + } + + .current-weather-icon { + justify-self: end; + } + + .current-weather-icon img { + width: 90px; + height: 90px; + object-fit: contain; + } + + .extra-info { + margin-top: 10px; + font-size: 14px; + } + + .forecast-day { + flex: 0 0 calc(100% / 3 - 1rem); + } +} + +@media (max-width: 371px) { + body { + min-width: 350px; + overflow-x: auto; + } + + .box { + min-width: 350px; + } + + .search-row { + flex-direction: column; + } + + #cityInput { + padding: 5px 10px; + } + + .extra-info { + margin-top: 10px; + font-size: 14px; + } + + .forecast-day p { + font-size: 16px; + } + + .forecast-day .icon img { + width: 30px; + height: 30px; + object-fit: contain; + } +} \ No newline at end of file diff --git a/style/style.css b/style/style.css deleted file mode 100644 index e69de29..0000000 diff --git a/sunny.png b/sunny.png new file mode 100644 index 0000000..74feb0e Binary files /dev/null and b/sunny.png differ diff --git a/windy.png b/windy.png new file mode 100644 index 0000000..972b98a Binary files /dev/null and b/windy.png differ