diff --git a/bonus/index.html b/bonus/index.html
new file mode 100644
index 00000000..e1f741fb
--- /dev/null
+++ b/bonus/index.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+ 💣 Сапер — Minesweeper на WebDataRocks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bonus/main.css b/bonus/main.css
new file mode 100644
index 00000000..0a000278
--- /dev/null
+++ b/bonus/main.css
@@ -0,0 +1,352 @@
+:root
+{
+ --bg-page: #e8e4df;
+ --bg-card: #ffffff;
+ --bg-header: #f5f3f0;
+ --bg-closed: #c8c4be;
+ --bg-closed-hover: #bab6b0;
+ --bg-open: #e8e4df;
+ --border-color: #d0ccc6;
+ --border-dark: #a8a49e;
+ --text-primary: #2c2c2c;
+ --text-secondary: #6b6b6b;
+ --text-muted: #999;
+ --accent: #5a7a5a;
+ --danger: #b94a48;
+ --cell-size: 72px;
+ --radius: 6px;
+ --font-main: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+}
+
+*,
+*::before,
+*::after
+{
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html
+{
+ font-size: 16px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body
+{
+ font-family: var(--font-main);
+ background: var(--bg-page);
+ color: var(--text-primary);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 100vh;
+ padding: 32px 16px;
+ overflow-x: hidden;
+}
+
+
+
+
+#game-header
+{
+ width: 100%;
+ max-width: 860px;
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+#game-title
+{
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ color: var(--text-primary);
+ margin-bottom: 16px;
+}
+
+
+
+#scoreboard
+{
+ display: flex;
+ justify-content: center;
+ align-items: stretch;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.score-cell
+{
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius);
+ padding: 10px 18px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-width: 100px;
+}
+
+.score-label
+{
+ font-size: 0.7rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--text-secondary);
+ margin-bottom: 4px;
+}
+
+.score-value
+{
+ font-size: 1.5rem;
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-primary);
+}
+
+
+#restart-btn,
+#mode-toggle
+{
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius);
+ padding: 8px 16px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-family: var(--font-main);
+ color: var(--text-primary);
+ font-size: 0.85rem;
+ font-weight: 600;
+ transition: background-color 0.15s ease;
+}
+
+#restart-btn:hover,
+#mode-toggle:hover
+{
+ background: var(--bg-header);
+}
+
+#restart-btn:active,
+#mode-toggle:active
+{
+ background: var(--bg-page);
+}
+
+#restart-btn #face
+{
+ font-size: 1.8rem;
+ line-height: 1;
+}
+
+#mode-icon
+{
+ font-size: 1.2rem;
+}
+
+/*Прапорець */
+#mode-toggle.flag-mode
+{
+ border-color: var(--border-dark);
+ background: var(--bg-header);
+}
+
+#pivot-wrapper
+{
+ width: 100%;
+ max-width: 860px;
+ border-radius: var(--radius);
+ overflow: hidden;
+ border: 1px solid var(--border-color);
+ background: var(--bg-card);
+}
+
+#wdr-container
+{
+ width: 100%;
+ position: relative;
+}
+
+
+
+#wdr-container .wdr-credits,
+#wdr-container .wdr-toolbar-wrapper,
+#wdr-container .wdr-sheet-list
+{
+ display: none !important;
+}
+
+#wdr-container .wdr-grid-layout
+{
+ border: none !important;
+ background: var(--bg-header) !important;
+}
+
+#wdr-container td.wdr-cell,
+#wdr-container th.wdr-cell,
+#wdr-container td.wdr-header,
+#wdr-container th.wdr-header
+{
+ width: var(--cell-size) !important;
+ min-width: var(--cell-size) !important;
+ max-width: var(--cell-size) !important;
+ height: var(--cell-size) !important;
+ min-height: var(--cell-size) !important;
+ max-height: var(--cell-size) !important;
+ text-align: center !important;
+ vertical-align: middle !important;
+ font-size: 1.3rem !important;
+ line-height: var(--cell-size) !important;
+ padding: 0 !important;
+ overflow: hidden !important;
+ font-family: var(--font-main) !important;
+}
+
+#wdr-container .wdr-header
+{
+ background: var(--bg-header) !important;
+ color: var(--text-muted) !important;
+ font-size: 0.65rem !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.5px !important;
+ border: 1px solid var(--border-color) !important;
+}
+
+#wdr-container td.wdr-cell
+{
+ border: 1px solid var(--border-color) !important;
+ user-select: none;
+}
+
+#wdr-container .wdr-grid-view
+{
+ overflow: hidden !important;
+}
+
+#wdr-container .wdr-cell
+{
+ -webkit-user-select: none !important;
+ user-select: none !important;
+}
+
+.cell-closed
+{
+ background: var(--bg-closed) !important;
+ border-top: 2px solid #ddd !important;
+ border-left: 2px solid #ddd !important;
+ border-bottom: 2px solid #999 !important;
+ border-right: 2px solid #999 !important;
+ cursor: pointer !important;
+}
+
+.cell-closed:hover
+{
+ background: var(--bg-closed-hover) !important;
+}
+
+/* Прапорець */
+.cell-flag
+{
+ background: var(--bg-closed) !important;
+ border-top: 2px solid #ddd !important;
+ border-left: 2px solid #ddd !important;
+ border-bottom: 2px solid #999 !important;
+ border-right: 2px solid #999 !important;
+ cursor: pointer !important;
+ font-size: 1.3rem !important;
+}
+
+.cell-open-0
+{
+ background: var(--bg-open) !important;
+ color: transparent !important;
+ cursor: default !important;
+}
+
+.cell-open-number
+{
+ background: var(--bg-open) !important;
+ cursor: default !important;
+ font-weight: 800 !important;
+ font-size: 1.3rem !important;
+}
+
+.cell-num-1 { color: #0000ff !important; }
+.cell-num-2 { color: #008000 !important; }
+.cell-num-3 { color: #ff0000 !important; }
+.cell-num-4 { color: #000080 !important; }
+.cell-num-5 { color: #800000 !important; }
+.cell-num-6 { color: #008080 !important; }
+.cell-num-7 { color: #000000 !important; }
+.cell-num-8 { color: #808080 !important; }
+
+
+.cell-mine
+{
+ background: var(--bg-open) !important;
+ cursor: default !important;
+ font-size: 1.3rem !important;
+}
+
+.cell-mine-exploded
+{
+ background: #e8b4b4 !important;
+ cursor: default !important;
+ font-size: 1.4rem !important;
+}
+
+
+
+
+@media (max-width: 580px)
+{
+ :root
+ {
+ --cell-size: 42px;
+ }
+
+ #game-title
+ {
+ font-size: 1.2rem;
+ letter-spacing: 1px;
+ }
+
+ #scoreboard
+ {
+ gap: 6px;
+ }
+
+ .score-cell
+ {
+ min-width: 80px;
+ padding: 8px 10px;
+ }
+
+ .score-value
+ {
+ font-size: 1.2rem;
+ }
+
+ #wdr-container td.wdr-cell,
+ #wdr-container th.wdr-cell,
+ #wdr-container td.wdr-header,
+ #wdr-container th.wdr-header {
+ font-size: 1rem !important;
+ }
+}
+
+@media (max-width: 400px)
+{
+ :root
+ {
+ --cell-size: 34px;
+ }
+}
diff --git a/bonus/main.js b/bonus/main.js
new file mode 100644
index 00000000..d532e19a
--- /dev/null
+++ b/bonus/main.js
@@ -0,0 +1,474 @@
+const ROWS = 9;
+const COLS = 9;
+const MINES_TOTAL = 10;
+
+const SYM_CLOSED = "⬜";
+const SYM_FLAG = "🚩";
+const SYM_MINE = "💣";
+
+
+let gridData = [];
+let gameOver = false;
+let gameWon = false;
+let flagMode = false;
+let timerInterval = null;
+let seconds = 0;
+let timerStarted = false;
+let explodedRow = -1;
+let explodedCol = -1;
+
+const elMineCounter = document.getElementById("mine-counter");
+const elTimer = document.getElementById("timer");
+const elFace = document.getElementById("face");
+const elModeToggle = document.getElementById("mode-toggle");
+const elModeIcon = document.getElementById("mode-icon");
+const elModeText = document.getElementById("mode-text");
+const elRestartBtn = document.getElementById("restart-btn");
+
+
+let pivot = null;
+
+
+
+function generateGrid()
+{
+ const data = [];
+
+ for (let r = 0; r < ROWS; r++)
+ {
+ for (let c = 0; c < COLS; c++)
+ {
+ data.push({
+ Row: `Рядок ${r + 1}`,
+ Col: `Кол ${c + 1}`,
+ IsMine: false,
+ MinesAround: 0,
+ IsOpen: false,
+ IsFlagged: false,
+ Display: SYM_CLOSED,
+ _row: r,
+ _col: c
+ });
+ }
+ }
+
+ let minesPlaced = 0;
+ while (minesPlaced < MINES_TOTAL)
+ {
+ const idx = Math.floor(Math.random() * data.length);
+ if (!data[idx].IsMine)
+ {
+ data[idx].IsMine = true;
+ minesPlaced++;
+ }
+ }
+
+ for (let r = 0; r < ROWS; r++)
+ {
+ for (let c = 0; c < COLS; c++)
+ {
+ const cell = getCellByRC(data, r, c);
+ if (cell.IsMine) continue; // для мін не рахуємо
+ let count = 0;
+ forEachNeighbor(r, c, (nr, nc) => {
+ const neighbor = getCellByRC(data, nr, nc);
+ if (neighbor && neighbor.IsMine) count++;
+ });
+ cell.MinesAround = count;
+ }
+ }
+
+ return data;
+}
+
+
+function getCellByRC(data, row, col)
+{
+ return data[row * COLS + col];
+}
+
+
+function forEachNeighbor(row, col, callback)
+{
+ for (let dr = -1; dr <= 1; dr++)
+ {
+ for (let dc = -1; dc <= 1; dc++)
+ {
+ if (dr === 0 && dc === 0) continue;
+ const nr = row + dr;
+ const nc = col + dc;
+ if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS)
+ {
+ callback(nr, nc);
+ }
+ }
+ }
+}
+
+
+
+function startGame() {
+ gameOver = false;
+ gameWon = false;
+ flagMode = false;
+ seconds = 0;
+ timerStarted = false;
+ explodedRow = -1;
+ explodedCol = -1;
+ clearInterval(timerInterval);
+ timerInterval = null;
+
+ elFace.textContent = "🙂";
+ elTimer.textContent = "000";
+ elModeIcon.textContent = "⛏️";
+ elModeText.textContent = "Копати";
+ elModeToggle.classList.remove("flag-mode");
+ document.body.classList.remove("game-won");
+
+ gridData = generateGrid();
+ updateMineCounter();
+
+ if (pivot)
+ {
+ applyReport();
+ }
+}
+
+
+function updateMineCounter()
+{
+ const flagsCount = gridData.filter(c => c.IsFlagged).length;
+ const remaining = MINES_TOTAL - flagsCount;
+ elMineCounter.textContent = String(remaining).padStart(2, "0");
+}
+
+
+
+function startTimer()
+{
+ if (timerStarted) return;
+ timerStarted = true;
+ timerInterval = setInterval(() => {
+ seconds++;
+ if (seconds > 999) seconds = 999;
+ elTimer.textContent = String(seconds).padStart(3, "0");
+ }, 1000);
+}
+
+
+function stopTimer()
+{
+ clearInterval(timerInterval);
+ timerInterval = null;
+}
+
+
+
+function buildReport()
+{
+ return {
+ dataSource: {
+ data: prepareDataForWDR()
+ },
+ slice: {
+ rows: [{ uniqueName: "Row" }],
+ columns: [{ uniqueName: "Col" }],
+ measures: [{
+ uniqueName: "Display",
+ aggregation: "max"
+ }]
+ },
+ options: {
+ grid: {
+ type: "classic",
+ showGrandTotals: "off",
+ showTotals: "off"
+ },
+ showAggregationLabels: false
+ }
+ };
+}
+
+
+function prepareDataForWDR()
+{
+ return gridData.map(cell => ({
+ Row: cell.Row,
+ Col: cell.Col,
+ Display: cell.Display
+ }));
+}
+
+
+function applyReport()
+{
+ pivot.setReport(buildReport());
+}
+
+
+
+function refreshPivot()
+{
+ if (!pivot) return;
+ pivot.updateData({ data: prepareDataForWDR() });
+}
+
+
+
+function onCustomizeCell(cell, data)
+{
+ if (!data || data.type !== "value") return;
+
+ const rowName = (data.rows && data.rows.length > 0) ? (data.rows[0].caption || data.rows[0].member) : null;
+ const colName = (data.columns && data.columns.length > 0) ? (data.columns[0].caption || data.columns[0].member) : null;
+
+ if (!rowName || !colName) return;
+
+ const cellObj = gridData.find(c => c.Row === rowName && c.Col === colName);
+ if (!cellObj) return;
+
+ const value = cellObj.Display;
+ cell.text = value;
+
+ if (value === SYM_CLOSED)
+ {
+ cell.addClass("cell-closed");
+ }
+ else if (value === SYM_FLAG)
+ {
+ cell.addClass("cell-flag");
+ }
+ else if (value === SYM_MINE)
+ {
+ if (cellObj._row === explodedRow && cellObj._col === explodedCol)
+ {
+ cell.addClass("cell-mine-exploded");
+ }
+ else
+ {
+ cell.addClass("cell-mine");
+ }
+ }
+ else if (value === "·")
+ {
+ cell.addClass("cell-open-0");
+ cell.text = "";
+ }
+ else
+ {
+ const num = parseInt(value, 10);
+ if (!isNaN(num) && num >= 1 && num <= 8) {
+ cell.addClass("cell-open-number");
+ cell.addClass("cell-num-" + num);
+ }
+ }
+}
+
+
+function onCellClick(cellData)
+{
+ if (gameOver || gameWon) return;
+
+ if (!cellData || !cellData.rows || cellData.rows.length === 0 ||
+ !cellData.columns || cellData.columns.length === 0) {
+ return;
+ }
+
+ if (!cellData.measure) return;
+
+ const rowName = cellData.rows[0].caption || cellData.rows[0].member;
+
+
+ const colObj = cellData.columns.find(c => c.hierarchyName === "Col" || (c.caption && c.caption.startsWith("Кол")));
+ if (!colObj) return;
+ const colName = colObj.caption || colObj.member;
+
+ const cell = gridData.find(c => c.Row === rowName && c.Col === colName);
+ if (!cell) return;
+
+ startTimer();
+
+ if (flagMode)
+ {
+ handleFlagClick(cell);
+ }
+ else
+ {
+ handleDigClick(cell);
+ }
+}
+
+
+function handleDigClick(cell)
+{
+
+ if (cell.IsOpen || cell.IsFlagged) return;
+
+ if (cell.IsMine)
+ {
+
+ handleGameOver(cell);
+ }
+ else if (cell.MinesAround === 0)
+ {
+ openCell(cell);
+ floodFill(cell._row, cell._col);
+ refreshPivot();
+ checkWin();
+ }
+ else
+ {
+ openCell(cell);
+ refreshPivot();
+ checkWin();
+ }
+}
+
+
+function handleFlagClick(cell)
+{
+ if (cell.IsOpen) return;
+
+
+ cell.IsFlagged = !cell.IsFlagged;
+ cell.Display = cell.IsFlagged ? SYM_FLAG : SYM_CLOSED;
+
+ updateMineCounter();
+ refreshPivot();
+}
+
+
+function openCell(cell)
+{
+ if (cell.IsOpen || cell.IsFlagged) return;
+ cell.IsOpen = true;
+
+ if (cell.MinesAround === 0)
+ {
+ cell.Display = "·";
+ }
+ else
+ {
+ cell.Display = String(cell.MinesAround);
+ }
+}
+
+
+function floodFill(row, col)
+{
+ forEachNeighbor(row, col, (nr, nc) => {
+ const neighbor = getCellByRC(gridData, nr, nc);
+ if (neighbor.IsOpen || neighbor.IsFlagged || neighbor.IsMine) return;
+
+ openCell(neighbor);
+
+ if (neighbor.MinesAround === 0)
+ {
+ floodFill(nr, nc);
+ }
+ });
+}
+
+
+
+function handleGameOver(clickedMine) {
+ gameOver = true;
+ stopTimer();
+
+
+ explodedRow = clickedMine._row;
+ explodedCol = clickedMine._col;
+
+
+ gridData.forEach(cell => {
+ if (cell.IsMine) {
+ cell.IsOpen = true;
+ cell.Display = SYM_MINE;
+ }
+ });
+
+
+ elFace.textContent = "😵";
+
+
+
+ refreshPivot();
+}
+
+
+function checkWin()
+{
+ const nonMineCells = gridData.filter(c => !c.IsMine);
+ const allOpened = nonMineCells.every(c => c.IsOpen);
+
+ if (allOpened)
+ {
+ gameWon = true;
+ stopTimer();
+
+ elFace.textContent = "😎";
+
+ gridData.forEach(cell => {
+ if (cell.IsMine && !cell.IsFlagged) {
+ cell.IsFlagged = true;
+ cell.Display = SYM_FLAG;
+ }
+ });
+ updateMineCounter();
+
+ document.body.classList.add("game-won");
+
+ refreshPivot();
+ }
+}
+
+
+
+elRestartBtn.addEventListener("click", () => {
+ startGame();
+});
+
+elModeToggle.addEventListener("click", () => {
+ flagMode = !flagMode;
+
+ if (flagMode)
+ {
+ elModeIcon.textContent = "🚩";
+ elModeText.textContent = "Прапорець";
+ elModeToggle.classList.add("flag-mode");
+ }
+ else
+ {
+ elModeIcon.textContent = "⛏️";
+ elModeText.textContent = "Копати";
+ elModeToggle.classList.remove("flag-mode");
+ }
+});
+
+
+
+let cellClickRegistered = false;
+
+function initPivot()
+{
+ gridData = generateGrid();
+ updateMineCounter();
+
+ pivot = new WebDataRocks({
+ container: "#wdr-container",
+ toolbar: false,
+ width: "100%",
+ height: "auto",
+ report: buildReport(),
+ customizeCell: onCustomizeCell,
+ reportcomplete: function () {
+ if (!cellClickRegistered)
+ {
+ cellClickRegistered = true;
+ pivot.on("cellclick", onCellClick);
+ }
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", initPivot);
diff --git a/index.html b/index.html
index 5971bf7d..3752d31f 100644
--- a/index.html
+++ b/index.html
@@ -1,16 +1,75 @@
-
-
+
+
- My Page
-
-
+
+ Кошик покупок
-
- Apple
- 4
-
+
+
+
+
+
+
+
+
+
+
+
Помідори
+
+ 2
+
+
+
+
+
+
Печиво
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
Залишилося
+
+ Печиво 2
+ Сир 1
+
+
+
+
+
Куплено
+
+ Помідори 2
+
+
+
+
+
+
+ Buy List
+ Виконано: Мартинов Богдан
+
+
\ No newline at end of file
diff --git a/main.css b/main.css
index 00bc872e..758ab52f 100644
--- a/main.css
+++ b/main.css
@@ -1,17 +1,397 @@
-.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;
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+}
+
+body
+{
+ background-color: #e5e5e5;
+ min-height: 100vh;
+ padding: 40px 20px;
+}
+
+
+.app-container
+{
+ max-width: 1000px;
+ margin: 0 auto;
+ display: flex;
+ gap: 24px;
+ align-items: flex-start;
+}
+
+.products-section
+{
+ flex: 2;
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 16px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
+}
+
+.summary-section
+{
+ flex: 1;
+}
+
+.summary-card
+{
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
+}
+
+
+.add-item-box
+{
+ display: flex;
+ gap: 12px;
+ border-bottom: 2px solid #f0f0f0;
+ padding-bottom: 16px;
+ margin-bottom: 8px;
+}
+
+.input-field, .input-edit
+{
+ flex: 1;
+ padding: 10px 14px;
+ border: 1px solid #dcdcdc;
+ border-radius: 4px;
+ font-size: 16px;
+ outline: none;
+}
+
+.input-edit
+{
+ border-color: #4285f4;
+ max-width: 200px;
+}
+
+.input-field:focus, .input-edit:focus
+{
+ border-color: #4285f4;
+ box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
+}
+
+
+button
+{
+ cursor: pointer;
+ border: none;
+ outline: none;
+ font-size: 14px;
+ transition: background-color 0.2s ease, transform 0.1s ease;
+}
+
+button:focus-visible
+{
+ outline: 3px solid #7b2cbf;
+ outline-offset: 2px;
+}
+
+.btn-add
+{
+ background-color: #4285f4;
+ color: white;
+ padding: 10px 24px;
+ border-radius: 4px;
+ font-weight: bold;
+}
+
+.btn-add:hover { background-color: #357ae8; }
+
+
+.products-list
+{
+ display: flex;
+ flex-direction: column;
+}
+
+.product-row
+{
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.product-row:last-child
+{
+ border-bottom: none;
+}
+
+.product-name
+{
+ font-size: 18px;
+ color: #333;
+}
+
+
+
+
+.controls
+{
+ display: grid;
+ grid-template-columns: 28px 28px 28px 100px 28px;
+ gap: 8px;
+ align-items: center;
+ justify-items: center;
+}
+
+.amount-badge
+{
+ background-color: #e0e0e0;
+ color: #333;
+ width: 28px;
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ font-weight: bold;
+ font-size: 14px;
+}
+
+.btn-plus, .btn-minus
+{
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ color: white;
+ font-weight: bold;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+
+.btn-plus { background-color: #22ba46; }
+.btn-plus:hover { background-color: #1e9e3b; }
+
+
+.btn-minus { background-color: #db2828; }
+.btn-minus:hover { background-color: #b82222; }
+
+
+.btn-minus.disabled
+{
+ background-color: #ef9f9e;
+ cursor: not-allowed;
+}
+
+.btn-status
+{
+ width: 100%;
+ background-color: #eeeeee;
+ color: #444;
+ padding: 6px 0;
+ border-radius: 4px;
+ border: 1px solid #dcdcdc;
+ text-align: center;
+}
+.btn-status:hover { background-color: #e0e0e0; }
+
+.btn-status.active
+{
+ background-color: #f8f9fa;
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
+}
+
+.btn-delete
+{
+ background-color: #db2828;
+ color: white;
+ width: 28px;
+ height: 28px;
+ border-radius: 4px;
+ font-size: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.btn-delete:hover { background-color: #b82222; }
+
+
+.summary-card h2
+{
+ font-size: 20px;
+ margin-bottom: 14px;
+ color: #111;
+}
+
+.summary-divider
+{
+ border: none;
+ border-top: 1px solid #f0f0f0;
+ margin: 20px 0;
+}
+
+.summary-tags
+{
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.product-tag
+{
+ background-color: #eeeeee;
+ padding: 6px 12px;
+ border-radius: 20px;
+ font-size: 14px;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ color: #444;
+}
+
+.product-tag.bought
+{
+ text-decoration: line-through;
+ color: #888;
+}
+
+
+.tag-count {
+ background-color: #f2711d;
+ color: white;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+
+[data-tooltip]
+{
+ position: relative;
+}
+
+[data-tooltip]::after
+{
+ content: attr(data-tooltip);
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translate(-50%, 8px) scale(0.6);
+ opacity: 0;
+ pointer-events: none;
+ transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.2s ease;
+
+ background-color: #7b2cbf;
+ color: #ffffff;
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: normal;
+ white-space: nowrap;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
+ margin-bottom: 8px;
+ z-index: 999;
+}
+
+[data-tooltip]:hover::after,
+[data-tooltip]:focus-visible::after
+{
+ opacity: 1;
+ transform: translate(-50%, 0) scale(1);
+}
+
+
+
+.author-badge
+{
+ position: fixed;
+ bottom: 0;
+ left: 40px;
+ background-color: #5a189a;
+ color: #ffffff;
+ padding: 12px 24px;
+ border-radius: 12px 12px 0 0;
+ font-weight: bold;
+ font-size: 16px;
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.15);
+ z-index: 1000;
+ cursor: default;
+
+ height: 45px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ transition: height 0.3s ease, background-color 0.3s ease;
+}
+
+.badge-text-author
+{
+ font-weight: normal;
+ font-size: 13px;
+ opacity: 0.9;
+}
+
+.author-badge:hover, .author-badge:focus-within
+{
+ height: 75px;
+ background-color: #3c096c;
+}
+
+
+@media (max-width: 650px)
+{
+ body
+ {
+ padding: 16px 10px;
+ }
+
+ .app-container
+ {
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .products-section, .summary-section
+ {
+ width: 100%;
+ flex: none;
+ }
+}
+
+
+@media print
+{
+ .author-badge
+ {
+ position: absolute;
+ bottom: 20px;
+ left: 20px;
+ background: #ffffff;
+ color: #5a189a;
+ border: 2px solid #5a189a;
+ height: auto;
+ padding: 8px 16px;
+ border-radius: 8px;
+ box-shadow: none;
+ }
+
+ .badge-text-main
+ {
+ display: none;
+ }
+
+ .badge-text-author
+ {
+ display: block;
+ font-size: 16px;
+ font-weight: bold;
+ }
}
\ No newline at end of file
diff --git a/main.js b/main.js
new file mode 100644
index 00000000..cb9fd28f
--- /dev/null
+++ b/main.js
@@ -0,0 +1,222 @@
+document.addEventListener('DOMContentLoaded', () => {
+ let products = [
+ { id: 1, name: 'Помідори', amount: 2, isBought: true },
+ { id: 2, name: 'Печиво', amount: 2, isBought: false },
+ { id: 3, name: 'Сир', amount: 1, isBought: false }
+ ];
+
+ let nextId = 4;
+
+ const productsListElement = document.querySelector('.products-list');
+ const inputField = document.querySelector('.input-field');
+ const btnAdd = document.querySelector('.btn-add');
+ const summaryTagsContainers = document.querySelectorAll('.summary-tags');
+
+ function renderList()
+ {
+ productsListElement.innerHTML = '';
+
+ products.forEach(product => {
+ productsListElement.appendChild(createProductRow(product));
+ });
+
+ updateSummary();
+ }
+
+ function createProductRow(product)
+ {
+ const row = document.createElement('div');
+ row.className = `product-row ${product.isBought ? 'bought' : ''}`;
+
+
+ const nameSpan = document.createElement('span');
+ nameSpan.className = 'product-name';
+ nameSpan.textContent = product.name;
+
+ const nameInput = document.createElement('input');
+ nameInput.type = 'text';
+ nameInput.className = 'input-edit';
+ nameInput.setAttribute('aria-label', 'Редагувати назву товару');
+
+ if (!product.isBought)
+ {
+ nameSpan.style.cursor = 'pointer';
+
+ nameSpan.addEventListener('click', () => {
+ row.replaceChild(nameInput, nameSpan);
+ nameInput.value = product.name;
+ nameInput.focus();
+ });
+
+ const saveEdit = () => {
+ const newName = nameInput.value.trim();
+ if (newName)
+ {
+ product.name = newName;
+ }
+ nameSpan.textContent = product.name;
+
+ if (nameInput.parentNode === row)
+ {
+ row.replaceChild(nameSpan, nameInput);
+ }
+ updateSummary();
+ };
+
+ nameInput.addEventListener('blur', saveEdit);
+ nameInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') saveEdit();
+ });
+ }
+
+ row.appendChild(nameSpan);
+
+ const controls = document.createElement('div');
+ controls.className = 'controls';
+
+ if (product.isBought)
+ {
+ const amountBadge = document.createElement('span');
+ amountBadge.className = 'amount-badge';
+ amountBadge.style.gridColumn = '2';
+ amountBadge.textContent = product.amount;
+
+ const btnStatus = document.createElement('button');
+ btnStatus.type = 'button';
+ btnStatus.className = 'btn-status';
+ btnStatus.style.gridColumn = '4';
+ btnStatus.setAttribute('data-tooltip', 'Повернути в список залишившихся');
+ btnStatus.textContent = 'Не куплено';
+
+ btnStatus.addEventListener('click', () => {
+ product.isBought = false;
+ renderList();
+ });
+
+ controls.appendChild(amountBadge);
+ controls.appendChild(btnStatus);
+ }
+ else
+ {
+ const btnMinus = document.createElement('button');
+ btnMinus.type = 'button';
+ btnMinus.className = `btn-minus ${product.amount === 1 ? 'disabled' : ''}`;
+ if (product.amount === 1)
+ {
+ btnMinus.disabled = true;
+ }
+ btnMinus.setAttribute('aria-label', 'Зменшити кількість');
+ btnMinus.setAttribute('data-tooltip', 'Мінімум 1');
+ btnMinus.textContent = '-';
+ btnMinus.addEventListener('click', () => {
+ if (product.amount > 1)
+ {
+ product.amount--;
+ renderList();
+ }
+ });
+
+ const amountBadge = document.createElement('span');
+ amountBadge.className = 'amount-badge';
+ amountBadge.textContent = product.amount;
+
+ const btnPlus = document.createElement('button');
+ btnPlus.type = 'button';
+ btnPlus.className = 'btn-plus';
+ btnPlus.setAttribute('aria-label', 'Збільшити кількість');
+ btnPlus.setAttribute('data-tooltip', 'Збільшити');
+ btnPlus.textContent = '+';
+ btnPlus.addEventListener('click', () => {
+ product.amount++;
+ renderList();
+ });
+
+ const btnStatus = document.createElement('button');
+ btnStatus.type = 'button';
+ btnStatus.className = 'btn-status active';
+ btnStatus.setAttribute('data-tooltip', 'Позначити як куплене');
+ btnStatus.textContent = 'Куплено';
+ btnStatus.addEventListener('click', () => {
+ product.isBought = true;
+ renderList();
+ });
+
+ const btnDelete = document.createElement('button');
+ btnDelete.type = 'button';
+ btnDelete.className = 'btn-delete';
+ btnDelete.setAttribute('aria-label', 'Видалити товар');
+ btnDelete.setAttribute('data-tooltip', 'Видалити зі списку');
+ btnDelete.textContent = '×';
+ btnDelete.addEventListener('click', () => {
+ products = products.filter(p => p.id !== product.id);
+ renderList();
+ });
+
+ controls.appendChild(btnMinus);
+ controls.appendChild(amountBadge);
+ controls.appendChild(btnPlus);
+ controls.appendChild(btnStatus);
+ controls.appendChild(btnDelete);
+ }
+
+ row.appendChild(controls);
+ return row;
+ }
+
+ function updateSummary() {
+ const leftContainer = summaryTagsContainers[0];
+ const rightContainer = summaryTagsContainers[1];
+
+ leftContainer.innerHTML = '';
+ rightContainer.innerHTML = '';
+
+ products.forEach(product => {
+ const tag = document.createElement('span');
+ tag.className = `product-tag ${product.isBought ? 'bought' : ''}`;
+
+ tag.appendChild(document.createTextNode(product.name + ' '));
+
+ const countSpan = document.createElement('span');
+ countSpan.className = 'tag-count';
+ countSpan.textContent = product.amount;
+
+ tag.appendChild(countSpan);
+
+ if (product.isBought)
+ {
+ rightContainer.appendChild(tag);
+ }
+ else
+ {
+ leftContainer.appendChild(tag);
+ }
+ });
+ }
+
+ function handleAddProduct()
+ {
+ const name = inputField.value.trim();
+ if (name)
+ {
+ products.push({
+ id: nextId++,
+ name: name,
+ amount: 1,
+ isBought: false
+ });
+ inputField.value = '';
+ inputField.focus();
+ renderList();
+ }
+ }
+
+ btnAdd.addEventListener('click', handleAddProduct);
+ inputField.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter')
+ {
+ handleAddProduct();
+ }
+ });
+
+ renderList();
+});
\ No newline at end of file