diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..9ff610ff Binary files /dev/null and b/.DS_Store differ diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 00000000..3b6b0e43 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/img/.DS_Store b/assets/img/.DS_Store new file mode 100644 index 00000000..b9cd5fdd Binary files /dev/null and b/assets/img/.DS_Store differ diff --git a/assets/img/1.jpg b/assets/img/1.jpg new file mode 100644 index 00000000..f9253027 Binary files /dev/null and b/assets/img/1.jpg differ diff --git a/assets/img/10.jpg b/assets/img/10.jpg new file mode 100644 index 00000000..e64bfd9b Binary files /dev/null and b/assets/img/10.jpg differ diff --git a/assets/img/2.jpg b/assets/img/2.jpg new file mode 100644 index 00000000..bcbf0b9a Binary files /dev/null and b/assets/img/2.jpg differ diff --git a/assets/img/3.jpg b/assets/img/3.jpg new file mode 100644 index 00000000..778e72f8 Binary files /dev/null and b/assets/img/3.jpg differ diff --git a/assets/img/4.jpg b/assets/img/4.jpg new file mode 100644 index 00000000..c6b36bf9 Binary files /dev/null and b/assets/img/4.jpg differ diff --git a/assets/img/5.jpg b/assets/img/5.jpg new file mode 100644 index 00000000..76a15339 Binary files /dev/null and b/assets/img/5.jpg differ diff --git a/assets/img/6.jpg b/assets/img/6.jpg new file mode 100644 index 00000000..a5746f86 Binary files /dev/null and b/assets/img/6.jpg differ diff --git a/assets/img/7.jpg b/assets/img/7.jpg new file mode 100644 index 00000000..a585f48a Binary files /dev/null and b/assets/img/7.jpg differ diff --git a/assets/img/8.jpg b/assets/img/8.jpg new file mode 100644 index 00000000..6823a4c9 Binary files /dev/null and b/assets/img/8.jpg differ diff --git a/assets/img/9.jpg b/assets/img/9.jpg new file mode 100644 index 00000000..360ebfa3 Binary files /dev/null and b/assets/img/9.jpg differ diff --git a/assets/img/humidity.png b/assets/img/humidity.png new file mode 100644 index 00000000..780eed19 Binary files /dev/null and b/assets/img/humidity.png differ diff --git a/assets/img/rain.png b/assets/img/rain.png new file mode 100644 index 00000000..679c24b0 Binary files /dev/null and b/assets/img/rain.png differ diff --git a/assets/img/searchIcon.png b/assets/img/searchIcon.png new file mode 100644 index 00000000..26c5807e Binary files /dev/null and b/assets/img/searchIcon.png differ diff --git a/assets/img/wind.png b/assets/img/wind.png new file mode 100644 index 00000000..d7cde6cb Binary files /dev/null and b/assets/img/wind.png differ diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 00000000..d7cab29e --- /dev/null +++ b/css/styles.css @@ -0,0 +1,492 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* ----- */ + + +/* WHOLE PAGE */ + +header { + overflow: hidden; + background-color: #007BFF; + padding: 20px 10px; + border: 2px solid darkblue; +} + +header a { + float: left; + color: black; + text-align: center; + padding: 12px; + text-decoration: none; + font-size: 18px; + line-height: 25px; + border-radius: 4px; +} + +header a:hover { + background-color: darkblue; + color: lightpink; +} + +header a.active { + background-color: dodgerblue; + color: white; +} + +.navBar { + float: right; +} + +.navBar a.active { + background-color: darkblue; + color: lightpink; +} + +h1 a { + font-size: 28px; + color: darkblue; +} + +h2 { + font-size: 18px; + color: darkblue; + text-align: center; + padding: 10px; +} + +.container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; + align-content: stretch; + gap: 100px; + margin: 20px; + padding: 40px; +} + +.clockCard, .weatherCard, .linkCard, .passwordCard { + border: 6px solid darkblue; +} + +.weatherCard, .linkCard, .passwordCard { + height: auto; + width: 420px; + padding: 20px; + border-radius: 40px; + background-color: #007BFF; +} + +footer { + overflow: hidden; + background-color: #007BFF; + padding: 20px 10px; + border: 2px solid darkblue; +} + +footer p { + float: right; + color: black; + text-align: center; + padding: 5px; + font-size: 12px; +} + +/* CLOCK WIDGET */ +.clockCard { + display: flex; + flex-wrap: wrap; + height: 400px; + width: 400px; + color: blue; + background-color: pink; + border-radius: 300px; + justify-content: center; + align-items: center; + align-content: center; + position: relative; +} + +.clock { + display: flex; + flex-direction: column; + flex-wrap: wrap; + color: blue; + background-color: goldenrod; + width: 300px; + height: 300px; + border-radius: 300px; + border: 6px solid darkblue; + justify-content: center; + align-items: center; + align-content: center; + font-size: 3rem; +} + +.text { + position: absolute; + width: 100%; + height: 100%; + animation: rotateText 60s linear infinite; + white-space: no-wrap; + letter-spacing: 1px; +} + +@keyframes rotateText { + 0% { + transform: rotate(0deg); + } + 100%{ + transform: rotate(360deg); + } +} + +.text span { + position: absolute; + left: 50%; + font-size: 2rem; + transform-origin: 0 200px; +} + +span { + position: absolute; + left: 50%; + transform-origin: 0 150pc; + font-size: 20px; + font-weight: 500; + text-transform: uppercase; + color: white; +} + +/* WEATHER WIDGET */ + +.weatherCard { + display: block; + flex-grow: 0; + flex-shrink: 1; + flex-basis: auto; + align-self: auto; + order: 0; + max-width: 500px; + color: #fff; + text-align: center; +}; + +.weatherSearch { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.weatherSearch input { + border: 0; + outline: 0; + background: #ebfffc; + color: #555; + padding: 10px 25px; + height: 30px; + width: 200px; + border-radius: 30px; + flex: 1; + margin-right: 16px; + font-size: 18px; +} + +.weatherSearch button { + border: 0; + outline: 0; + background: #ebfffc; + border-radius: 50%; + width: 50px; + height: 50px; + cursor: pointer; +} + +.weatherSearch button img { + width: 16px; +} + +.weatherIcon { + width: 170px; + margin-top: 0px; +} + +.weather img { + width: 100px; + margin: -10; +} + +.weather h1 { + font-size: 50px; + font-weight: 500; +} + +.weather h2 { + font-size: 28px; + font-weight: 400; + margin-top: 10px; +} + +.weather h4 { + font-size: 15px; + font-weight: 200; + margin-top: 5px; +} + +.details { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: center; + padding: 0 20px; + margin-top: 10px; +} + +.col { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + text-align: center; + margin: 10px; +} + +.col img { + width: 30px; + margin-right: 10px; +} + +.humidity, .wind { + font-size: 18px; + margin-top: -6px; +} + +.weather { + display: none; +} + +.hourlyWeather { + display: flex; + flex-wrap: wrap; + flex-direction: row; + padding: 10px; + align-content: center; + justify-content: space-between; +} + +.hourlyWeather img { + width: 50px; +} + +.error { + text-align: left; + margin-left: 10px; + font-size: 14px; + margin-top: 10px; + display: none; +} + +/* LINKS WIDGET */ + +.linkCard { + display: block; + flex-grow: 0; + flex-shrink: 1; + flex-basis: auto; + align-self: auto; + order: 0; + max-width: 500px; + color: #fff; + text-align: center; + +} + +.link-items { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-content: center; + justify-content: flex-start; + align-items: center; + margin-bottom: 10px; + gap: 10px; +} + +#link-list { + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 10px; + margin-left: 20px; + } + + #link-list button { + border: 0; + outline: 0; + background: #ebfffc; + border-radius: 50%; + width: 20px; + height: 20px; + cursor: pointer; + align-self: center; + margin-right: 20px; + } + +.link-items input { + border: 0; + outline: 0; + background: #ebfffc; + color: #555; + padding: 10px 25px; + height: 30px; + width: auto; + border-radius: 30px; + margin-right: 16px; + margin: 10px; + font-size: 18px; + margin-left: 10px; +} + +.link-items button { + border: 0; + outline: 0; + background: #ebfffc; + border-radius: 50%; + width: 80px; + height: 80px; + cursor: pointer; + align-self: center; + margin-right: 20px; +} + +/* PASSWORD WIDGET */ +/* .passwordCard { +} */ + +.passwordCard { + display: block; + flex-grow: 0; + flex-shrink: 1; + flex-basis: auto; + align-self: auto; + order: 0; + max-width: 500px; + color: #fff; + text-align: center; + +} + +.password-items { + width: 100%; + display: flex; + flex-wrap: wrap; + flex-direction: row; + align-items: center; + justify-content: center; + margin-bottom: 10px; + align-content: center; + gap: 10px; + margin-top: 20px; +} + +.password-number { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +.password-number input{ + border: 0; + outline: 0; + background: #ebfffc; + color: #555; + height: 80px; + width: 80px; + border-radius: 50px; + margin-right: 16px; + text-align: center; + font-size: 18px; +} + +.password-items button { + border: 0; + outline: 0; + background: #ebfffc; + border-radius: 50%; + width: 80px; + height: 80px; + cursor: pointer; + align-self: center; +} + +.password-items .passwords input { + border: 0; + outline: 0; + background: #ebfffc; + color: #555; + padding: 10px 25px; + height: 30px; + width: 300px; + border-radius: 30px; + font-size: 18px; + margin-top: 20px; +} + +#generateBtn:hover { + background-color: #0056b3; +} diff --git a/index.html b/index.html new file mode 100644 index 00000000..83d0b421 --- /dev/null +++ b/index.html @@ -0,0 +1,112 @@ + + + + + + + + Dashboard Project GGMJ + + + + +
+

GGMJ's Widget App

+ + + +
+ +
+ +
+
+
+
+
+
+

+
+
+ +
+
+ + +
+
+

City not found

+
+
+ +

+

+

+
+
+ humidityIcon +
+

+

Humidity

+
+
+ windIcon +
+

25km/h

+

Wind Speed

+
+
+
+
+
+
+
+ +
+

Favorite Links

+ + +
+ +
+

Password Generator

+
+
+

Number of characters:

+ +
+
+ +
+
+ +
+
+
+ +
+ + + + + + + + \ No newline at end of file diff --git a/js/widgets.js b/js/widgets.js new file mode 100644 index 00000000..431385cb --- /dev/null +++ b/js/widgets.js @@ -0,0 +1,324 @@ +// //NAV BAR + +// const navLinks = document.querySelectorAll(".navBar a"); + +// navLinks.forEach(link => { +// link.addEventListener('click', function (event) { +// event.preventDefault(); + +// const widgets = document.document.querySelectorAll('.widget'); +// widgets.forEach(widget => { +// widget.style.display = "none"; +// }); + +// const widgetId = link.getAttribute("data-widget"); +// document.querySelector("#" + widgetId).style.display = "block"; +// }); +// }); + +//CLOCK WIDGET + +const currentTime = () => { + let date = new Date(); + let hours = date.getHours(); + let mins = date.getMinutes(); + let secs = date.getSeconds(); + + hours = (hours < 10) ? "0" + hours : hours; + mins = (mins < 10) ? "0" + mins : mins; + secs = (secs < 10) ? "0" + secs : secs; + + let hour = `${hours}:${mins}:${secs}`; + + document.getElementsByClassName("time")[0].innerHTML = hour; + let clock = setTimeout(function () { currentTime() }, 1000); + + updateText(hour); +} + +const currentDate = () => { + let date = new Date(); + let day = date.getUTCDate(); + let month = date.getUTCMonth() + 1; + let year = date.getUTCFullYear(); + + let today = `${day}/${month}/${year}` + + document.getElementsByClassName("date")[0].innerHTML = today; + +} + +const updateText = (hour) => { + let string = ""; + const text = document.querySelector(".text p"); + + const timeRanges = { + rest: ["00:01", "07:00"], + morning: ["07:01", "12:00"], + lunch: ["12:01", "14:00"], + afternoon: ["14:01", "16:00"], + push: ["16:01", "18:00"], + overtime: ["18:01", "22:00"], + night: ["22:01", "00:00"] + }; + + for (const range of Object.values(timeRanges)) { + if (hour >= range[0] && hour < range[1]) { + string = messageForRange(range); + } + } + + if (!string) { + string = "| Have a good night! |" + } + + text.innerHTML = string.split("").map( + (char, i) => + `${char}` + + ).join("") +}; + +const messageForRange = (range) => { + switch (range[0]) { + case "00:01": + return "| Shut off and continue tomorrow! |"; + case "07:01": + return "| Good morning! |" + case "12:01": + return "| Don't forget to eat! |" + case "14:01": + return "| Hope you had a nice lunch! |" + case "16:01": + return "| Good afternoon! |" + case "18:01": + return "| Consider stopping soon! |" + case "22:01": + return "| Goodnight, get some rest! |" + default: + return ""; + } +}; + +currentTime(); +currentDate(); + + +//WEATHER WIDGET + +// Temperature: current > temp_c +// Feels like: current > feelslike_c +// Humidity: current > humidity +// Wind: current > wind_kph +// Image: current > condition > icon + +// Sunrise: forecast > 0 > astro > sunrise +// Sunset: forecast > 0 > astro > sunset + +// City: location > name +// Country: location > country + +const apiKey = "7317477b37ea40df8f8195024231312"; +const searchInput = document.querySelector(".weatherSearch input"); +const searchBtn = document.querySelector(".weatherSearch button"); +const city = ""; +const weatherIcon = document.querySelector(".weatherIcon"); + +const fetchWeather = async () => { + + let city = localStorage.getItem("city"); + if (!city) { + city = searchInput.value; + } + + searchBtn.addEventListener("click", () => { + city = searchInput.value; + localStorage.setItem("city", city); + fetchWeather(); + }); + + searchInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + city = searchInput.value; + localStorage.setItem("city", city); + fetchWeather(); + } + }); + + try { + const response = await fetch(`https://api.weatherapi.com/v1/forecast.json?key=${apiKey}&q=${city}&aqi=no`); + + const data = await response.json(); + + document.querySelector(".city").innerHTML = data.location.name + ", " + data.location.country; + document.querySelector(".temp").innerHTML = Math.round(data.current.temp_c) + "ºC"; + document.querySelector(".feel").innerHTML = "Feels like: " + Math.round(data.current.feelslike_c) + "ºC"; + document.querySelector(".humidity").innerHTML = data.current.humidity + "%"; + document.querySelector(".wind").innerHTML = data.current.wind_kph + "km/h"; + weatherIcon.src = "https://" + data.current.condition.icon; + + const hourlyWeather = document.querySelector(".hourlyWeather"); + hourlyWeather.innerHTML = ""; + + let counter = [0]; + data.forecast.forecastday[0].hour.forEach(hourData => { + if (counter < 6) { + const hourElement = document.createElement("div"); + const time = hourData.time.split(" ")[1]; + + hourElement.innerHTML = ` +

${time}

+

${Math.round(hourData.temp_c)}ºC

+ ${hourData.condition.text} + `; + hourlyWeather.appendChild(hourElement); + counter++; + } + }); + + document.querySelector(".weather").style.display = "block"; + + } catch (error) { + } + +}; + +fetchWeather(); + +//FAVORITE LINKS WIDGET + +const linkList = document.getElementById("link-list"); +const addLinkBtn = document.getElementById("add-link-btn"); +const linkTitle = document.getElementById("link-title"); +const linkUrl = document.getElementById("link-url"); + +const addLink = (title, url) => { + const listItem = document.createElement("li"); + listItem.classList.add("link-item"); + + // if (!url.startsWith("http")) { + // url = "https://www." + url; + // } + + const linkElement = document.createElement("a"); + linkElement.href = url; + linkElement.textContent = title; + listItem.appendChild(linkElement); + + const deleteBtn = document.createElement("button"); + deleteBtn.textContent = "X"; + deleteBtn.addEventListener("click", () => { + linkList.removeChild(listItem); + updateLocalStorage(); + }); + listItem.appendChild(deleteBtn); + + linkList.appendChild(listItem) + updateLocalStorage(); +} + +addLinkBtn.addEventListener('click', () => { + const title = linkTitle.value.trim(); + const url = linkUrl.value.trim(); + + if (!title || !url) { + alert('Please enter both title and url'); + return + } + + try { + new URL(url); + } catch (_) { + alert('Please enter a valid url'); + return + } + + addLink(title, url); + + linkTitle.value = ""; + linkUrl.value = ""; +}); + + +const getLinksFromLocalStorage = () => { + try { + return JSON.parse(localStorage.getItem("favoriteLinks") || "[]"); + } catch (error) { + console.error("Error reading from localStorage", error); + return []; + } +} + + +const updateLocalStorage = () => { + const links = []; + for (const item of linkList.querySelectorAll("li")) { + links.push({ + title: item.querySelector("a").textContent, + url: item.querySelector("a").href + }) + } + try { + localStorage.setItem("favoriteLinks", JSON.stringify(links)) + } catch (error) { + console.error("Error writing to localStorage", error) + } + +}; + +document.addEventListener("DOMContentLoaded", () => { + getLinksFromLocalStorage().forEach((link) => addLink(link.title, link.url)) +}) + + +//BACKGROUND IMAGES + +const images = [ + './assets/img/1.jpg', + './assets/img/2.jpg', + './assets/img/3.jpg', + './assets/img/4.jpg', + './assets/img/5.jpg', + './assets/img/6.jpg', + './assets/img/7.jpg', + './assets/img/8.jpg', + './assets/img/9.jpg', + './assets/img/10.jpg' +]; + +const changeBackgroundImage = () => { + const randomImage = Math.floor(Math.random() * images.length); + const imageUrl = images[randomImage]; + document.body.style.backgroundImage = `url('${imageUrl}')`; +} + +setInterval(changeBackgroundImage, 15000); + +changeBackgroundImage(); + + +//PASSWORD GENERATOR WIDGET + +document.getElementById('generateBtn').addEventListener('click', () => { + const length = document.getElementById('passwordLength').value; + let result = ""; + const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const lowercase = "abcdefghijklmnopqrstuvwxyz"; + const numbers = "0123456789"; + const symbols = "!@#$%^&*()-_=+"; + const allCharacters = uppercase + lowercase + numbers + symbols; + + result += uppercase[Math.floor(Math.random() * uppercase.length)]; + result += lowercase[Math.floor(Math.random() * lowercase.length)]; + result += numbers[Math.floor(Math.random() * numbers.length)]; + result += symbols[Math.floor(Math.random() * symbols.length)]; + + for (let i = 4; i < length; i++) { + result += allCharacters[Math.floor(Math.random() * allCharacters.length)]; + } + + result = result.split('').sort(() => Math.random() - 0.5).join(''); + + document.getElementById("password").value = result; +}); +