fuzzyworld

dao-game / commit

Merge branch 'undo-reset' into 'main'

019797d35440b4c1a1cd53e6b87dd6a5f19a7055 · Pavel S · 2022-03-01 09:07:25 +0000 · parents ac5892e93b46 e946b6c82035

Merge branch 'undo-reset' into 'main'

Undo reset

See merge request fuzzy-rp/dao-game!2
 game.js    | 569 +++++++++++++++++++++++++++++++++----------------------------
 index.html |  20 +--
 styles.css |  66 ++++---
 3 files changed, 364 insertions(+), 291 deletions(-)

diff --git a/game.js b/game.js
index 74909e5..19547fc 100644
--- a/game.js
+++ b/game.js
@@ -6,7 +6,9 @@ class Coord {
      * @memberof Coord
      */
     constructor(row, col) {
+        /** @type {number} */
         this.row = row;
+        /** @type {number} */
         this.col = col;
     }
 
@@ -36,235 +38,143 @@ class Coord {
         const areEqual = (this.col === otherCoord.col && this.row === otherCoord.row);
         return areEqual;
     }
+
+    // clock face directions
+    static get allDirections() {
+        const allDirections = [
+            { row: 1, col: 0 }, // up
+            { row: 1, col: 1 }, // up-right
+            { row: 0, col: 1 }, // right
+            { row: -1, col: 1 }, // right-down
+            { row: -1, col: 0 }, // down
+            { row: -1, col: -1 }, // down-left
+            { row: 0, col: -1 }, // left
+            { row: 1, col: -1 }, // left-up
+        ]
+        return allDirections;
+    }
+
 }
 
-// Constants
-const selectedPieceClassName = 'selected-piece';
-const selectedPlayerTextClassName = 'selected-player-text';
-const activePlayerPieceClassName = 'active-player-piece';
-const blinkingClassName = 'blinking';
-
-const standardBoard = [
-    0, null, null, 4,
-    null, 1, 5, null,
-    null, 6, 2, null,
-    7, null, null, 3
-];
-const testBoard = [
-    4, null, null, null,
-    null, 0, 1, 5,
-    null, 2, 3, null,
-    6, null, null, 7
-];
-
-const board = standardBoard;
-const rows = 4;
-const cols = 4;
-// clock face directions
-const allDirections = [
-    { row: 1, col: 0 }, // up
-    { row: 1, col: 1 }, // up-right
-    { row: 0, col: 1 }, // right
-    { row: -1, col: 1 }, // right-down
-    { row: -1, col: 0 }, // down
-    { row: -1, col: -1 }, // down-left
-    { row: 0, col: -1 }, // left
-    { row: 1, col: -1 }, // left-up
-]
-
-let isWhite = true;
-const table = document.getElementById('table');
-for (let r = 0; r < rows; r++) {
-    const tr = document.createElement('tr');
-    table.appendChild(tr);
-    for (let c = 0; c < cols; c++) {
-        const td = document.createElement('td');
-        tr.appendChild(td);
-        td.className = isWhite ? 'white' : 'black';
-        isWhite = !isWhite;
+const constants = {
+    selectedPieceClassName: 'selected-piece',
+    selectedPlayerTextClassName: 'selected-player-text',
+    activePlayerPieceClassName: 'active-player-piece',
+    blinkingClassName: 'blinking',
+    rows: 4,
+    cols: 4,
+}
+
+const gameBoard = {
+    board: [
+        0, null, null, 4,
+        null, 1, 5, null,
+        null, 6, 2, null,
+        7, null, null, 3
+    ],
+    // testBoard: [
+    //     4, null, null, null,
+    //     null, 0, 1, 5,
+    //     null, 2, 3, null,
+    //     6, null, null, 7
+    // ],
+    /**
+     * @param {Coord} data
+     * @return {number} 
+     */
+    coordToBoardIndex: ({ row, col }) => {
+        const index = row * 4 + col;
+        return index;
+    },
+    /**
+     * @param {number} index
+     * @return {Coord} 
+     */
+    boardIndexToCoord: (index) => {
+        const col = index % 4;
+        const row = Math.floor(index / 4);
+        return new Coord(row, col);
+    }
+}
+
+class Move {
+    /**
+     * Creates an instance of Move.
+     * @param {number} sourceIndex
+     * @param {number} destIndex
+     * @memberof Move
+     */
+    constructor(sourceIndex, destIndex) {
+        /** @type {number} */
+        this.sourceIndex = sourceIndex;
+        /** @type {number} */
+        this.destIndex = destIndex;
     }
-    isWhite = !isWhite;
 }
+const player = {
+    isRedTurn: false,
+    /** @type {number | undefined} */
+    selectedPieceId: undefined,
+    /** @type {Move[]} */
+    moveHistory: [],
+}
+
 // DOM references
 const defaultRedPiece = document.getElementById('default-red-piece');
 const defaultBluePiece = document.getElementById('default-blue-piece');
 const cells = () => Array.from(document.querySelectorAll("td"));
-
-board.forEach((value, index) => {
-    if (value !== null) {
-        const isRed = value < 4;
-        const defaultPiece = isRed ? defaultRedPiece : defaultBluePiece;
-        const newPiece = defaultPiece.cloneNode(true);
-        newPiece.id = value;
-        cells()[index].appendChild(newPiece);
-    }
-});
-
-const redPieces = () => Array.from(document.querySelectorAll(".red-piece"));
-const bluePieces = () => Array.from(document.querySelectorAll(".blue-piece"));
+const redPieces = () => Array.from(document.querySelectorAll('.red-piece:not(#default-red-piece'));
+const bluePieces = () => Array.from(document.querySelectorAll('.blue-piece:not(#default-blue-piece'));
 const redTurnText = () => Array.from(document.querySelectorAll(".red-turn-text"));
 const blueTurnText = () => Array.from(document.querySelectorAll(".blue-turn-text"));
-const divider = () => Array.from(document.querySelector("#divider"));
+const resetButton = document.getElementById('reset-button');
+const undoButton = document.getElementById('undo-button');
 
-// player properties
-let isRedTurn = false;
-let selectedPieceId = -1;
+const listenerManager = {
+    /** @type {{element: Element, type: string, listener: (event: Event) => void}[]} */
+    listeners: [],
 
-/** @type {{element: Element, type: string, listener: (event: Event) => void}[]} */
-const listeners = [];
-
-/**
- * @param {Element} element
- * @param {string} eventName
- * @param {Event => void} callback
- */
-const addListener = (element, type, listener) => {
-    element.addEventListener(type, listener);
-    listeners.push({ element, type, listener });
-}
-const removeListeners = () => {
-    listeners.forEach(value => {
-        const { element, type, listener } = value;
-        element.removeEventListener(type, listener);
-    });
-    listeners.length = 0;
+    /**
+     * @param {Element} element
+     * @param {string} eventName
+     * @param {Event => void} callback
+     */
+    addListener: (element, type, listener) => {
+        const { listeners } = listenerManager;
+        element.addEventListener(type, listener);
+        listeners.push({ element, type, listener });
+    },
+
+    removeListeners: () => {
+        const { listeners } = listenerManager;
+        listeners.forEach(value => {
+            const { element, type, listener } = value;
+            element.removeEventListener(type, listener);
+        });
+        listeners.length = 0;
+    }
 }
 
-/**
- * @param {Coord} data
- * @return {number} 
- */
-const coordToBoardIndex = ({ row, col }) => {
-    const index = row * 4 + col;
-    return index;
-};
-/**
- * @param {number} index
- * @return {Coord} 
- */
-const boardIndexToCoord = (index) => {
-    const col = index % 4;
-    const row = Math.floor(index / 4);
-    return new Coord(row, col);
-};
-
 const resetBoard = () => {
     // resetCells
     cells().forEach(cell => {
-        cell.classList.remove(blinkingClassName);
+        cell.classList.remove(constants.blinkingClassName);
     });
 
     // remove all listeners
-    removeListeners();
+    listenerManager.removeListeners();
 };
 
-const setActivePlayerPieces = () => {
-    // mark active pieces
-    const playerPieces = isRedTurn ? redPieces() : bluePieces()
-    playerPieces.forEach(pp => {
-        pp.classList.add(activePlayerPieceClassName);
-        addListener(pp, 'click', selectPiece);
-    });
-    const otherPieces = isRedTurn ? bluePieces() : redPieces();
-    otherPieces.forEach(op => {
-        if (op.classList.contains(activePlayerPieceClassName)) {
-            op.classList.toggle(activePlayerPieceClassName);
-        }
-    });
-};
-
-/**
- * @param {number} pieceId
- * @returns {number[]}
- */
-const getAllowedMoveIndicesForIndex = (boardIndex) => {
-    const coord = boardIndexToCoord(boardIndex);
-
-    const farthestCoords = allDirections
-        .map(direction => getFarthestCoord(coord, direction))
-        .filter(farCoord => farCoord !== undefined);
-
-    const farthestIndices = farthestCoords.map(coordToBoardIndex);
-    return farthestIndices;
-};
 /**
- *
- *
- * @param {Coord} startCoord
- * @param {Coord} direction
- * @returns {Coord}
+ * @param {number} sourceIndex
+ * @param {number} destIndex
  */
-const getFarthestCoord = (startCoord, direction) => {
-    let farthestPos = undefined;
-    let testPos = startCoord;
-    while (true) {
-        const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col);
-        const index = coordToBoardIndex(pos);
-        const withinBounds =
-            pos.row >= 0 && pos.row < rows &&
-            pos.col >= 0 && pos.col < cols;
-        const isOccupied = Number.isInteger(board[index]);
-        if (isOccupied || !withinBounds) {
-            break;
-        } else {
-            farthestPos = pos;
-            testPos = pos;
-        }
-    }
-    return farthestPos;
-};
-
-/**
- *
- *
- * @param {number} pieceId
- * @return {Element} 
- */
-const getPieceElementById = (pieceId) => {
-    return document.getElementById(pieceId);
-}
-
-/** @param {Event} event */
-const selectPiece = (event) => {
-    const previousSelectedPieceId = selectedPieceId;
-    if (previousSelectedPieceId >= 0) {
-        const previousPiece = getPieceElementById(previousSelectedPieceId);
-        if (previousPiece.classList.contains(selectedPieceClassName)) {
-            previousPiece.classList.toggle(selectedPieceClassName);
-        }
-        cells().forEach(cell => {
-            cell.classList.remove(blinkingClassName);
-        });
-    }
-
-    // read event data
-    /** @type {Element} */
-    const piece = event.target;
-    const pieceId = parseInt(piece.id);
-
-    // set as selected
-    selectedPieceId = pieceId;
-    piece.classList.add(selectedPieceClassName);
-
-    // hook up allowed moves for:
-    // 1. blink
-    // 2. complete the move
-    const pieceIndex = board.indexOf(pieceId);
-    const allowedMoveIndices = getAllowedMoveIndicesForIndex(pieceIndex);
-    allowedMoveIndices.forEach(i => {
-        const cell = cells()[i];
-        addListener(cell, 'click', async () => await makeMove(i));
-        cell.classList.add(blinkingClassName);
-    });
-};
-
-const makeMove = async (destIndex) => {
-    removeListeners();
-
+const movePiece = async (sourceIndex, destIndex) => {
+    const pieceId = gameBoard.board[sourceIndex];
     // find current piece
-    const piece = getPieceElementById(selectedPieceId);
+    const piece = document.getElementById(pieceId);
     // unselect piece
-    piece.classList.remove(selectedPieceClassName);
+    piece.classList.remove(constants.selectedPieceClassName);
 
     // create clone for new position
     const destPiece = piece.cloneNode(true);
@@ -288,78 +198,178 @@ const makeMove = async (destIndex) => {
 
     // remove old piece
     piece.parentNode.removeChild(piece);
-    board[board.indexOf(selectedPieceId)] = null;
+    gameBoard.board[sourceIndex] = null;
     // set new piece
-    board[destIndex] = selectedPieceId;
-
-    checkForWin();
-    nextMove();
-}
+    gameBoard.board[destIndex] = pieceId;
+};
 
 const nextMove = () => {
+    const setActivePlayerPieces = () => {
+        /** @param {Event} event */
+        const selectPiece = (event) => {
+            /**
+             * @param {number} pieceId
+             * @returns {number[]}
+             */
+            const getAllowedMoveIndicesForIndex = (boardIndex) => {
+                /**
+                 *
+                 *
+                 * @param {Coord} startCoord
+                 * @param {Coord} direction
+                 * @returns {Coord}
+                 */
+                const getFarthestCoord = (startCoord, direction) => {
+                    let farthestPos = undefined;
+                    let testPos = startCoord;
+                    while (true) {
+                        const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col);
+                        const index = gameBoard.coordToBoardIndex(pos);
+                        const withinBounds =
+                            pos.row >= 0 && pos.row < constants.rows &&
+                            pos.col >= 0 && pos.col < constants.cols;
+                        const isOccupied = Number.isInteger(gameBoard.board[index]);
+                        if (isOccupied || !withinBounds) {
+                            break;
+                        } else {
+                            farthestPos = pos;
+                            testPos = pos;
+                        }
+                    }
+                    return farthestPos;
+                };
+
+                const coord = gameBoard.boardIndexToCoord(boardIndex);
+
+                const farthestCoords = Coord.allDirections
+                    .map(direction => getFarthestCoord(coord, direction))
+                    .filter(farCoord => farCoord !== undefined);
+
+                const farthestIndices = farthestCoords.map(c => gameBoard.coordToBoardIndex(c));
+                return farthestIndices;
+            };
+
+            const previousSelectedPieceId = player.selectedPieceId;
+            if (previousSelectedPieceId !== undefined) {
+                const previousPiece = document.getElementById(previousSelectedPieceId);
+                if (previousPiece.classList.contains(constants.selectedPieceClassName)) {
+                    previousPiece.classList.toggle(constants.selectedPieceClassName);
+                }
+                cells().forEach(cell => {
+                    cell.classList.remove(constants.blinkingClassName);
+                });
+            }
+
+            // read event data
+            /** @type {Element} */
+            const piece = event.target;
+            const pieceId = parseInt(piece.id);
+
+            // set as selected
+            player.selectedPieceId = pieceId;
+            piece.classList.add(constants.selectedPieceClassName);
+
+            // hook up allowed moves for:
+            // 1. blink
+            // 2. complete the move
+            const pieceIndex = gameBoard.board.indexOf(pieceId);
+            const allowedMoveIndices = getAllowedMoveIndicesForIndex(pieceIndex);
+            allowedMoveIndices.forEach(i => {
+                const cell = cells()[i];
+                listenerManager.addListener(cell, 'click', async () => await makeMove(i));
+                cell.classList.add(constants.blinkingClassName);
+            });
+        };
+
+        const makeMove = async (destIndex) => {
+            listenerManager.removeListeners();
+
+            const sourceIndex = gameBoard.board.indexOf(player.selectedPieceId);
+            player.moveHistory.push(new Move(sourceIndex, destIndex));
+
+            await movePiece(sourceIndex, destIndex);
+
+            checkForWin();
+            nextMove();
+        }
+
+        // mark active pieces
+        const playerPieces = player.isRedTurn ? redPieces() : bluePieces()
+        playerPieces.forEach(pp => {
+            pp.classList.add(constants.activePlayerPieceClassName);
+            listenerManager.addListener(pp, 'click', selectPiece);
+        });
+        const otherPieces = player.isRedTurn ? bluePieces() : redPieces();
+        otherPieces.forEach(op => {
+            if (op.classList.contains(constants.activePlayerPieceClassName)) {
+                op.classList.toggle(constants.activePlayerPieceClassName);
+            }
+        });
+    };
+
     // reset text
-    redTurnText().forEach(t => t.classList.remove(selectedPlayerTextClassName));
-    blueTurnText().forEach(t => t.classList.remove(selectedPlayerTextClassName));
+    redTurnText().forEach(t => t.classList.remove(constants.selectedPlayerTextClassName));
+    blueTurnText().forEach(t => t.classList.remove(constants.selectedPlayerTextClassName));
 
     // changePlayer
     // 1. flip flag
     // 2. set text
     // 3. set click callbacks
-    if (isRedTurn) {
-        isRedTurn = false;
-        blueTurnText().forEach(t => t.classList.add(selectedPlayerTextClassName));
+    if (player.isRedTurn) {
+        player.isRedTurn = false;
+        blueTurnText().forEach(t => t.classList.add(constants.selectedPlayerTextClassName));
     }
     else {
-        isRedTurn = true;
-        redTurnText().forEach(t => t.classList.add(selectedPlayerTextClassName));
+        player.isRedTurn = true;
+        redTurnText().forEach(t => t.classList.add(constants.selectedPlayerTextClassName));
     }
 
-    selectedPieceId = -1;
+    player.selectedPieceId = undefined;
     resetBoard();
     setActivePlayerPieces();
 };
 
-/**
- * @param {Element[]} elements
- * @returns {number[]}
- */
-const elementsToBoardIndexes = (elements) => {
-    const indices = elements.map(pp => {
-        const pieceId = Number.parseInt(pp.id);
-        const pieceIndex = board.indexOf(pieceId);
-        return pieceIndex;
-    });
-    return indices;
-};
-/**
- * @param {Element[]} elements
- * @return {Coord[]} 
- */
-const elementsToCoords = (elements) => {
-    const indices = elementsToBoardIndexes(elements);
-    const coords = indices.map(pieceIndex => boardIndexToCoord(pieceIndex));
-    return coords;
-};
-
-/**
- * @param {Coord[]} coords
- * @returns {boolean}
- */
-const doCoordsFormSquare = (coords) => {
-    const allRows = coords.map(c => c.row);
-    const allCols = coords.map(c => c.col);
-    const min = new Coord(Math.min(...allRows), Math.min(...allCols));
-    const max = new Coord(Math.max(...allRows), Math.max(...allCols));
-    const diff = max.sub(min);
-    const isSquare = (diff.row === 1 && diff.col === 1);
-    return isSquare;
-};
-
 const checkForWin = () => {
+    /**
+     * @param {Element[]} elements
+     * @return {Coord[]} 
+     */
+    const elementsToCoords = (elements) => {
+        /**
+         * @param {Element[]} elements
+         * @returns {number[]}
+         */
+        const elementsToBoardIndexes = (elements) => {
+            const indices = elements.map(pp => {
+                const pieceId = Number.parseInt(pp.id);
+                const pieceIndex = gameBoard.board.indexOf(pieceId);
+                return pieceIndex;
+            });
+            return indices;
+        };
+
+        const indices = elementsToBoardIndexes(elements);
+        const coords = indices.map(pieceIndex => gameBoard.boardIndexToCoord(pieceIndex));
+        return coords;
+    };
+    /**
+     * @param {Coord[]} coords
+     * @returns {boolean}
+     */
+    const doCoordsFormSquare = (coords) => {
+        const allRows = coords.map(c => c.row);
+        const allCols = coords.map(c => c.col);
+        const min = new Coord(Math.min(...allRows), Math.min(...allCols));
+        const max = new Coord(Math.max(...allRows), Math.max(...allCols));
+        const diff = max.sub(min);
+        const isSquare = (diff.row === 1 && diff.col === 1);
+        return isSquare;
+    };
+
     const playerCoords = elementsToCoords(
-        isRedTurn ? redPieces() : bluePieces())
+        player.isRedTurn ? redPieces() : bluePieces())
     const otherCoords = elementsToCoords(
-        isRedTurn ? bluePieces() : redPieces());
+        player.isRedTurn ? bluePieces() : redPieces());
     /** @type {Coord[]} */
     const squareCoords = [
         new Coord(0, 0),
@@ -382,7 +392,7 @@ const checkForWin = () => {
     const allInCorners = playerCoords.every(pc => squareCoords.findIndex(sc => pc.equals(sc)) >= 0);
     const allInSquare = doCoordsFormSquare(playerCoords);
 
-    const winnerName = isRedTurn ? 'Red' : 'Blue';
+    const winnerName = player.isRedTurn ? 'Red' : 'Blue';
     let winningMessage = undefined;
     if (allInRow) {
         winningMessage = `${winnerName} wins by forming a row`;
@@ -403,7 +413,52 @@ const checkForWin = () => {
     }
 };
 
+const init = () => {
+    // create board and pieces
+    let isWhite = true;
+    const table = document.getElementById('table');
+    for (let r = 0; r < constants.rows; r++) {
+        const tr = document.createElement('tr');
+        table.appendChild(tr);
+        for (let c = 0; c < constants.cols; c++) {
+            const td = document.createElement('td');
+            tr.appendChild(td);
+            td.className = isWhite ? 'white' : 'black';
+            isWhite = !isWhite;
+        }
+        isWhite = !isWhite;
+    }
+    gameBoard.board.forEach((value, index) => {
+        if (value !== null) {
+            const isRed = value < 4;
+            const defaultPiece = isRed ? defaultRedPiece : defaultBluePiece;
+            const newPiece = defaultPiece.cloneNode(true);
+            newPiece.id = value;
+            cells()[index].appendChild(newPiece);
+        }
+    });
+
+    // hook up buttons
+    resetButton.addEventListener('click', () => {
+        if (player.moveHistory.length === 0) {
+            return;
+        }
+
+        if (confirm('Reset?')) {
+            window.location.reload();
+        }
+    });
+    undoButton.addEventListener('click', () => {
+        if (player.moveHistory.length === 0) {
+            return;
+        }
+
+        const { destIndex, sourceIndex } = player.moveHistory.pop();
+        movePiece(destIndex, sourceIndex);
+        nextMove();
+    });
+};
 
 // start!
-// checkForWin();
+init();
 nextMove();
\ No newline at end of file
diff --git a/index.html b/index.html
index 64b6d31..db50b20 100644
--- a/index.html
+++ b/index.html
@@ -16,26 +16,26 @@
             <p class='piece blue-piece' id='default-blue-piece'></p>
         </div>
         <main>
-            <div class="red-turn-text mobile">
-                Red's turn
-            </div>
-
             <table id="table">
             </table>
 
-            <div class="desktop">
+            <div class="controls">
                 <span class="red-turn-text">
                     Red's turn
                 </span>
-                <span id="divider">|</span>
+                <span class="divider">|</span>
+                <button type="button" class="button" id="undo-button">
+                    Undo
+                </button>
+                <span class="divider">|</span>
+                <button type="button" class="button" id="reset-button">
+                    Reset
+                </button>
+                <span class="divider">|</span>
                 <span class="blue-turn-text">
                     Blue's turn
                 </span>
             </div>
-
-            <div class="blue-turn-text mobile">
-                Blue's turn
-            </div>
         </main>
         <hr />
         <div class="title">
diff --git a/styles.css b/styles.css
index 9fcb6de..1cd85a7 100644
--- a/styles.css
+++ b/styles.css
@@ -1,6 +1,7 @@
 :root {
     --default-size: 5px;
     --active-size: 40px;
+    --cell-size: 80px;
     /* --p1-color: crimson;
     --p2-color: mediumblue; */
 }
@@ -40,39 +41,54 @@ div {
     font-size: 40px;
 }
 
-.mobile {
-    display: none;
-}
-
-.desktop {
-    margin: 0;
-    width: 100%;
-}
-
 td {
-    width: 65px;
-    height: 65px;
+    width: var(--cell-size);
+    height: var(--cell-size);
     text-align: center;
 }
 
-#divider {
+.controls {
     font-size: 90px;
     margin-left: 15px;
     margin-right: 15px;
-}
-
-.red-turn-text {
-    text-align: center;
-    color: lightgray;
     font-family: "open sans";
     font-size: 55px;
+    border-radius: 10px;
+    padding: 10px;
+}
+
+.divider {
+    font-size: 90px;
+    margin-left: 15px;
+    margin-right: 15px;
 }
 
-.blue-turn-text {
+.red-turn-text, .blue-turn-text {
     text-align: center;
-    font-family: "open sans";
-    font-size: 55px;
     color: lightgray;
+    background-color: white;
+    /* border: 1px solid black; */
+    border-radius: 10px;
+    padding: 10px;
+}
+
+.red-turn-text.selected-player-text {
+    background-color: red;
+}
+
+.blue-turn-text.selected-player-text {
+    background-color: blue;
+}
+
+.selected-player-text {
+    color: white;
+}
+
+.button {
+    border-radius: 10px;
+    padding: 10px;
+    background-color: darkseagreen;
+    font-size: 60px;
 }
 
 .white {
@@ -110,10 +126,6 @@ td {
     animation: activePulsing 1s infinite;
 }
 
-.selected-player-text {
-    color: black;
-}
-
 .info {
     text-align: center;
     font-family: "open sans";
@@ -129,6 +141,12 @@ td {
     100%{opacity: 1;}
 }
 
+@keyframes fade-in {
+    0%{opacity: 0;}
+    50%{opacity: .5;}
+    100%{opacity: 1;}    
+}
+
 @keyframes disabledToActive {
     from {
         width: var(--default-size);