<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Connect & Fill Grid Game</title>
    <link rel="stylesheet" href="style.css">
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✏️</text></svg>">
</head>
<body>
    <h1>Connect & Fill</h1>

    <div class="game-info">
        <p id="message">Click and drag from dot 1 to connect all dots in order, filling every square!</p>
        <p>Next Dot: <span id="next-dot">1</span> | Cells Visited: <span id="cells-visited">0</span> / <span id="total-cells">?</span></p>
        <div class="button-group">
            <button id="reset-button" title="Reset the grid">Reset Grid</button>
            <button id="hint-button" title="Show possible next moves">Show Hint</button>
        </div>
    </div>

    <div id="grid-container">
        <!-- Grid cells will be generated by JavaScript -->
    </div>

    <script src="script.js"></script>
</body>
</html>
/* --- Global Styles --- */
:root {
    --cell-size: 50px; /* Default, will be set by JS */
    --grid-bg: #1e2127;
    --cell-border: #444;
    --dot-bg: #111;
    --dot-text: #fff;
    --dot-border: #fff;
    --hint-color: #4a90e2; /* Blue for hint */
    --win-color: #4caf50; /* Green */
    --error-color: #f44336; /* Red */
    --info-color: #eee;
    --body-bg: #282c34;
    --text-light: #fff;
    --text-dark: #333;
    --button-reset-bg: #f05a5a;
    --button-reset-hover: #d94848;
    --button-hint-bg: var(--hint-color);
    --button-hint-hover: #3a7bc8;
    --info-box-bg: #3a3f47;
    --title-color: #61dafb;

    /* --- Path Segment Colors (Adjust as desired) --- */
    --path-color-1: #d81b60;
    --path-color-2: #e91e63;
    --path-color-3: #ec407a;
    --path-color-4: #f06292;
    --path-color-5: #f48fb1;
    --path-color-6: #f8bbd0;
    --path-color-7: #fce4ec;
    --path-color-8: #fff5f7; /* Lighter end */
    /* --- Start/Special Path Colors --- */
    --path-start-color: #ad1457;
    --path-dot-reached-opacity: 0.95;
    --path-head-opacity: 1;
    --path-default-opacity: 0.75;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    background-color: var(--body-bg);
    color: var(--text-light);
    margin: 0;
    padding: 20px 15px;
    min-height: 100vh;
    box-sizing: border-box;
}

h1 {
    color: var(--title-color);
    margin-bottom: 15px;
    text-align: center;
}

.game-info {
    margin-bottom: 20px;
    text-align: center;
    background-color: var(--info-box-bg);
    padding: 15px 20px;
    border-radius: 8px;
    max-width: 600px;
    width: 90%;
    box-sizing: border-box;
}

#message {
    min-height: 2.5em; /* Space for messages */
    color: var(--info-color);
    margin-bottom: 10px;
}
#message.win { color: var(--win-color); font-weight: bold;}
#message.error { color: var(--error-color); font-weight: bold;}

.game-info p:last-of-type { /* Status line */
    margin-bottom: 0;
}

.game-info span { /* Info spans */
    font-weight: bold;
}

.button-group {
    margin-top: 15px;
    display: flex;
    justify-content: center;
    flex-wrap: wrap; /* Allow wrapping on small screens */
    gap: 15px;
}

#reset-button, #hint-button {
    padding: 10px 20px;
    font-size: 1em;
    cursor: pointer;
    color: white;
    border: none;
    border-radius: 5px;
    transition: background-color 0.2s, opacity 0.2s;
}

#reset-button { background-color: var(--button-reset-bg); }
#reset-button:hover { background-color: var(--button-reset-hover); }

#hint-button { background-color: var(--button-hint-bg); }
#hint-button:hover { background-color: var(--button-hint-hover); }
#hint-button:disabled { opacity: 0.5; cursor: not-allowed; }


/* --- Grid & Cell Styling --- */
#grid-container {
    /* Grid size (width, height, columns, rows) set by JS */
    display: grid;
    border: 3px solid var(--title-color);
    background-color: var(--grid-bg);
    user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    cursor: pointer; /* Hand cursor */
    touch-action: none; /* Prevent scrolling on touch devices */
    box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}

.cell {
    border: 1px solid var(--cell-border);
    box-sizing: border-box;
    position: relative; /* Crucial for pseudo-element & dot */
    display: flex; /* Helps center dot if needed, although dot is relative */
    justify-content: center;
    align-items: center;
    background-color: transparent; /* Cell itself is clear */
    transition: box-shadow 0.2s ease; /* Hint transition */
}

/* --- Path Styling (using ::before) --- */
.cell.path::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 1; /* Below dot */
    opacity: var(--path-default-opacity);
    transition: background-color 0.1s ease-out, opacity 0.1s ease-out;
    /* Default background, will be overridden by segments */
    background-color: gray; /* Fallback */
}

/* Path segment colors applied to ::before */
.cell.path-segment-1::before { background-color: var(--path-color-1); }
.cell.path-segment-2::before { background-color: var(--path-color-2); }
.cell.path-segment-3::before { background-color: var(--path-color-3); }
.cell.path-segment-4::before { background-color: var(--path-color-4); }
.cell.path-segment-5::before { background-color: var(--path-color-5); }
.cell.path-segment-6::before { background-color: var(--path-color-6); }
.cell.path-segment-7::before { background-color: var(--path-color-7); }
.cell.path-segment-8::before { background-color: var(--path-color-8); }
/* Add more .path-segment-N::before rules if MAX_DOT > 8 */

/* State overrides for ::before */
.cell.path-start::before {
    background-color: var(--path-start-color) !important; /* Override segments */
    opacity: var(--path-head-opacity); /* Start is also head initially */
}
.cell.path-dot-reached::before {
    opacity: var(--path-dot-reached-opacity);
}
.cell.path-head::before {
    opacity: var(--path-head-opacity);
}

/* --- Dot Styling --- */
.dot {
    width: 70%;
    height: 70%;
    background-color: var(--dot-bg);
    border-radius: 50%;
    color: var(--dot-text);
    font-weight: bold;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: calc(var(--cell-size) * 0.4);
    position: relative; /* Keeps it in flow, respects z-index */
    z-index: 2; /* Above path pseudo-element */
    border: 2px solid var(--dot-border);
    text-shadow: 0 0 3px rgba(0,0,0,0.7);
    box-shadow: 0 0 5px rgba(0,0,0,0.5);
    opacity: 1 !important; /* Ensure full visibility */
}

/* --- Hint Styling --- */
.hint-cell {
    box-shadow: inset 0 0 10px 3px var(--hint-color);
    transition: box-shadow 0.3s ease-out;
    z-index: 3; /* Above path, below dot maybe? Check visually */
}
document.addEventListener('DOMContentLoaded', () => {
    // --- DOM Elements ---
    const gridContainer = document.getElementById('grid-container');
    const messageElement = document.getElementById('message');
    const nextDotElement = document.getElementById('next-dot');
    const cellsVisitedElement = document.getElementById('cells-visited');
    const totalCellsElement = document.getElementById('total-cells');
    const resetButton = document.getElementById('reset-button');
    const hintButton = document.getElementById('hint-button');

    // --- Game Configuration ---
    const GRID_SIZE = 6; // Grid dimensions
    const CELL_SIZE_PX = 60; // Visual size of cells
    // Dot positions [row, col], 0-indexed for 6x6 grid
    const DOT_POSITIONS = {
        1: [2, 2], 2: [2, 4], 3: [5, 1], 4: [4, 5],
        5: [0, 5], 6: [0, 1], 7: [2, 1], 8: [2, 3]
    };
    const MAX_DOT = Object.keys(DOT_POSITIONS).length; // Number of dots
    const TOTAL_CELLS = GRID_SIZE * GRID_SIZE; // Total cells (36)
    const HINT_DURATION = 1500; // How long hints stay visible (ms)

    // --- Game State Variables ---
    let gridData = []; // 2D array: { visited: bool, dot: num|null, element: node }
    let currentPath = []; // Array of [row, col] representing the path
    let isDrawing = false; // Is the user currently dragging/drawing?
    let currentPos = { row: -1, col: -1 }; // Position of the path's head
    let nextExpectedDot = 1; // Which numbered dot is next?
    let hintActive = false; // Is a hint currently displayed?
    let hintTimeoutId = null; // ID for the hint clearing timeout

    // --- Initialization ---
    function setupGrid() {
        // 1. Reset State
        gridContainer.innerHTML = ''; // Clear visual grid
        gridData = [];
        currentPath = [];
        isDrawing = false;
        currentPos = { row: -1, col: -1 };
        nextExpectedDot = 1;
        hintActive = false;
        clearTimeout(hintTimeoutId); // Clear any pending hint removal
        hintButton.disabled = false; // Re-enable hint button

        // 2. Apply Grid Styles (using CSS variables)
        gridContainer.style.gridTemplateColumns = `repeat(${GRID_SIZE}, ${CELL_SIZE_PX}px)`;
        gridContainer.style.gridTemplateRows = `repeat(${GRID_SIZE}, ${CELL_SIZE_PX}px)`;
        gridContainer.style.width = `${GRID_SIZE * CELL_SIZE_PX}px`;
        gridContainer.style.height = `${GRID_SIZE * CELL_SIZE_PX}px`;
        document.documentElement.style.setProperty('--cell-size', `${CELL_SIZE_PX}px`);

        // 3. Create Cells and Dots
        for (let r = 0; r < GRID_SIZE; r++) {
            gridData[r] = [];
            for (let c = 0; c < GRID_SIZE; c++) {
                // a. Create Cell Data Structure
                gridData[r][c] = {
                    visited: false,
                    dot: null,
                    element: null // Store reference to the DOM element
                };

                // b. Create Cell DOM Element
                const cell = document.createElement('div');
                cell.classList.add('cell');
                cell.dataset.row = r; // Store row/col for easy access in events
                cell.dataset.col = c;
                gridData[r][c].element = cell; // Link data structure to DOM element

                // c. Check for and Add Dot
                for (const dotNumStr in DOT_POSITIONS) {
                    const dotNum = parseInt(dotNumStr);
                    if (DOT_POSITIONS[dotNum][0] === r && DOT_POSITIONS[dotNum][1] === c) {
                        gridData[r][c].dot = dotNum; // Store dot number in data

                        // Create Dot DOM Element
                        const dotElement = document.createElement('div');
                        dotElement.classList.add('dot');
                        dotElement.textContent = dotNumStr; // Display number
                        cell.appendChild(dotElement); // Add dot visually inside cell
                        break; // Only one dot per cell
                    }
                }
                // d. Add Cell to Grid Container
                gridContainer.appendChild(cell);
            }
        }

        // 4. Update Initial UI Info
        updateInfo();
        setMessage("Click/Tap and drag from dot 1...");
    }

    // --- UI Update Functions ---
    function updateInfo() {
        nextDotElement.textContent = nextExpectedDot > MAX_DOT ? 'Done!' : nextExpectedDot;
        cellsVisitedElement.textContent = currentPath.length;
        totalCellsElement.textContent = TOTAL_CELLS; // Display total cells (36)
    }

    function setMessage(msg, type = 'info') { // type can be 'info', 'win', 'error'
        messageElement.textContent = msg;
        messageElement.className = type; // Applies .win or .error CSS class
    }

    // --- Path & Cell Styling Helper ---
    // Applies/removes necessary CSS classes based on cell's path state
    function updateCellPathStyle(row, col, isPath, segmentIndex = 0, isStart = false, isDotReached = false, isHead = false) {
        const cell = gridData[row]?.[col]?.element;
        if (!cell) return; // Safety check

        // Toggle base '.path' class (required for ::before selector)
        cell.classList.toggle('path', isPath);

        // Toggle specific state classes
        cell.classList.toggle('path-start', isPath && isStart);
        cell.classList.toggle('path-dot-reached', isPath && isDotReached);
        cell.classList.toggle('path-head', isPath && isHead);

        // Manage path segment color classes
        const maxSegments = MAX_DOT; // Number of color segments needed
        for (let i = 1; i <= maxSegments; i++) {
            // Remove segment class if it's not the correct one for this state, OR if the cell is no longer part of the path
            if ((i !== segmentIndex && cell.classList.contains(`path-segment-${i}`)) || !isPath) {
                 cell.classList.remove(`path-segment-${i}`);
            }
        }
         // Add the correct segment class if it's part of the path, but NOT the start cell (start has its own override)
        if (isPath && segmentIndex > 0 && !isStart) {
            cell.classList.add(`path-segment-${segmentIndex}`);
        }

        // Cleanup: If no longer part of the path, ensure all path-related classes are gone
        if (!isPath) {
            cell.classList.remove('path-start', 'path-dot-reached', 'path-head');
             // Loop again for segment classes just to be absolutely sure on removal
             for (let i = 1; i <= maxSegments; i++) {
                  cell.classList.remove(`path-segment-${i}`);
             }
        }
    }

    // --- Event Handlers (Combined Mouse & Touch) ---

    // Function to get row/col from either mouse or touch event
    function getCoordsFromEvent(event) {
        let clientX, clientY;
        if (event.type.startsWith('touch')) {
            if (!event.touches[0]) return null; // No touch data
            clientX = event.touches[0].clientX;
            clientY = event.touches[0].clientY;
        } else { // Mouse event
            clientX = event.clientX;
            clientY = event.clientY;
        }

        const gridRect = gridContainer.getBoundingClientRect();
        const x = clientX - gridRect.left;
        const y = clientY - gridRect.top;
        const col = Math.floor(x / CELL_SIZE_PX);
        const row = Math.floor(y / CELL_SIZE_PX);

        // Basic bounds check before returning
        if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) {
            return null;
        }
        return { row, col };
    }


    function handleInteractionStart(event) {
        event.preventDefault(); // Prevent scrolling/default drag
        clearHint(); // Clear hint on any interaction

        const coords = getCoordsFromEvent(event);
        if (!coords) return; // Click/touch was outside grid or invalid
        const { row, col } = coords;

        const cellData = gridData[row][col];

        // Condition 1: Start drawing on dot 1 if path is empty
        if (cellData.dot === 1 && currentPath.length === 0) {
            isDrawing = true;
            currentPos = { row, col };
            currentPath = [[row, col]]; // Start path array
            cellData.visited = true;
            // Style: isPath=true, segment=1, isStart=true, isDotReached=false, isHead=true
            updateCellPathStyle(row, col, true, 1, true, false, true);
            nextExpectedDot = 2;
            updateInfo();
            setMessage("Drawing path... Go to dot 2!");
        }
        // Condition 2: Resume drawing if clicking/tapping the current head
        else if (currentPath.length > 0 && row === currentPos.row && col === currentPos.col) {
             isDrawing = true;
             setMessage(`Continue path... Go to dot ${nextExpectedDot}.`);
             // Ensure head styling is correct (find current segment)
             const segment = nextExpectedDot > 1 ? nextExpectedDot - 1 : 1;
             const headDotValue = cellData.dot;
             // Style: isPath=true, segment, isStart=(headDotValue===1), isDotReached=(headDotValue===nextExpectedDot-1), isHead=true
             updateCellPathStyle(row, col, true, segment, headDotValue === 1, headDotValue === (nextExpectedDot - 1), true);
        }
        // Condition 3: Invalid start/resume attempt
        else if (currentPath.length > 0) {
             setMessage("Click/Tap on the end of the current path to continue or backtrack.", "error");
        } else if (cellData.dot !== 1) {
            setMessage("You must start on dot 1!", "error");
        }
    }

    function handleInteractionMove(event) {
        if (!isDrawing) return; // Only process if actively drawing
        event.preventDefault();
        clearHint();

        const coords = getCoordsFromEvent(event);
        if (!coords) return; // Moved outside grid bounds
        const { row: targetRow, col: targetCol } = coords;

        // Ignore if moved onto the same cell
        if (targetRow === currentPos.row && targetCol === currentPos.col) {
            return;
        }

        // --- Check for BACKTRACKING ---
        if (currentPath.length >= 2) {
            const prevPosArr = currentPath[currentPath.length - 2]; // The cell before the current head
            // Is the target cell the one we just came from?
            if (targetRow === prevPosArr[0] && targetCol === prevPosArr[1]) {
                const lastPosArr = currentPath.pop(); // Remove current head from path
                const lastRow = lastPosArr[0];
                const lastCol = lastPosArr[1];

                gridData[lastRow][lastCol].visited = false; // Mark as unvisited
                updateCellPathStyle(lastRow, lastCol, false); // Remove all path styling

                // Check if we backtracked over a required dot (decrement expected dot)
                const removedDotValue = gridData[lastRow][lastCol].dot;
                let segmentForNewHead = 1;
                if (removedDotValue !== null && removedDotValue === (nextExpectedDot - 1) && removedDotValue !== 1 ) {
                     nextExpectedDot--; // Decrement if we un-reached a target dot (excluding dot 1)
                     segmentForNewHead = (nextExpectedDot > 1) ? nextExpectedDot - 1 : 1; // Segment before the un-reached dot
                 } else if (currentPath.length > 0){
                     // If not over a target dot, the segment is the one leading to the current expected dot
                     segmentForNewHead = (nextExpectedDot > 1) ? nextExpectedDot - 1 : 1;
                 }

                // Update currentPos to the new head (which is prevPosArr)
                currentPos = { row: prevPosArr[0], col: prevPosArr[1] };
                const headDotValue = gridData[currentPos.row][currentPos.col].dot;
                // Restyle the new head correctly
                 // Style: isPath=true, segment=segmentForNewHead, isStart=(headDotValue===1), isDotReached=(headDotValue===nextExpectedDot-1), isHead=true
                updateCellPathStyle(currentPos.row, currentPos.col, true, segmentForNewHead, headDotValue === 1, headDotValue === (nextExpectedDot - 1), true);

                updateInfo();
                setMessage(`Backtracked. Continue path... Go to dot ${nextExpectedDot}.`);
                return; // Exit after handling backtrack
            }
        }

        // --- Check for FORWARD MOVEMENT ---
        if (isValidForwardMove(targetRow, targetCol)) {
             const currentSegmentIndex = (nextExpectedDot > 1) ? nextExpectedDot - 1 : 1;
             const prevHeadDotValue = gridData[currentPos.row][currentPos.col].dot;
             // Remove 'head' style from the previously current cell
             // Style: isPath=true, segment=currentSegmentIndex, isStart=(prevHeadDotValue===1), isDotReached=(prevHeadDotValue<nextExpectedDot), isHead=false
             updateCellPathStyle(currentPos.row, currentPos.col, true, currentSegmentIndex, prevHeadDotValue === 1, prevHeadDotValue !== null && prevHeadDotValue < nextExpectedDot, false);

            // Update state for the new cell
            currentPos = { row: targetRow, col: targetCol }; // Move head position
            currentPath.push([targetRow, targetCol]); // Add to path array
            gridData[targetRow][targetCol].visited = true; // Mark as visited

            const targetDotValue = gridData[targetRow][targetCol].dot;
            let isTargetDotReached = false;
            let message = "";
            let nextSegmentIndex = currentSegmentIndex; // Assume same segment unless a dot is hit

            // Check if the new cell contains a dot
            if (targetDotValue === nextExpectedDot) { // Correct dot hit!
                isTargetDotReached = true;
                 if (nextExpectedDot === MAX_DOT) { // Final dot reached
                    nextExpectedDot++; // Mark as all dots done
                    message = `Connected final dot ${MAX_DOT}! Release touch/mouse.`;
                    nextSegmentIndex = MAX_DOT; // Use final segment color
                 } else { // Intermediate dot reached
                    nextExpectedDot++; // Increment expected dot
                    message = `Connected dot ${nextExpectedDot - 1}! Go to dot ${nextExpectedDot}.`;
                    nextSegmentIndex = nextExpectedDot - 1; // Segment index for the *next* part of path
                 }
            } else if (targetDotValue !== null && targetDotValue !== 1) { // Hit a dot out of order (ignore dot 1)
                isDrawing = false; // Stop drawing on error
                setMessage(`Wrong dot! Expected ${nextExpectedDot} but landed on ${targetDotValue}. Reset.`, "error");
                 // Style the error cell as part of the current path segment, but not head/dot-reached
                updateCellPathStyle(targetRow, targetCol, true, currentSegmentIndex, false, false, false);
                checkWinCondition(); // Force check to potentially show final error message
                return; // Stop processing move
            } else { // Moved to an empty cell or back over dot 1 (allowed but has no effect)
                 message = `Continue path... Go to dot ${nextExpectedDot}.`;
                 nextSegmentIndex = currentSegmentIndex; // Stay on current segment color
            }

            // Style the new head cell
            // Style: isPath=true, segment=nextSegmentIndex, isStart=false, isDotReached, isHead=true
            updateCellPathStyle(targetRow, targetCol, true, nextSegmentIndex, false, isTargetDotReached, true);
            setMessage(message);
            updateInfo();
        }
        // Else: Invalid forward move (diagonal, already visited, etc.) - do nothing
    }

    function handleInteractionEnd(event) {
        if (!isDrawing) return; // Only act if drawing was active
        event.preventDefault();
        isDrawing = false; // Stop drawing state

        // Remove 'head' styling from the last cell
        if(currentPath.length > 0) {
            const head = currentPath[currentPath.length - 1];
            const segment = (nextExpectedDot > 1) ? nextExpectedDot - 1 : 1;
            const headDotValue = gridData[head[0]][head[1]].dot;
            // Style: isPath=true, segment, isStart=(headDotValue===1), isDotReached=(headDotValue<nextExpectedDot), isHead=false
            updateCellPathStyle(head[0], head[1], true, segment, headDotValue === 1, headDotValue !== null && headDotValue < nextExpectedDot, false);
        }

        // Check if the game is won or if there are errors
        checkWinCondition();
    }

    function handleMouseLeaveGrid(event) {
        // If drawing and mouse leaves the grid container bounds, end interaction
         if (isDrawing && (!event.relatedTarget || !gridContainer.contains(event.relatedTarget))) {
            handleInteractionEnd(event);
        }
    }

    // --- Game Logic ---
    function isValidForwardMove(newRow, newCol) {
        // 1. Check bounds (although likely checked before calling)
        if (newRow < 0 || newRow >= GRID_SIZE || newCol < 0 || newCol >= GRID_SIZE) return false;

        // 2. Check adjacency (only horizontal/vertical)
        const rowDiff = Math.abs(newRow - currentPos.row);
        const colDiff = Math.abs(newCol - currentPos.col);
        if (!((rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1))) return false;

        // 3. Check if already visited
        if (gridData[newRow][newCol].visited) return false;

        return true; // Passed all checks
    }

    function checkWinCondition() {
        const allDotsVisited = (nextExpectedDot > MAX_DOT);
        const allCellsVisited = (currentPath.length === TOTAL_CELLS);

        // Only evaluate fully if drawing has stopped
        if (!isDrawing) {
            if (allDotsVisited && allCellsVisited) {
                setMessage("Congratulations! You solved the puzzle!", "win");
                hintButton.disabled = true; // Disable hint on win
            } else if (currentPath.length > 0) { // Only show errors if a path was started
                 if (allDotsVisited && !allCellsVisited) {
                     setMessage(`Connected all dots, but missed ${TOTAL_CELLS - currentPath.length} cells. Path invalid.`, "error");
                } else { // Dots not finished OR cells missed
                     setMessage(`Path incomplete or invalid. Expected dot ${nextExpectedDot}.`, "error");
                }
            }
            // If path length is 0, keep initial message (no error needed)
        }
        // If still drawing, intermediate messages are handled by handleInteractionMove
    }


    // --- Hint Logic ---
    function showHint() {
        if (hintActive || currentPath.length === 0 || nextExpectedDot > MAX_DOT) {
            // No hint if already showing, game not started, or game won
            return;
        }

        const head = currentPath[currentPath.length - 1]; // Current end of path
        const r = head[0];
        const c = head[1];
        let foundHint = false;
        const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; // N, S, W, E

        directions.forEach(([dr, dc]) => {
            const nr = r + dr;
            const nc = c + dc;
            // Is the adjacent cell a valid FORWARD move?
            if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE && !gridData[nr][nc].visited) {
                gridData[nr][nc].element?.classList.add('hint-cell'); // Add hint style
                foundHint = true;
            }
        });

        if (foundHint) {
            hintActive = true;
            hintButton.disabled = true; // Temporarily disable hint button
            setMessage("Showing possible next moves...");
            clearTimeout(hintTimeoutId); // Clear any previous hint timeout
            hintTimeoutId = setTimeout(clearHint, HINT_DURATION); // Set timer to clear this hint
        } else {
             setMessage("Stuck! No valid moves from here?", "error"); // Should not happen in solvable puzzle
        }
    }

    function clearHint() {
        gridContainer.querySelectorAll('.hint-cell').forEach(cell => cell.classList.remove('hint-cell'));
        hintActive = false;
        // Re-enable hint button only if the game is still in progress
        if (nextExpectedDot <= MAX_DOT && currentPath.length > 0) {
             hintButton.disabled = false;
        }
        // Restore message if it was the hint message
        if (messageElement.textContent === "Showing possible next moves...") {
             setMessage(`Continue path... Go to dot ${nextExpectedDot}.`);
        }
    }

    // --- Event Listeners ---
    // Mouse
    gridContainer.addEventListener('mousedown', handleInteractionStart);
    gridContainer.addEventListener('mousemove', handleInteractionMove);
    window.addEventListener('mouseup', handleInteractionEnd); // Catch mouseup anywhere
    gridContainer.addEventListener('mouseleave', handleMouseLeaveGrid); // Handle leaving grid bounds

     // Touch
    gridContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });
    gridContainer.addEventListener('touchmove', handleInteractionMove, { passive: false });
    gridContainer.addEventListener('touchend', handleInteractionEnd);
    gridContainer.addEventListener('touchcancel', handleInteractionEnd); // Treat cancel like end

    // Buttons
    resetButton.addEventListener('click', setupGrid);
    hintButton.addEventListener('click', showHint);

    // --- Initial Setup ---
    setupGrid(); // Create the grid and initialize the game when the page loads

}); // End DOMContentLoaded

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.