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 @@
+
+
+
+
+
+ П'ятнашки
+
+
+
+
+
+
+
+
+
+
+
\ 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