From 3df8e55accd5d0e867baf5138914f639c8b23855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=B3=D0=BE=D1=80=20=D0=9C=D0=BE=D0=B2=D1=87=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 16 May 2026 00:45:22 +0300 Subject: [PATCH 1/3] Complete Buy List --- index.html | 95 ++++++++++++++-- main.css | 17 --- styles.css | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 408 insertions(+), 29 deletions(-) delete mode 100644 main.css create mode 100644 styles.css diff --git a/index.html b/index.html index 5971bf7d..321265f3 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,87 @@ - - - - My Page + + + BuyList + + + + + +
+
+ + +
    +
  • + Помідори +
    + 2 +
    +
    + Не куплено +
    +
  • +
  • + Печиво - - - - - Apple - 4 - - +
    + + 2 + +
    +
    + Куплено + +
    + +
  • +
  • + + +
    + + 2 + +
    + +
    + Куплено + +
    +
  • +
+
+ +
+
+

Залишилося

+
+ Печиво2 + Сир1 +
+
+ +
+

Куплено

+
+ Помідори2 +
+
+ +
+
+
+
+ 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/styles.css b/styles.css new file mode 100644 index 00000000..859dbe1e --- /dev/null +++ b/styles.css @@ -0,0 +1,325 @@ +: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, #last-input-bar { + flex: 1; + display: flex; + justify-content: flex-start; + font-size: large; + box-sizing: border-box; +} + +#tomato { + text-decoration: line-through; +} + +#last-input-bar { + outline: none; + border-radius: var(--main-border-radius); + border: 1px solid lightgray; + flex: none; + width: 60%; + height: 30px; + font-size: small; +} + +#last-input-bar:focus { + border-color: rgb(70, 210, 245); + box-shadow: 0 0 8px rgba(102, 175, 233, 0.6); +} + +#last-input-bar: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; +} + +.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 { + 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; +} + +.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 From 74b690da7fa26c23e9bbc235ac9fd6184b571ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=B3=D0=BE=D1=80=20=D0=9C=D0=BE=D0=B2=D1=87=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 23 May 2026 14:34:15 +0300 Subject: [PATCH 2/3] feat: complete homework2 + bonus --- index.html | 48 ++------------ main.js | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 31 +++++++-- 3 files changed, 213 insertions(+), 49 deletions(-) create mode 100644 main.js diff --git a/index.html b/index.html index 321265f3..d7e3dd3e 100644 --- a/index.html +++ b/index.html @@ -15,63 +15,24 @@ - +

Залишилося

- Печиво2 - Сир1

Куплено

- Помідори2
- +
+ +
@@ -83,5 +44,6 @@

Куплено

Ihor Movchan
+ \ 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 index 859dbe1e..0cedc92a 100644 --- a/styles.css +++ b/styles.css @@ -73,19 +73,20 @@ li { gap: 15px; } -.prod-name, #last-input-bar { +.prod-name, .turn-into-input { flex: 1; display: flex; justify-content: flex-start; font-size: large; box-sizing: border-box; + cursor: pointer; } -#tomato { +.bought { text-decoration: line-through; } -#last-input-bar { +.turn-into-input { outline: none; border-radius: var(--main-border-radius); border: 1px solid lightgray; @@ -95,12 +96,12 @@ li { font-size: small; } -#last-input-bar:focus { +.turn-into-input:focus { border-color: rgb(70, 210, 245); box-shadow: 0 0 8px rgba(102, 175, 233, 0.6); } -#last-input-bar:hover { +.turn-into-input:hover { border-color: rgb(70, 210, 245); } @@ -128,6 +129,11 @@ li { 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; @@ -151,7 +157,7 @@ li { gap: 5px; } -.status { +.status, .buy-btn, #save-btn{ display: flex; justify-content: center; align-items: center; @@ -163,6 +169,19 @@ li { 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); From 17955727d9c1f1fd6ebe0257bd49a1ec567d909c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=B3=D0=BE=D1=80=20=D0=9C=D0=BE=D0=B2=D1=87=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Wed, 17 Jun 2026 16:58:38 +0300 Subject: [PATCH 3/3] feat: complete bonus task --- Bonus/index.html | 19 ++++++ Bonus/script.js | 148 +++++++++++++++++++++++++++++++++++++++++++++++ Bonus/styles.css | 66 +++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 Bonus/index.html create mode 100644 Bonus/script.js create mode 100644 Bonus/styles.css 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