diff --git a/Bonus/index.html b/Bonus/index.html new file mode 100644 index 00000000..3165321a --- /dev/null +++ b/Bonus/index.html @@ -0,0 +1,19 @@ + + + + + + П'ятнашки + + + + + + +
Кількість ходів: 0
+
+
+
+ + + \ No newline at end of file diff --git a/Bonus/script.js b/Bonus/script.js new file mode 100644 index 00000000..03098fbb --- /dev/null +++ b/Bonus/script.js @@ -0,0 +1,148 @@ +const moveCounter = document.querySelector("span"); + +let board = []; + +for (let i = 0; i < 4; i++) { + for (let j = 0; j < 4; j++) { + board.push({"row": `${i}`, "column": `${j}`, "value": (i*4) + j + 1},) + } +} +board[15].value = null; + +function getTableData() { + return [ + { + "row": { "type": "string" }, + "column": { "type": "string" }, + "value": { "type": "number" } + }, + ...board + ]; +} + +function getNeighborsIndices(cellIndex) { + let validIndices = []; + + if (cellIndex % 4 !== 0) validIndices.push(cellIndex - 1); + if ((cellIndex + 1) % 4 !== 0) validIndices.push(cellIndex + 1); + if (cellIndex >= 4) validIndices.push(cellIndex - 4); + if (cellIndex <= 11) validIndices.push(cellIndex + 4); + + return validIndices; +} + +function swapCells(firstCellIndex, secondCellIndex) { + const cellA = board[firstCellIndex]; + const cellB = board[secondCellIndex]; + + [cellA.value, cellB.value] = [cellB.value, cellA.value]; +} + +function randomizeGameBoard() { + let currentCell = 15; + + for (let i = 0; i < 100; i++) { + const neighbors = getNeighborsIndices(currentCell); + const randomNeighbor = Math.floor(Math.random() * (neighbors.length)); + + console.log(`Обміняно з: ${board[neighbors[randomNeighbor]].value}`); + swapCells(currentCell, neighbors[randomNeighbor]); + + currentCell = neighbors[randomNeighbor]; + } +} + +function checkWin() { + for (let i = 0; i < 15; i++) { + if (board[i].value !== i+1) return false; + } + + return true; +} + +randomizeGameBoard(); + +const pivot = new WebDataRocks({ + container: "#pivotContainer", + toolbar: false, + report: { + dataSource: { + data: getTableData() + }, + slice: { + rows: [{uniqueName: "row"}], + columns: [ + {uniqueName: "column"}, + {uniqueName: "Measures"} + ], + measures: [{uniqueName: "value", aggregation: "sum"}] + }, + options: { + grid: { + showHeaders: false, + showHierarchyCaptions: false, + showTotals: "off", + showGrandTotals: "off" + } + }, + tableSizes: { + columns: [ + { idx: 0, width: 80 }, { idx: 1, width: 80 }, + { idx: 2, width: 80 }, { idx: 3, width: 80 }, { idx: 4, width: 80 } + ], + rows: [ + { idx: 0, height: 80 }, { idx: 1, height: 80 }, + { idx: 2, height: 80 }, { idx: 3, height: 80 }, { idx: 4, height: 80 } + ] + } + }, + customizeCell: (builder, { type, value }) => { + if (type !== "value") return; + + builder.text = value + ? `
${value}
` + : `
`; + } +}); + +pivot.on("cellclick", (cellData) => { + if (!cellData.measure) return; + + const cellRow = +(cellData.rows[0].caption); + const cellColumn = +(cellData.columns[0].caption); + const currentCellIndex = cellRow * 4 + cellColumn; + + let neighbors = []; + const neighborsIndices = getNeighborsIndices(currentCellIndex); + neighborsIndices.map(neighborIndex => neighbors.push(board[neighborIndex])); + + const freeNeighbor = neighbors.find(cell => !cell.value); + + if (!freeNeighbor) return; + const freeNeighborIndex = (+freeNeighbor.row * 4) + (+freeNeighbor.column); + + swapCells(currentCellIndex, freeNeighborIndex); + let currentReport = pivot.getReport(); + currentReport.dataSource.data = getTableData(); + pivot.setReport(currentReport); + + let counterValue = +(moveCounter.textContent); + moveCounter.textContent = ++counterValue; + + if (checkWin()) { + setTimeout(() => { + const counterValue = +(moveCounter.textContent); + let repeatGame = confirm(`Вітаю! Ви пройшли головоломку за ${counterValue} ходів! Бажаєте повторити?`); + + if (repeatGame) { + moveCounter.textContent = 0; + + randomizeGameBoard(); + + let currentReport = pivot.getReport(); + currentReport.dataSource.data = getTableData(); + pivot.setReport(currentReport); + } + }, 100); + } +}); \ No newline at end of file diff --git a/Bonus/styles.css b/Bonus/styles.css new file mode 100644 index 00000000..9e89b1d9 --- /dev/null +++ b/Bonus/styles.css @@ -0,0 +1,66 @@ +* { + box-sizing: border-box; +} + +#pivotContainer .wdr-cell { + padding: 0 !important; + border: 1px solid lightgray !important; +} + +body { + font-family: sans-serif; + background-color: white; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 50px; + gap: 20px; +} + +header { + display: flex; + justify-content: center; + padding: 10px; + border: 1px solid lightblue; + width: 30%; + border-radius: 6px; + background-color: aliceblue; +} + +#game-wrapper { + display: flex; + justify-content: center; + align-items: center; + width: 480px; + height: 504px; + border-radius: 4px; + overflow: hidden; +} + +#pivotContainer { + width: 100%; + height: 100%; +} + +.value-cell { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + background-color: #3498db; + color: white; + font-size: 20px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.value-cell:hover { + background-color: #2980b9; +} + +.empty-cell { + height: 100%; + width: 100%; +} \ No newline at end of file diff --git a/index.html b/index.html index 5971bf7d..d7e3dd3e 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,49 @@ - - - - My Page + + + BuyList + + + + + +
+
+ + + +
- - - - - Apple - 4 - - +
+
+

Залишилося

+
+
+
+ +
+

Куплено

+
+
+
+
+ +
+
+
+
+
+ Buy List +
+
+ Created by: + Ihor Movchan +
+
+ + \ No newline at end of file diff --git a/main.css b/main.css deleted file mode 100644 index 00bc872e..00000000 --- a/main.css +++ /dev/null @@ -1,17 +0,0 @@ -.product-item { - background-color: gray; - display: inline-block; - height: 25px; - padding: 5px; - border-radius: 5px; -} - -.amount { - background-color: yellow; - border-radius: 10px; - - display: inline-block; - height: 20px; - width: 20px; - text-align: center; -} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 00000000..b9e85bae --- /dev/null +++ b/main.js @@ -0,0 +1,183 @@ +const mainInput = document.querySelector(".main-search-bar input"); +const addButton = document.querySelector(".main-search-bar button"); +const prodList = document.querySelector("#left-cont ul"); +const remainedProdList = document.querySelector(".prod-remained .prod-list"); +const boughtProdList = document.querySelector(".prod-bought .prod-list"); +const saveButton = document.querySelector("#save-btn"); +let itemsInList = []; +let nextProdID = 0; + +function addProduct(name) { + if (name === "") return; + const currentID = nextProdID++; + + const prod = { + id: currentID, + name: name, + quantity: 1, + isBought: false + } + itemsInList.push(prod); + showProduct(prod); + updateStatistics(); +} + +function showProduct(product) { + const newProd = document.createElement("li"); + newProd.dataset.id = product.id; + + newProd.innerHTML = generateProdHTML(product); + + prodList.appendChild(newProd); + mainInput.value = ""; + mainInput.focus(); +} + +function removeProduct(id) { + itemsInList = itemsInList.filter(item => item.id !== id); +} + +function updateStatistics() { + remainedProdList.innerHTML = ""; + boughtProdList.innerHTML = ""; + + itemsInList.forEach(item => { + const prodElement = document.createElement("span") + + if(item.isBought) { + prodElement.className = "prod-item-bought"; + prodElement.innerHTML = `${item.name}${item.quantity}`; + boughtProdList.appendChild(prodElement); + }else { + prodElement.className = "prod-item-rem"; + prodElement.innerHTML = `${item.name}${item.quantity}`; + remainedProdList.appendChild(prodElement); + } + }); +} + +function generateProdHTML(product) { + const isSubDisabled = product.quantity === 1; + const subClass = isSubDisabled ? "sub-non-active" : "sub"; + const quantitySection = product.isBought ? `${product.quantity}` + : ` + ${product.quantity} + ` + + return `${product.name} + +
+ ${quantitySection} +
+
+ + ${product.isBought ? "Куплено" : "Не куплено"} + ${product.isBought ? "" : ``} +
`; +} + +addButton.addEventListener("click", () => { + const prodName = mainInput.value.trim(); + addProduct(prodName); +}); + +mainInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + const prodName = mainInput.value.trim(); + addProduct(prodName); + } +}); + +saveButton.addEventListener("click", () => { + localStorage.setItem("savedCartState", JSON.stringify(itemsInList)); + + const origText = saveButton.textContent; + saveButton.textContent = "Збережено!"; + setTimeout(() => {saveButton.textContent = origText;}, 1000) +}) + +prodList.addEventListener("click", (event) => { + + const chosenLi = event.target.closest("li"); + if (!chosenLi) return; + const id = parseInt(chosenLi.dataset.id, 10); + const currentProd = itemsInList.find(item => item.id === id); + + switch(true) { + case event.target.classList.contains("discard"): + removeProduct(id); + chosenLi.remove(); + updateStatistics(); + break; + + case event.target.classList.contains("buy-btn"): + + currentProd.isBought = !currentProd.isBought; + chosenLi.innerHTML = generateProdHTML(currentProd); + updateStatistics(); + break; + + case event.target.classList.contains("prod-name"): + if (currentProd.isBought) break; + const nameSpan = event.target; + + const editInput = document.createElement("input"); + editInput.type = "text"; + editInput.value = currentProd.name; + editInput.className = "turn-into-input"; + + nameSpan.innerHTML = ""; + nameSpan.appendChild(editInput); + editInput.focus(); + + const saveNewName = () => { + const newName = editInput.value.trim(); + + if (newName !== "") { + currentProd.name = newName; + } + + chosenLi.innerHTML = generateProdHTML(currentProd); + updateStatistics(); + } + + editInput.addEventListener ("blur", saveNewName); + editInput.addEventListener ("keydown", (event) => { + if (event.key === "Enter") saveNewName(); + }); + break; + + case event.target.classList.contains("sub"): + if (currentProd.quantity > 1) { + currentProd.quantity--; + chosenLi.innerHTML = generateProdHTML(currentProd); + updateStatistics(); + } + break; + + case event.target.classList.contains("add"): + currentProd.quantity++; + chosenLi.innerHTML = generateProdHTML(currentProd); + updateStatistics(); + break; + } +}); + +const savedCart = localStorage.getItem("savedCartState"); +if (savedCart) { + itemsInList = JSON.parse(savedCart); + + if (itemsInList.lenth > 0) { + nextProdID = Math.max(...itemsInList.map(item => item.id)) + 1; + } + + itemsInList.forEach(product => { + showProduct(product); + }); + +}else{ + addProduct("Помідори"); + addProduct("Печиво"); + addProduct("Сир"); +} +updateStatistics(); \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 00000000..0cedc92a --- /dev/null +++ b/styles.css @@ -0,0 +1,344 @@ +:root { + --main-border-radius: 5px; +} + +body { + font-family: sans-serif, 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS'; + background-color: lightgray; + margin: 0px; + min-height: 100vh; +} + +.main-cont { + display: flex; + align-items: flex-start; + gap: 20px; + margin: 5px; + padding-top: 15px; + position: relative; +} + +.secondary-cont { + background-color: white; + margin: 10px; + border-radius: var(--main-border-radius); +} + +#left-cont { + flex: 2; +} + +#right-cont { + flex: 1; +} + +.main-search-bar { + display: flex; + height: 100%; + padding: 12px 15px; +} + +.main-search-bar input { + flex: 1; + border: 1px solid lightgray; + border-right: none; + border-radius: 6px 0px 0px 6px; + font-size: large; + padding-left: 20px; +} + +.main-search-bar button { + background-color: rgb(37, 122, 241); + color: white; + font-weight: bold; + font-size: large; + border: none; + border-radius: 0px 6px 6px 0px; + padding: 12px 25px; + box-shadow: inset 0px -5px 0px rgb(30, 103, 206); + cursor: pointer; +} + +ul { + list-style: none; + padding: 0px; + margin: 0px; +} + +li { + display: flex; + align-items: center; + padding: 15px; + border-top: 1px solid lightgray ; + gap: 15px; +} + +.prod-name, .turn-into-input { + flex: 1; + display: flex; + justify-content: flex-start; + font-size: large; + box-sizing: border-box; + cursor: pointer; +} + +.bought { + text-decoration: line-through; +} + +.turn-into-input { + outline: none; + border-radius: var(--main-border-radius); + border: 1px solid lightgray; + flex: none; + width: 60%; + height: 30px; + font-size: small; +} + +.turn-into-input:focus { + border-color: rgb(70, 210, 245); + box-shadow: 0 0 8px rgba(102, 175, 233, 0.6); +} + +.turn-into-input:hover { + border-color: rgb(70, 210, 245); +} + +.quantity-reg { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + gap: 8px; +} + +.quantity-reg button { + font-size: large; + font-weight: bold; + color: white; + width: 40px; + height: 40px; + border-radius: 50%; + border: none; + cursor: pointer; +} + +.sub { + background-color: rgb(230, 0, 0); + box-shadow: 0px 2px 0px darkred; +} + +.sub-non-active { + background-color: lightcoral; + box-shadow: none; +} + +.add { + background-color: rgb(54, 199, 54); + box-shadow: 0px 2px 0px green; +} + +.prod-quantity-left { + display: flex; + justify-content: center; + align-items: center; + height: 40px; + width: 40px; + background-color: rgb(230, 230, 230); + border-radius: var(--main-border-radius); +} + +.card-status { + flex: 1; + display: flex; + justify-content: flex-end; + align-items: center; + gap: 5px; +} + +.status, .buy-btn, #save-btn{ + display: flex; + justify-content: center; + align-items: center; + background-color: rgb(242, 242, 242); + width: 120px; + height: 40px; + border: 1px solid lightgray; + border-radius: var(--main-border-radius); + box-shadow: 0px 3px 0px lightgray; +} + +.buy-btn, #save-btn { + background-color: rgb(163, 69, 251); + border: 1px solid darkviolet; + box-shadow: 0px 3px 0px darkviolet; + color: white; + font-weight: bold; + cursor: pointer; +} + +#save-btn { + margin: 7px; +} + +.discard { + color: white; + background-color: rgb(230, 0, 0); + width: 40px; + height: 40px; + border: none; + border-radius: var(--main-border-radius); + font-size:large; + font-weight: bold; + box-shadow: 0px 3px 0px darkred; + cursor: pointer; +} + +#right-cont h2 { + display: flex; + border-bottom: 1px solid lightgray; + justify-content: flex-start; + align-items: center; + margin: 0px; + padding: 15px; +} + +.prod-list { + display: flex; + justify-content: flex-start; + align-items: center; + padding: 15px; +} + +.prod-remained { + border-bottom: 1px solid lightgray; +} + +.prod-item-rem, .prod-item-bought { + background-color: rgb(230, 230, 230); + padding: 5px; + margin: 5px; + border-radius: var(--main-border-radius); +} + +.prod-item-bought { + text-decoration: line-through; +} + +.prod-quantity-right { + display: inline-flex; + background-color: darkorange; + color: white; + justify-content: center; + align-items: center; + height: 18px; + width: 18px; + border-radius: 50%; + margin: 2px; +} + +.prod-item-bought .prod-quantity-right { + text-decoration: line-through; +} + +.badge { + position: fixed; + left: 20px; + bottom: 0px; + height: 40px; + z-index: 10; + background-color: blueviolet; + border-radius: 10px 10px 0px 0px; + color: white; + font-weight: bold; + cursor: pointer; + transition: height 0.4s ease-in-out, background-color 0.4s ease-in-out; + overflow: hidden +} + +.badge:hover { + background-color: blue; + height: 80px; +} + +.badge-visible, .badge-hidden { + padding: 0px 20px; + height: 40px; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + white-space: nowrap; +} + +.badge-visible { + font-size: large; +} + +.badge-hidden { + font-size: small; + flex-direction: column; +} + +.creator { + font-size: medium; +} + +@media print { + .badge { + background-color: white !important; + border: 2px solid blueviolet; + color: black; + } + + .badge-visible { + display: none; + } +} + +@media (max-width: 499px) { + .main-cont { + flex-direction: column; + align-items: stretch; + } + + #left-cont, #right-cont { + flex: auto; + margin: 0; + } + + #left-cont { + margin-bottom: 20px; + } +} + +button[data-tooltip] { + position: relative; +} + +button[data-tooltip]::after { + z-index: 10; + pointer-events: none; + content: attr(data-tooltip); + opacity: 0; + visibility: hidden; + position: absolute; + bottom: 130%; + left: 50%; + font-size: medium; + background-color: rgb(163, 69, 251); + color: white; + padding: 10px 14px; + border-radius: 10px; + white-space: nowrap; + transform-origin: bottom center; + transform: translateX(-50%) scale(0); + transition: all 0.3s ease-in-out ; +} + +button[data-tooltip]:hover::after { + opacity: 1; + visibility: visible; + transform: translateX(-50%) scale(1); +} \ No newline at end of file