diff --git a/assets/svg/close.svg b/assets/svg/close.svg new file mode 100644 index 0000000..4f9fb5e --- /dev/null +++ b/assets/svg/close.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/svg/settings.svg b/assets/svg/settings.svg new file mode 100644 index 0000000..4217564 --- /dev/null +++ b/assets/svg/settings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/css/style.css b/css/style.css index dd0df23..3cc511f 100644 --- a/css/style.css +++ b/css/style.css @@ -108,7 +108,9 @@ body { .slider-icon, .change-quote, .volume-icon, -.li-button { +.li-button, +.settings-button, +.settings-close { width: 32px; aspect-ratio: 1; background-size: 32px 32px; @@ -126,7 +128,9 @@ body { .slider-icon:hover, .change-quote:hover, .volume-icon:hover, -.li-button:hover { +.li-button:hover, +.settings-button:hover, +.settings-close:hover { opacity: 1; } @@ -134,7 +138,9 @@ body { .slider-icon:active, .change-quote:active, .volume-icon:active, -.li-button:active { +.li-button:active, +.settings-button:active, +.settings-close:active { border: 0; outline: 0; transform: scale(1.1); @@ -361,6 +367,7 @@ body { } .footer { + position: relative; display: flex; flex-direction: column; justify-content: flex-end; @@ -384,6 +391,111 @@ body { min-height: 20px; } +.settings-button { + background-image: url("../assets/svg/settings.svg"); + background-size: 32px; + width: 32px; + aspect-ratio: 1; + margin-right: 20px; + margin-left: auto; +} + +.settings-container { + display: flex; + justify-content: center; + align-items: center; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100vh; + z-index: 999998; + background-color: rgba(0, 0, 0, .3); + overflow-y: auto; + visibility: hidden; + opacity: 0; + transition: opacity .6s, visibility .6s; +} + +.settings { + position: relative; + /* popup size */ + width: 500px; + padding: 45px; + z-index: 1; + margin: 30px 15px; + background-color: #F2F2F7; + border-radius: 10px; + box-shadow: 0px 0px 17px -7px rgba(34, 60, 80, 0.2); + display: flex; + flex-direction: column; + gap: 24px; + color: black; +} + +.settings-close { + position: absolute; + /* button position */ + top: 10px; + right: 10px; + background-size: 25px; + background-image: url("../assets/svg/close.svg"); +} + +.settings-container.open { + visibility: visible; + opacity: 1; +} + +.settings-language, +.settings-photo, +.settings-hide tbody { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 15px; + background-color: white; + border-radius: 10px; + height: 50px; +} + +select { + font-size: 16px; + color: #8A8A8E; +} + +.settings-hide tbody { + flex-direction: column; + height: unset; + justify-content: unset; +} + +.settings-hide tbody tr { + width: 100%; + height: 50px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #B9B9BB; +} + +.settings-hide tbody tr:last-child { + border: none; +} + +.settings-hide caption { + color: #8A8A8E; + margin-bottom: 5px; + padding-left: 10px; + text-align: left; +} + +.hidden { + transition: 0.3s all ease-out; + opacity: 0; + position: absolute; +} + @media (max-width: 768px) { .time { min-height: 80px; @@ -403,4 +515,68 @@ body { font-size: 32px; padding: 5px; } +} + + + +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #E9E9EA; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: var(--accent-color) +} + +input:focus + .slider { + box-shadow: 0 0 1px #34C759; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; } \ No newline at end of file diff --git a/index.html b/index.html index 18e6c3c..bce5346 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ - momentum + Momentum @@ -58,11 +58,104 @@ +
+
+
+ +
+ Language + +
+
+ Photo source + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hidden elements
Time + +
Date + +
Greeting + +
Quotes + +
Weather + +
Player + +
To-Do list + +
+
+
\ No newline at end of file diff --git a/js/audio.js b/js/audio.js index b986c94..b030035 100644 --- a/js/audio.js +++ b/js/audio.js @@ -163,10 +163,7 @@ function volumeDown() { volumeSlider.addEventListener("input", updateVolume); volumeButton.addEventListener("click", muteAudio); - - -// hotkeys -document.addEventListener("keydown", (event) => { +export function onKeyDownEvent(event) { if (event.code == "KeyM") { muteAudio(); } else if (event.code == "KeyK") { @@ -180,4 +177,14 @@ document.addEventListener("keydown", (event) => { } else if (event.code == "ArrowDown") { volumeDown(); } -}) \ No newline at end of file +} + +createAudioList(); + +playAudioButton.addEventListener("click", playAudio) + +nextAudioButton.addEventListener("click", playNext); +prevAudioButton.addEventListener("click", playPrev); + +updateAudioTime(); +updateVolume(); \ No newline at end of file diff --git a/js/greeting.js b/js/greeting.js index 8a8ccb7..9dee15b 100644 --- a/js/greeting.js +++ b/js/greeting.js @@ -1,4 +1,6 @@ -import { greetingTranslation, lang } from "./translate.js"; +import { state } from "./settings.js"; +import { greetingTranslation } from "./translate.js"; + const greetingDiv = document.querySelector(".greeting"); export const inputName = document.querySelector(".name"); @@ -12,12 +14,12 @@ export function getGreeting() { const date = new Date(); const hours = date.getHours(); if (hours > 5 && hours < 12) { - return greetingTranslation[lang][0]; + return greetingTranslation[state.language][0]; } else if (hours > 11 && hours < 18) { - return greetingTranslation[lang][1]; + return greetingTranslation[state.language][1]; } else if (hours > 17 && hours < 24) { - return greetingTranslation[lang][2]; + return greetingTranslation[state.language][2]; } else { - return greetingTranslation[lang][3]; + return greetingTranslation[state.language][3]; } } \ No newline at end of file diff --git a/js/hide.js b/js/hide.js new file mode 100644 index 0000000..1dce268 --- /dev/null +++ b/js/hide.js @@ -0,0 +1,16 @@ +import { state } from "./settings.js"; + +const blocks = ["time", "date", "greeting-container", "quote-container", "weather", "player", "todo-list"] + +export function updateHiddenElements() { + const toHide = state.blocks; + for (let className of blocks) { + const element = document.querySelector(`.${className}`); + element.classList.remove("hidden"); + } + + for (let className of toHide) { + const element = document.querySelector(`.${className}`); + element.classList.add("hidden"); + } +} \ No newline at end of file diff --git a/js/index.js b/js/index.js index 4e3a36d..30de0e8 100644 --- a/js/index.js +++ b/js/index.js @@ -1,49 +1,23 @@ import { showTime } from './time.js'; +import { state } from './settings.js'; import { inputName } from './greeting.js'; import { prevSlideButton, nextSlideButton, setRandomNum, slideNext, slidePrev, setBg } from './slider.js'; import { setLocalStorage, getLocalStorage } from './localStorage.js'; import { setQuote, changeQuoteButton } from './quotes.js'; import { playAudio, playAudioButton, createAudioList, playNext, playPrev, prevAudioButton, nextAudioButton, updateAudioTime, updateVolume } from './audio.js'; -import { cityPlaceholderTranslation, namePlaceholderTranslation, lang, startCityTranslation } from './translate.js'; +import { cityPlaceholderTranslation, namePlaceholderTranslation, startCityTranslation } from './translate.js'; import { inputCity, getWeather } from './weather.js'; +import { updateHiddenElements } from './hide.js'; +import { applySettings } from './updateSettings.js'; -// первый вызов цикличного счетчика времени -showTime(); - -// случайный выбор фоновой картинки и первый запуск фона +// slider setRandomNum(1, 20); setBg(); - -// управление кнопками слайдера nextSlideButton.addEventListener("click", slideNext); prevSlideButton.addEventListener("click", slidePrev); - -// сохранение в localstorage +// localstorage window.addEventListener("beforeunload", setLocalStorage); window.addEventListener("load", getLocalStorage); - -// первый запуск погоды и ожидание изменений -inputCity.value = startCityTranslation[lang]; -getWeather(); - -inputCity.addEventListener("change", getWeather); - -// quotes -setQuote(); -changeQuoteButton.addEventListener("click", setQuote); - -// audio -createAudioList(); - -playAudioButton.addEventListener("click", playAudio) - -nextAudioButton.addEventListener("click", playNext); -prevAudioButton.addEventListener("click", playPrev); - -updateAudioTime(); -updateVolume(); - - // translation -inputName.placeholder = namePlaceholderTranslation[lang]; -inputCity.placeholder = cityPlaceholderTranslation[lang]; +inputName.placeholder = namePlaceholderTranslation[state.language]; +inputCity.placeholder = cityPlaceholderTranslation[state.language]; diff --git a/js/quotes.js b/js/quotes.js index bdc1f18..95f2c39 100644 --- a/js/quotes.js +++ b/js/quotes.js @@ -1,5 +1,5 @@ import { getRandomNum } from "./slider.js"; -import { lang } from "./translate.js"; +import { state } from "./settings.js"; import quotes from './quotes.json' assert { type: "json" }; const quoteDiv = document.querySelector(".quote"); @@ -8,8 +8,12 @@ const authorDiv = document.querySelector(".author"); export const changeQuoteButton = document.querySelector(".change-quote"); export function setQuote() { - const random = getRandomNum(0, quotes[lang].length - 1); + const random = getRandomNum(0, quotes[state.language].length - 1); - quoteDiv.textContent = quotes[lang][random].text; - authorDiv.textContent = quotes[lang][random].author; -} \ No newline at end of file + quoteDiv.textContent = quotes[state.language][random].text; + authorDiv.textContent = quotes[state.language][random].author; + console.log(state.language, random, quotes[state.language][random]); +} + +setQuote(); +changeQuoteButton.addEventListener("click", setQuote); \ No newline at end of file diff --git a/js/quotes.json b/js/quotes.json index e78c9e2..2d1d7fb 100644 --- a/js/quotes.json +++ b/js/quotes.json @@ -67,68 +67,68 @@ ], "ru": [ { - "текст": "Гений – это один процент вдохновения и девяносто девять процентов пота.", - "автор": "Томас Эдисон" + "text": "Гений – это один процент вдохновения и девяносто девять процентов пота.", + "author": "Томас Эдисон" }, { - "текст": "Можно много увидеть, просто наблюдая.", - "автор": "Йоги Берра" + "text": "Можно много увидеть, просто наблюдая.", + "author": "Йоги Берра" }, { - "текст": "Дом, разделенный сам с собой, не может устоять.", - "автор": "Абраам Линкольн" + "text": "Дом, разделенный сам с собой, не может устоять.", + "author": "Абраам Линкольн" }, { - "текст": "Трудности увеличиваются, когда мы приближаемся к цели.", - "автор": "Иоганн Вольфганг Гёте" + "text": "Трудности увеличиваются, когда мы приближаемся к цели.", + "author": "Иоганн Вольфганг Гёте" }, { - "текст": "Судьба в ваших руках и ни в чьих других.", - "автор": "Байрон Пульсифер" + "text": "Судьба в ваших руках и ни в чьих других.", + "author": "Байрон Пульсифер" }, { - "текст": "Будьте главой, но никогда не будьте повелителем.", - "автор": "Лао Цзы" + "text": "Будьте главой, но никогда не будьте повелителем.", + "author": "Лао Цзы" }, { - "текст": "Ничего не происходит, пока мы не начнем мечтать.", - "автор": "Карл Сандберг" + "text": "Ничего не происходит, пока мы не начнем мечтать.", + "author": "Карл Сандберг" }, { - "текст": "Хорошее начало – это полдела.", - "автор": "Аристотель" + "text": "Хорошее начало – это полдела.", + "author": "Аристотель" }, { - "текст": "Жизнь – это учебный опыт, только если вы учитесь.", - "автор": "Йоги Берра" + "text": "Жизнь – это учебный опыт, только если вы учитесь.", + "author": "Йоги Берра" }, { - "текст": "Самодовольство – это смертельно для прогресса.", - "автор": "Маргарет Сэнгстер" + "text": "Самодовольство – это смертельно для прогресса.", + "author": "Маргарет Сэнгстер" }, { - "текст": "Мир приходит изнутри. Не ищите его вне себя.", - "автор": "Будда" + "text": "Мир приходит изнутри. Не ищите его вне себя.", + "author": "Будда" }, { - "текст": "То, что вы даете, то и получаете.", - "автор": "Байрон Пульсифер" + "text": "То, что вы даете, то и получаете.", + "author": "Байрон Пульсифер" }, { - "текст": "Мы можем научиться любить только, любя.", - "автор": "Ирис Мердок" + "text": "Мы можем научиться любить только, любя.", + "author": "Ирис Мердок" }, { - "текст": "Жизнь – это изменение. Рост – это выбор. Выбирайте мудро.", - "автор": "Карен Кларк" + "text": "Жизнь – это изменение. Рост – это выбор. Выбирайте мудро.", + "author": "Карен Кларк" }, { - "текст": "Вы увидите это, когда поверите в это.", - "автор": "Уэйн Дайер" + "text": "Вы увидите это, когда поверите в это.", + "author": "Уэйн Дайер" }, { - "текст": "Сегодня – это завтра, о котором мы беспокоились вчера.", - "автор": "Автор не указан" + "text": "Сегодня – это завтра, о котором мы беспокоились вчера.", + "author": "author не указан" } ], "be": [ diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..6f60f86 --- /dev/null +++ b/js/settings.js @@ -0,0 +1,87 @@ +import { onKeyDownEvent } from "./audio.js"; + +export const state = { + language: "en", + photoSource: "github", + blocks: [], +}; + +const settingsButton = document.querySelector(".settings-button"); +const settingsContainer = document.querySelector(".settings-container"); +const closeButton = document.querySelector(".settings-close"); +const settings = document.querySelector(".settings-container .settings"); + +export let isPopUpOpened = false; + +settingsButton.addEventListener("click", () => { + settingsContainer.classList.add("open"); + isPopUpOpened = true; + updateHotKeys(); +}) + +closeButton.addEventListener("click", () => { + settingsContainer.classList.remove("open"); + isPopUpOpened = false; + updateHotKeys(); + setSettings(); +}) + +window.addEventListener("keydown", (event) => { + if (event.key == "Escape") { + settingsContainer.classList.remove("open"); + isPopUpOpened = false; + updateHotKeys(); + setSettings(); + } +}) + +function updateHotKeys() { + if (!isPopUpOpened) { + document.addEventListener("keydown", onKeyDownEvent); + } else { + document.removeEventListener("keydown", onKeyDownEvent); + } +} + +updateHotKeys(); + +settings.addEventListener('click', event => { + event._isClickWithInModal = true; +}); + +settingsContainer.addEventListener('click', event => { + if (event._isClickWithInModal) return; + event.currentTarget.classList.remove('open'); + isPopUpOpened = false; + updateHotKeys(); + setSettings(); +}); + + +const checkboxes = document.querySelectorAll("input[name='toHide']"); + +function getSelectedCheckboxes() { + const selectedChekboxes = [] + for (let checkbox of checkboxes) { + if (checkbox.checked) { + selectedChekboxes.push(checkbox.value); + } + } + return selectedChekboxes; +} + +const selectedLanguage = document.querySelector("select[name='language']"); +const selectedPhotoSource = document.querySelector("select[name='photo']"); + +function setSettings() { + const blocks = getSelectedCheckboxes(); + state.blocks = blocks; + state.language = selectedLanguage.value; + state.photoSource = selectedPhotoSource.value; + console.log(state); +} + + + + + diff --git a/js/slider.js b/js/slider.js index 573db87..f325013 100644 --- a/js/slider.js +++ b/js/slider.js @@ -1,3 +1,5 @@ +import { state } from "./settings.js"; + export const nextSlideButton = document.querySelector('.slide-next'); export const prevSlideButton = document.querySelector('.slide-prev'); @@ -27,11 +29,10 @@ export function setRandomNum(min, max) { randomNum = getRandomNum(min, max); } -export function setBg() { - const timeOfDay = getTimeOfDay(); - const bgNum = randomNum.toString().padStart(2, "0"); +export async function setBg() { + const imageLink = await setImageLink(); const img = new Image(); - img.src = `https://raw.githubusercontent.com/Junk-debug/stage1-tasks/assets/images/${timeOfDay}/${bgNum}.jpg`; + img.src = imageLink; img.onload = () => { document.body.style.backgroundImage = `url('${img.src}')`; }; @@ -52,3 +53,34 @@ export function slidePrev() { } setBg(); } + +async function getLinkFromUnsplash() { + const unsplashUrl = 'https://api.unsplash.com/photos/random?orientation=landscape&query=nature&client_id=97BROPl_UZvO5qc-w-C5bPcK0Bfu7goXFz-JbUrDKaU'; + const res = await fetch(unsplashUrl); + const data = await res.json(); + return data.urls.regular; +} + +async function getLinkFromFlickr() { + const flickrUrl = 'https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=6b2b5f35c8598701f64ac533d6f53d57&tags=nature&extras=url_l&format=json&nojsoncallback=1'; + const res = await fetch(flickrUrl); + const data = await res.json(); + return data.photos.photo[randomNum].url_l; +} + +function getLinkFromGithub() { + const timeOfDay = getTimeOfDay(); + const bgNum = randomNum.toString().padStart(2, "0"); + return `https://raw.githubusercontent.com/Junk-debug/stage1-tasks/assets/images/${timeOfDay}/${bgNum}.webp`; +} + +async function setImageLink() { + switch (state.photoSource) { + case "github": + return getLinkFromGithub(); + case "unsplash": + return await getLinkFromUnsplash(); + case "flickr": + return await getLinkFromFlickr(); + } +} \ No newline at end of file diff --git a/js/time.js b/js/time.js index cfc2880..7f7a3e3 100644 --- a/js/time.js +++ b/js/time.js @@ -1,5 +1,6 @@ import { showGreeting } from './greeting.js'; -import { dateTranslation, lang, belMonthFromNum, belDayFromNum } from './translate.js'; +import { dateTranslation, belMonthFromNum, belDayFromNum } from './translate.js'; +import { state } from './settings.js'; const timeDiv = document.querySelector(".time"); const dateDiv = document.querySelector(".date"); @@ -10,23 +11,25 @@ export function showTime() { timeDiv.textContent = currentTime; showDate(); showGreeting(); - + console.log(state.language); setTimeout(showTime, 1000); } function showDate() { const date = new Date(); - const langOption = dateTranslation[lang]; + const langOption = dateTranslation[state.language]; const options = { weekday: "long", month: "long", day: "numeric", }; let currentDate; - if (lang == "be") { + if (state.language == "be") { currentDate = `${belDayFromNum[date.getDay()]}, ${date.getDate()} ${belMonthFromNum[date.getMonth()]}`; } else { currentDate = date.toLocaleDateString(langOption, options); } dateDiv.textContent = currentDate; -} \ No newline at end of file +} + +showTime(); \ No newline at end of file diff --git a/js/translate.js b/js/translate.js index 142341b..4128ba8 100644 --- a/js/translate.js +++ b/js/translate.js @@ -1,5 +1,3 @@ -export const lang = "be"; - export const greetingTranslation = { en: [ "Good morning,", @@ -86,4 +84,10 @@ export const weatherDescriptionTranslation = { "м/с", "Вільготнасць" ] +} + +export const errorMessage = { + en: "City not found for", + ru: "Город не найден для", + be: "Горад не знойдзены для" } \ No newline at end of file diff --git a/js/updateSettings.js b/js/updateSettings.js new file mode 100644 index 0000000..5baf778 --- /dev/null +++ b/js/updateSettings.js @@ -0,0 +1,34 @@ +import { setBg } from "./slider.js"; +import { updateHiddenElements } from "./hide.js"; +import { setQuote } from "./quotes.js"; +import { getWeather } from "./weather.js"; + + +export function applySettings() { + // photo source + setBg(); + // language + console.log("setted"); + getWeather(); + setQuote(); + // hidden items + updateHiddenElements(); +} + +const closeButton = document.querySelector(".settings-close"); +const settingsContainer = document.querySelector(".settings-container"); + +closeButton.addEventListener("click", () => { + applySettings(); +}) + +window.addEventListener("keydown", (event) => { + if (event.key == "Escape") { + applySettings(); + } +}) + +settingsContainer.addEventListener('click', event => { + if (event._isClickWithInModal) return; + applySettings(); +}); \ No newline at end of file diff --git a/js/weather.js b/js/weather.js index 23396b5..0b99ebb 100644 --- a/js/weather.js +++ b/js/weather.js @@ -1,4 +1,5 @@ -import { lang, weatherDescriptionTranslation } from "./translate.js"; +import { weatherDescriptionTranslation, startCityTranslation, errorMessage } from "./translate.js"; +import { state } from "./settings.js"; const weatherIcon = document.querySelector(".weather-icon"); const temperature = document.querySelector(".temperature"); @@ -9,23 +10,30 @@ const weatherError = document.querySelector(".weather-error"); export const inputCity = document.querySelector(".city"); export async function getWeather() { - const url = `https://api.openweathermap.org/data/2.5/weather?q=${inputCity.value}&lang=${lang}&appid=b1201d454068452807855ae9447aa96e&units=metric`; + const url = `https://api.openweathermap.org/data/2.5/weather?q=${inputCity.value}&lang=${state.language}&appid=b1201d454068452807855ae9447aa96e&units=metric`; const res = await fetch(url); const data = await res.json(); - if (data.cod == 404) { - weatherError.textContent = `Error: ${data.message} for '${inputCity.value}'!`; - weatherIcon.className = "weather-icon owf"; - temperature.textContent = ''; - weatherDescription.textContent = ''; - wind.textContent = ''; - humidity.textContent = ''; - } else { + console.log(url); + try { weatherError.textContent = ''; weatherIcon.className = "weather-icon owf"; weatherIcon.classList.add(`owf-${data.weather[0].id}`); temperature.textContent = `${data.main.temp}°C`; weatherDescription.textContent = data.weather[0].description; - wind.textContent = `${weatherDescriptionTranslation[lang][0]}: ${data.wind.speed} ${weatherDescriptionTranslation[lang][1]}`; - humidity.textContent = `${weatherDescriptionTranslation[lang][2]}: ${data.main.humidity} %`; + wind.textContent = `${weatherDescriptionTranslation[state.language][0]}: ${data.wind.speed} ${weatherDescriptionTranslation[state.language][1]}`; + humidity.textContent = `${weatherDescriptionTranslation[state.language][2]}: ${data.main.humidity} %`; + } catch { + weatherError.textContent = `Error: ${errorMessage[state.language]} '${inputCity.value}'!`; + weatherIcon.className = "weather-icon owf"; + temperature.textContent = ''; + weatherDescription.textContent = ''; + wind.textContent = ''; + humidity.textContent = ''; } -} \ No newline at end of file +} + +inputCity.value = startCityTranslation[state.language]; + +inputCity.addEventListener("change", getWeather); + +getWeather(); \ No newline at end of file