diff --git a/.js b/.js
new file mode 100644
index 00000000..08e3377a
--- /dev/null
+++ b/.js
@@ -0,0 +1,158 @@
+const input = document.querySelector('.text_input');
+const addButton = document.querySelector('.button-add');
+const productList = document.querySelector('.block');
+const initialProducts = ['Apple', 'Orange', 'Cheese'];
+
+initialProducts.forEach(name => {
+ productList.appendChild(createProductItem(name));
+});
+
+function createProductItem(name) {
+ const productItem = document.createElement('span');
+ productItem.className = 'product-item';
+ productItem.innerHTML = `
+ ${name}
+
+
+ 1
+
+
+
+
+ `;
+ attachProductHandlers(productItem);
+ return productItem;
+}
+
+function attachProductHandlers(productItem) {
+ const nameSpan = productItem.querySelector('.product-name');
+ const deleteButton = productItem.querySelector('.button-del');
+ const boughtButton = productItem.querySelector('.button-bought');
+ const minusButton = productItem.querySelector('.minus');
+ const plusButton = productItem.querySelector('.plus');
+ const amountSpan = productItem.querySelector('.amount');
+
+ function updateAmountSymbolState() {
+ const currentAmount = Number(amountSpan.textContent);
+ minusButton.disabled = currentAmount <= 1;
+ plusButton.disabled = currentAmount >= 99;
+ }
+
+ updateAmountSymbolState();
+
+ nameSpan.addEventListener('click', () => {
+ if (!productItem.classList.contains('bought')) {
+ editProductName(nameSpan);
+ }
+ });
+
+ deleteButton.addEventListener('click', () => {
+ productItem.remove();
+ refreshStatistics();
+ });
+
+ plusButton.addEventListener('click', () => {
+ const currentAmount = Number(amountSpan.textContent);
+ amountSpan.textContent = currentAmount + 1;
+ updateAmountSymbolState();
+ refreshStatistics();
+ });
+
+ minusButton.addEventListener('click', () => {
+ const currentAmount = Number(amountSpan.textContent);
+ if (currentAmount > 1) {
+ amountSpan.textContent = currentAmount - 1;
+ updateAmountSymbolState();
+ refreshStatistics();
+ }
+ });
+
+ boughtButton.addEventListener('click', () => {
+ productItem.classList.toggle('bought');
+ boughtButton.textContent = productItem.classList.contains('bought') ? 'Undo' : 'Bought';
+ refreshStatistics();
+ });
+}
+
+function refreshStatistics() {
+ const leftContainer = document.getElementById('stats-left');
+ const boughtContainer = document.getElementById('stats-bought');
+ leftContainer.innerHTML = '';
+ boughtContainer.innerHTML = '';
+
+ document.querySelectorAll('.product-item').forEach(item => {
+ const name = item.querySelector('.product-name').textContent.trim();
+ const amount = item.querySelector('.amount').textContent.trim();
+ const statItem = document.createElement('div');
+ statItem.className = 'stats-item';
+ statItem.innerHTML = `
+ ${name}
+ ${amount}
+ `;
+
+ if (item.classList.contains('bought')) {
+ boughtContainer.appendChild(statItem);
+ } else {
+ leftContainer.appendChild(statItem);
+ }
+ });
+}
+
+
+function editProductName(nameSpan) {
+ const currentName = nameSpan.textContent;
+ const input = document.createElement('input');
+ input.type = 'text';
+ input.className = 'product-name-edit';
+ input.value = currentName;
+
+ nameSpan.replaceWith(input);
+ input.focus();
+ input.select();
+
+ function saveEdit() {
+ const newName = input.value.trim() || currentName;
+ const newNameSpan = document.createElement('span');
+ newNameSpan.className = 'product-name';
+ newNameSpan.textContent = newName;
+
+ input.replaceWith(newNameSpan);
+ newNameSpan.addEventListener('click', () => {
+ if (!newNameSpan.closest('.product-item').classList.contains('bought')) {
+ editProductName(newNameSpan);
+ }
+ });
+ refreshStatistics();
+ }
+
+ input.addEventListener('blur', saveEdit);
+ input.addEventListener('keydown', event => {
+ if (event.key === 'Enter') {
+ input.blur();
+ }
+ });
+}
+
+function addProduct() {
+ const value = input.value.trim();
+ if (!value) {
+ input.value = '';
+ input.focus();
+ return;
+ }
+
+ productList.appendChild(createProductItem(value));
+ refreshStatistics();
+ input.value = '';
+ input.focus();
+}
+
+addButton.addEventListener('click', addProduct);
+input.addEventListener('keydown', event => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ addProduct();
+ }
+});
+
+refreshStatistics();
\ No newline at end of file
diff --git a/bonus/my.js b/bonus/my.js
new file mode 100644
index 00000000..bfc004de
--- /dev/null
+++ b/bonus/my.js
@@ -0,0 +1,153 @@
+let testInitial = [{row:1, column:1, value:1},
+ {row:1, column:2, value:2},
+ {row:1, column:3, value:3},
+ {row:1, column:4, value:4},
+ {row:2, column:1, value:5},
+ {row:2, column:2, value:6},
+ {row:2, column:3, value:7},
+ {row:2, column:4, value:8},
+ {row:3, column:1, value:9},
+ {row:3, column:2, value:10},
+ {row:3, column:3, value:11},
+ {row:3, column:4, value:12},
+ {row:4, column:1, value:13},
+ {row:4, column:2, value:14},
+ {row:4, column:3, value:" "},
+ {row:4, column:4, value:15}]
+
+const size = 4;
+const totalCells = 16;
+
+let currentGrid = generateSolvableGrid();
+//let currentGrid = testInitial;
+
+var pivot = new WebDataRocks({
+ container: "#wdr-component",
+ toolbar: false,
+ height: 152,
+ width: 502,
+ report: {
+ dataSource: {data: currentGrid},
+ slice: {
+ rows:[{uniqueName: "row"}],
+ columns:[{uniqueName: "column"}],
+ measures: [{uniqueName: "value"}]},
+ options: {
+ configuratorButton: false,
+ drillThrough: false,
+ sorting: "off",
+ showHeaders: false,
+ showGrandTotals: "none",
+ showTotals: "none",
+ showHierarchyCaptions: false
+ }
+
+ }
+});
+
+
+
+function handleClick(cell) {
+ //console.log("1. row, col " + cell.rowIndex + " " + cell.columnIndex);
+ const emptyCell = currentGrid.find(c => c.value === " ");
+ if (cell.value === " ") return;
+ //console.log('cell:', cell);
+ //console.log('emptyCell:', emptyCell);
+
+ const rows = [1,2,3,4];
+ const columns = [1,2,3,4];
+ const clickedR = rows.indexOf(cell.rowIndex)+1;
+ const emptyR = emptyCell.row;
+ const clickedC = columns.indexOf(cell.columnIndex)+1;
+ const emptyC = emptyCell.column;
+ //console.log("2. row, col " + cell.rowIndex + " " + cell.columnIndex);
+ const isClose = ((Math.abs(clickedR - emptyR) === 1 && clickedC === emptyC) || (Math.abs(clickedC - emptyC) === 1 && clickedR === emptyR));
+ //console.log(isClose, clickedR, emptyR, clickedC , emptyC, Math.abs(clickedR - emptyR))
+ //console.log("3. row, col " + cell.rowIndex + " " + cell.columnIndex);
+
+ if(isClose) {
+ //console.log("cell aaaaaa " + cell.value)
+ //console.log("empty aaaaaa " + emptyCell.value)
+ emptyCell.value = cell.value;
+ //console.log("empty aaaaaa " + emptyCell.value)
+ //console.log("cell aaaaaa " + cell.value)
+ let record = currentGrid.find(item => item.row === clickedR && item.column === clickedC);
+ if (record) {
+ record.value = " ";
+ }
+
+ //console.log("4. row, col " + cell.rowIndex + " " + cell.columnIndex);
+
+ pivot.updateData({data: currentGrid});
+ checkWin(currentGrid);
+ //console.log("empty aaaaaa " + emptyCell.value)
+ //console.log("5. row, col " + cell.rowIndex + " " + cell.columnIndex);
+ }
+ //console.log("6. row, col " + cell.rowIndex + " " + cell.columnIndex);
+
+};
+
+function checkWin(data) {
+ for (let cell of data) {
+ const index = (cell.row - 1) * size + (cell.column - 1);
+ const expectedValue = (index === totalCells - 1) ? " " : index + 1;
+ if (cell.value !== expectedValue) return false;
+ }
+ console.log("You won");
+ alert("You have won!");
+ return true;
+};
+
+pivot.on('cellclick', handleClick);
+
+
+
+function generateRandomGrid() {
+ const numbers = Array.from({ length: totalCells - 1 }, (_, i) => i + 1);
+ numbers.push(" ");
+
+ for (let i = numbers.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [numbers[i], numbers[j]] = [numbers[j], numbers[i]];
+ }
+
+ const grid = [];
+ for (let index = 0; index < totalCells; index++) {
+ const row = Math.floor(index / size) + 1;
+ const col = (index % size) + 1;
+ grid.push({
+ row: row,
+ column: col,
+ value: numbers[index]
+ });
+ }
+ return grid;
+}
+
+function isSolvable(grid) {
+ const arr = grid.map(c => c.value).filter(val => val !== "");
+ let invCount = 0;
+
+ for (let i = 0; i < arr.length - 1; i++) {
+ for (let j = i + 1; j < arr.length; j++) {
+ if (arr[i] > arr[j]) invCount++;
+ }
+ }
+
+ const blankIndex = grid.findIndex(c => c.value === " ");
+ const blankRow = Math.floor(blankIndex / size);
+
+ if (size % 2 !== 0) {
+ return invCount % 2 === 0;
+ } else {
+ return (invCount + blankRow) % 2 !== 0;
+ }
+}
+
+function generateSolvableGrid() {
+ let grid;
+ do {
+ grid = generateRandomGrid();
+ } while (!isSolvable(grid));
+ return grid;
+}
\ No newline at end of file
diff --git a/bonus/page.html b/bonus/page.html
new file mode 100644
index 00000000..e1b3c9a9
--- /dev/null
+++ b/bonus/page.html
@@ -0,0 +1,19 @@
+
+
+
+
+ 15-tiles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 5971bf7d..8a852a23 100644
--- a/index.html
+++ b/index.html
@@ -1,16 +1,38 @@
-
+
My Page
+
-
- Apple
- 4
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.css b/main.css
index 00bc872e..800be954 100644
--- a/main.css
+++ b/main.css
@@ -1,17 +1,232 @@
+body {
+ background-color: #afafaf;
+}
+
+[data-tooltip] {
+ position: relative;
+ cursor: pointer;
+}
+
+[data-tooltip]::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ bottom: 125%;
+ left: 0%;
+ border: none;
+ background-color: #d00ad7;
+ color: #ffffff;
+ border-radius: 10px;
+ font-size: 14px;
+ white-space: nowrap;
+
+ opacity: 0;
+ transition: 1s;
+ pointer-events: none;
+}
+
+[data-tooltip]:hover::after {
+ display: block;
+ position: absolute;
+ padding: 7px;
+ border-radius: 7px;
+ opacity: 1;
+}
+
+.badge {
+ border: none;
+ background-color: #d00ad7;
+ color: #ffffff;
+ border-radius: 10px 10px 0px 0;
+ font-size: 16px;
+ padding: 13px;
+ position: fixed;
+ bottom: 0;
+ left: 5px;
+ text-align: left;
+ transition: background-color 1s, height 2s linear;
+}
+
+.badge-by {
+ display: block;
+ font-size: 10px;
+ margin: 10px 0 2px 0;
+ visibility: hidden;
+ height: 0;
+ transition: height 2s linear;
+}
+
+.badge-name{
+ display: block;
+ visibility: hidden;
+ height: 0;
+ transition: height 2s linear;
+}
+
+.badge:hover {
+ background-color: #2954ca;
+}
+
+.badge:hover .badge-by, .badge:hover .badge-name {
+ visibility: visible;
+ height: auto;
+}
+
+.badge:active {
+ background-color: #f0f8ff;
+ border: 2px solid #d00ad7;
+ color: #d00ad7;
+}
+
+.badge:active b {
+ display: none;
+}
+
+.add-product-item {
+ color: #778899;
+ padding-bottom: 5px;
+ font-size: 20px;
+}
+
+.text_input {
+ width: 300px;
+ padding: 3px;
+}
+
+.button-add {
+ background-color: #2954ca;
+ color: #ffffff;
+ border: none;
+ padding: 4px 8px;
+}
+
.product-item {
- background-color: gray;
+ width: 450px;
+ background-color: #ffffff;
display: inline-block;
height: 25px;
padding: 5px;
border-radius: 5px;
+ margin: 0px 0px 5px 0px;
+ position: relative;
+}
+
+.amount-block {
+ display: inline-block;
+ position: absolute;
+ right: 40%;
}
.amount {
- background-color: yellow;
- border-radius: 10px;
+ background-color: #afafaf;
+ border-radius: 5px;
display: inline-block;
height: 20px;
width: 20px;
text-align: center;
+}
+
+.plus {
+ background-color: #2ac92f;
+ color: #ffffff;
+ border: none;
+ padding: 4px 8px;
+ border-radius: 15px;
+}
+
+.plus:hover {
+ background-color: #166818;
+}
+
+.minus {
+ background-color: #ca2929;
+ color: #ffffff;
+ border: none;
+ padding: 4px 8px;
+ border-radius: 15px;
+}
+
+.minus:hover {
+ background-color: #991e1e;
+}
+
+.button-bought {
+ position: absolute;
+ right: 15%;
+}
+
+.button-del {
+ background-color: #ca2929;
+ color: #ffffff;
+ border: none;
+ padding: 4px 8px;
+ border-radius: 5px;
+ float: right;
+}
+
+.button-del:hover {
+ background-color: #991e1e;
+}
+
+.minus:disabled,
+.plus:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.stats-section {
+ width: 450px;
+ margin-top: 20px;
+ padding: 10px;
+ background-color: #ffffff;
+ border-radius: 8px;
+}
+
+.stats-group {
+ padding: 10px 0;
+}
+
+.stats-title {
+ font-weight: bold;
+ margin-bottom: 8px;
+}
+
+.stats-divider {
+ height: 1px;
+ background-color: #afafaf;
+}
+
+.stats-list {
+ display: block;
+}
+
+.stats-item {
+ display: inline-block;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 6px;
+ background-color: #afafaf;
+ border-radius: 6px;
+ margin: 5px;
+}
+
+.stats-product-name {
+ font-size: 14px;
+}
+
+.stats-amount {
+ background-color: #ffa500;
+ color: #ffffff;
+ padding: 2px 7px;
+ border-radius: 12px;
+ font-weight: bold;
+ text-align: center;
+}
+
+.product-item.bought .product-name {
+ text-decoration: line-through;
+}
+
+.product-item.bought .amount-block, .product-item.bought .button-del {
+ display: none;
}
\ No newline at end of file