Beango
This commit is contained in:
parent
bd81f72bc9
commit
94291a6b94
3 changed files with 594 additions and 0 deletions
72
beango/css/beango.css
Normal file
72
beango/css/beango.css
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* Basic styling for the board */
|
||||
.centered h1 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
html {
|
||||
background: linear-gradient(135deg, #ff7e5f, #feb47b); /* Modern gradient background */
|
||||
}
|
||||
|
||||
.bingo-board {
|
||||
display: grid;
|
||||
gap: 4px; /* Adjust gap as needed */
|
||||
}
|
||||
.centered {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
#bean {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.bingo-cell {
|
||||
border: 1px solid #8B4513; /* Brown border */
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
background-color: #F5F5DC; /* Beige background */
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
min-width: 80px; /* Add minimum width */
|
||||
aspect-ratio: 1 / 1; /* Ensure square cells */
|
||||
/* Add transition for background color changes */
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.config-pane {
|
||||
transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
|
||||
max-height: 500px; /* Adjust as needed */
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
}
|
||||
.config-pane.minimized {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* Configuration Pane Styling */
|
||||
.config-pane-closed {
|
||||
transform: translateX(100%); /* Hide off-screen to the right */
|
||||
}
|
||||
|
||||
.config-pane-open {
|
||||
transform: translateX(0); /* Slide in from the right */
|
||||
}
|
||||
|
||||
/* Ensure transition is applied (already added in HTML via Tailwind classes) */
|
||||
/* #config-pane {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
} */
|
||||
|
||||
/* Basic styling for the board */
|
||||
#bingo-board-container {
|
||||
}
|
||||
79
beango/index.html
Normal file
79
beango/index.html
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Beango!</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="css/beango.css">
|
||||
</head>
|
||||
<body class="flex h-screen overflow-hidden font-sans relative">
|
||||
|
||||
<!-- Notification Area -->
|
||||
<div id="notification-area" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 px-4 py-2 rounded shadow-lg text-white text-sm transition-opacity duration-300 opacity-0">
|
||||
Notification Message
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area (Board and Controls) -->
|
||||
<div class="flex-grow flex flex-col items-center justify-center p-4 overflow-y-auto">
|
||||
<div id="board-header" class="bg-white rounded-t-lg p-4 w-full max-w-2xl centered">
|
||||
<h1 class="text-4xl font-bold text-green-800 mb-6">Beango!</h1>
|
||||
<img id="bean" src="../bean.svg" alt="Bean" onclick="explodeBeans()">
|
||||
</div>
|
||||
|
||||
<!-- Bingo Board Container -->
|
||||
<div id="bingo-board-container" class="w-full max-w-2xl bg-white p-4 rounded-b-lg shadow-md mb-4">
|
||||
<div id="bingo-board" class="bingo-board">
|
||||
<!-- Cells will be generated here -->
|
||||
<div class="bingo-cell">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Control Buttons -->
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<button onclick="generateBoard()" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
||||
Generate/Update Board
|
||||
</button>
|
||||
<button onclick="randomizeBoard()" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded">
|
||||
Randomize
|
||||
</button>
|
||||
<button onclick="clearMarks()" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
|
||||
Clear Marks
|
||||
</button>
|
||||
<button id="config-toggle-button" onclick="toggleConfig()" class="fixed top-4 right-4 z-30 bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-full shadow-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.646.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.004.827c-.293.24-.438.613-.431.992a6.759 6.759 0 0 1 0 1.555c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.333.184-.583.496-.646.87l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.063-.374-.313-.686-.646-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.293-.24.438-.613.431-.992a6.759 6.759 0 0 1 0-1.555c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.184.582-.496.646-.87l.213-1.281Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Pane (Right Side) -->
|
||||
<div id="config-pane" class="w-80 bg-white p-6 shadow-lg fixed top-0 right-0 h-full z-20 overflow-y-auto transition-transform duration-300 ease-in-out">
|
||||
<h2 class="text-xl font-semibold text-green-700 mb-4">Configuration</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="board-size" class="block text-sm font-medium text-gray-700">Board Size (e.g., 5 for 5x5):</label>
|
||||
<input type="number" id="board-size" name="board-size" min="1" value="5" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label for="cell-contents" class="block text-sm font-medium text-gray-700">Cell Contents (one item per line):</label>
|
||||
<textarea id="cell-contents" name="cell-contents" rows="10" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm" placeholder="Enter bingo items here, one per line..."></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="file-input" class="block text-sm font-medium text-gray-700">Or Upload File:</label>
|
||||
<input type="file" id="file-input" name="file-input" accept=".txt" class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-green-50 file:text-green-700 hover:file:bg-green-100">
|
||||
</div>
|
||||
<button onclick="generateBoard()" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
||||
Apply Settings & Generate
|
||||
</button>
|
||||
<button onclick="resetSettings()" class="w-full mt-2 bg-gray-400 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded">
|
||||
Reset Settings & Board
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/beango.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
443
beango/js/beango.js
Normal file
443
beango/js/beango.js
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
let currentItems = []; // Holds the original list of items provided by the user
|
||||
let displayedItems = []; // Holds the items currently displayed on the board
|
||||
let notificationTimeout = null; // To manage hiding notifications
|
||||
|
||||
// --- localStorage Keys ---
|
||||
const LS_BOARD_SIZE = 'beango_boardSize';
|
||||
const LS_CELL_ITEMS = 'beango_cellItems'; // Original items from textarea/file
|
||||
const LS_DISPLAYED_ITEMS = 'beango_displayedItems'; // Items currently shown on the board
|
||||
const LS_MARKED_INDICES = 'beango_markedIndices';
|
||||
const LS_CONFIG_OPEN = 'beango_configOpen'; // Changed from minimized
|
||||
|
||||
// --- Notification Function ---
|
||||
function showNotification(message, type = 'info', duration = 3000) {
|
||||
const notificationArea = document.getElementById('notification-area');
|
||||
if (!notificationArea) return;
|
||||
|
||||
// Clear any existing timeout
|
||||
if (notificationTimeout) {
|
||||
clearTimeout(notificationTimeout);
|
||||
}
|
||||
|
||||
notificationArea.textContent = message;
|
||||
// Apply Tailwind classes based on type
|
||||
notificationArea.className = 'fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 px-4 py-2 rounded shadow-lg text-white text-sm transition-opacity duration-300'; // Reset classes
|
||||
switch (type) {
|
||||
case 'success':
|
||||
notificationArea.classList.add('bg-green-500');
|
||||
break;
|
||||
case 'warning':
|
||||
notificationArea.classList.add('bg-yellow-500');
|
||||
break;
|
||||
case 'error':
|
||||
notificationArea.classList.add('bg-red-500');
|
||||
break;
|
||||
default: // info
|
||||
notificationArea.classList.add('bg-blue-500');
|
||||
break;
|
||||
}
|
||||
|
||||
// Make it visible
|
||||
notificationArea.classList.add('opacity-100');
|
||||
|
||||
// Set timeout to hide
|
||||
notificationTimeout = setTimeout(() => {
|
||||
notificationArea.classList.remove('opacity-100');
|
||||
notificationArea.classList.add('opacity-0');
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// --- Functions ---
|
||||
|
||||
function toggleConfig() {
|
||||
const pane = document.getElementById('config-pane');
|
||||
const isOpen = pane.classList.contains('config-pane-open');
|
||||
|
||||
if (isOpen) {
|
||||
pane.classList.remove('config-pane-open');
|
||||
pane.classList.add('config-pane-closed');
|
||||
localStorage.setItem(LS_CONFIG_OPEN, 'false');
|
||||
} else {
|
||||
pane.classList.remove('config-pane-closed');
|
||||
pane.classList.add('config-pane-open');
|
||||
localStorage.setItem(LS_CONFIG_OPEN, 'true');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('file-input').addEventListener('change', function(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('cell-contents').value = e.target.result;
|
||||
showNotification('File loaded. Click Generate/Update to use items.', 'success');
|
||||
};
|
||||
reader.onerror = function() {
|
||||
showNotification('Error reading file.', 'error');
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
});
|
||||
|
||||
function getItemsFromInput() {
|
||||
const text = document.getElementById('cell-contents').value.trim();
|
||||
return text ? text.split('\n').map(item => item.trim()).filter(item => item) : [];
|
||||
}
|
||||
|
||||
// Helper to save board state
|
||||
function saveBoardState() {
|
||||
const sizeInput = document.getElementById('board-size');
|
||||
const size = sizeInput ? parseInt(sizeInput.value, 10) : 5; // Default to 5 if input missing
|
||||
const markedIndices = getMarkedIndices();
|
||||
|
||||
localStorage.setItem(LS_BOARD_SIZE, size);
|
||||
localStorage.setItem(LS_CELL_ITEMS, JSON.stringify(currentItems)); // Save original items
|
||||
localStorage.setItem(LS_DISPLAYED_ITEMS, JSON.stringify(displayedItems)); // Save displayed items
|
||||
localStorage.setItem(LS_MARKED_INDICES, JSON.stringify(markedIndices));
|
||||
}
|
||||
|
||||
function generateBoard() {
|
||||
const sizeInput = document.getElementById('board-size');
|
||||
const size = parseInt(sizeInput.value, 10);
|
||||
if (isNaN(size) || size < 1) {
|
||||
showNotification("Please enter a valid board size (minimum 1).", 'warning');
|
||||
return;
|
||||
}
|
||||
sizeInput.value = size; // Ensure value reflects parsed int
|
||||
|
||||
// Update container width based on size
|
||||
updateBoardContainerMaxWidth(size);
|
||||
updateBoardContainerMaxWidth(size, '#board-header');
|
||||
|
||||
// Get items from input and store them as the canonical list
|
||||
currentItems = getItemsFromInput();
|
||||
localStorage.setItem(LS_CELL_ITEMS, JSON.stringify(currentItems)); // Save original items immediately
|
||||
|
||||
const requiredItems = size * size;
|
||||
let itemsForBoard = [...currentItems]; // Start with a copy of original items
|
||||
let notificationMessage = 'Board generated successfully!';
|
||||
let notificationType = 'success';
|
||||
|
||||
if (itemsForBoard.length < requiredItems) {
|
||||
const diff = requiredItems - itemsForBoard.length;
|
||||
notificationMessage = `Warning: Needed ${requiredItems} items, found ${itemsForBoard.length}. Padding with ${diff} placeholder(s).`;
|
||||
notificationType = 'warning';
|
||||
for (let i = 0; i < diff; i++) {
|
||||
itemsForBoard.push(`Placeholder ${i+1}`);
|
||||
}
|
||||
// Update currentItems if placeholders were added, so randomize uses them too
|
||||
currentItems = [...itemsForBoard];
|
||||
localStorage.setItem(LS_CELL_ITEMS, JSON.stringify(currentItems));
|
||||
} else if (itemsForBoard.length > requiredItems) {
|
||||
notificationMessage = `Info: Found ${itemsForBoard.length} items, randomly selecting ${requiredItems}.`;
|
||||
notificationType = 'info';
|
||||
itemsForBoard = shuffleArray(itemsForBoard).slice(0, requiredItems);
|
||||
// Update currentItems to reflect the selected subset
|
||||
currentItems = [...itemsForBoard];
|
||||
localStorage.setItem(LS_CELL_ITEMS, JSON.stringify(currentItems));
|
||||
}
|
||||
|
||||
// Shuffle the items specifically for display
|
||||
displayedItems = shuffleArray([...itemsForBoard]);
|
||||
|
||||
const board = document.getElementById('bingo-board');
|
||||
board.innerHTML = ''; // Clear previous board
|
||||
board.style.gridTemplateColumns = `repeat(${size}, minmax(80px, 1fr))`;
|
||||
|
||||
displayedItems.forEach((item, index) => {
|
||||
const cell = document.createElement('div');
|
||||
cell.classList.add('bingo-cell', 'cursor-pointer', 'hover:bg-green-200');
|
||||
cell.textContent = item;
|
||||
cell.dataset.index = index; // Add index for saving marks
|
||||
cell.onclick = () => selectCell(cell);
|
||||
board.appendChild(cell);
|
||||
});
|
||||
|
||||
clearMarks(false); // Clear previous marks visually but don't save yet
|
||||
saveBoardState(); // Save the newly generated state
|
||||
showNotification(notificationMessage, notificationType);
|
||||
}
|
||||
|
||||
function randomizeBoard() {
|
||||
const storedItemsRaw = localStorage.getItem(LS_CELL_ITEMS);
|
||||
const sizeRaw = localStorage.getItem(LS_BOARD_SIZE);
|
||||
|
||||
if (!storedItemsRaw || !sizeRaw) {
|
||||
showNotification("Cannot randomize: Board state not found. Please generate the board first.", 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const storedItems = JSON.parse(storedItemsRaw);
|
||||
const size = parseInt(sizeRaw, 10);
|
||||
const requiredItems = size * size;
|
||||
|
||||
if (storedItems.length < requiredItems) {
|
||||
showNotification(`Cannot randomize: Not enough items saved (${storedItems.length}/${requiredItems}). Please generate the board again.`, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we use the correct set if placeholders were added or items were sliced
|
||||
let itemsToShuffle = [...storedItems];
|
||||
if (itemsToShuffle.length > requiredItems) {
|
||||
// This case implies items were sliced during generation, which is stored in currentItems
|
||||
// Randomizing should still use the items that *could* be on the board
|
||||
itemsToShuffle = itemsToShuffle.slice(0, requiredItems);
|
||||
|
||||
} else if (itemsToShuffle.length < requiredItems) {
|
||||
// This case shouldn't happen if generateBoard saved padded items
|
||||
showNotification("Cannot randomize: Item count mismatch. Please regenerate the board.", 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
displayedItems = shuffleArray([...itemsToShuffle]); // Shuffle the items for display
|
||||
|
||||
const board = document.getElementById('bingo-board');
|
||||
const cells = board.querySelectorAll('.bingo-cell');
|
||||
|
||||
if (cells.length !== displayedItems.length) {
|
||||
showNotification("Cannot randomize: Board size mismatch. Please generate the board again.", 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
cells.forEach((cell, index) => {
|
||||
cell.textContent = displayedItems[index];
|
||||
cell.classList.remove('bg-yellow-300', 'ring-2', 'ring-blue-500'); // Clear marks visually
|
||||
});
|
||||
|
||||
clearMarks(false); // Clear visual marks
|
||||
saveBoardState(); // Save the new randomized state (including cleared marks)
|
||||
showNotification('Board randomized!', 'success');
|
||||
}
|
||||
|
||||
function selectCell(cell) {
|
||||
cell.classList.toggle('bg-yellow-300'); // Toggle mark style directly
|
||||
saveBoardState(); // Save updated marks immediately after click
|
||||
}
|
||||
|
||||
function clearMarks(save = true) {
|
||||
const cells = document.querySelectorAll('#bingo-board .bingo-cell');
|
||||
cells.forEach(cell => {
|
||||
cell.classList.remove('bg-yellow-300', 'ring-2', 'ring-blue-500'); // Remove ring class here too
|
||||
});
|
||||
if (save) {
|
||||
saveBoardState(); // Save cleared marks
|
||||
showNotification('Marks cleared.', 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// Fisher-Yates (aka Knuth) Shuffle Algorithm
|
||||
function shuffleArray(array) {
|
||||
let currentIndex = array.length, randomIndex;
|
||||
while (currentIndex !== 0) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex--;
|
||||
[array[currentIndex], array[randomIndex]] = [
|
||||
array[randomIndex], array[currentIndex]];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// Function to reset settings and clear board
|
||||
function resetSettings() {
|
||||
// Clear localStorage relevant to the board
|
||||
localStorage.removeItem(LS_BOARD_SIZE);
|
||||
localStorage.removeItem(LS_CELL_ITEMS);
|
||||
localStorage.removeItem(LS_DISPLAYED_ITEMS);
|
||||
localStorage.removeItem(LS_MARKED_INDICES);
|
||||
// Optionally reset config pane state too?
|
||||
// localStorage.removeItem(LS_CONFIG_OPEN);
|
||||
|
||||
// Reset global variables
|
||||
currentItems = [];
|
||||
displayedItems = [];
|
||||
|
||||
// Reset form inputs
|
||||
document.getElementById('board-size').value = 5; // Default size
|
||||
document.getElementById('cell-contents').value = '';
|
||||
document.getElementById('file-input').value = ''; // Clear file input
|
||||
|
||||
// Clear the board display
|
||||
const board = document.getElementById('bingo-board');
|
||||
board.innerHTML = '<div class="bingo-cell">Settings Reset. Generate a new board!</div>';
|
||||
board.style.gridTemplateColumns = ''; // Clear grid style
|
||||
|
||||
// Reset container width to default
|
||||
updateBoardContainerMaxWidth(5); // Assuming 5 is the default size
|
||||
updateBoardContainerMaxWidth(5, '#board-header');
|
||||
showNotification('Settings and board reset.', 'success');
|
||||
}
|
||||
|
||||
// --- Helper to get indices of marked cells ---
|
||||
function getMarkedIndices() {
|
||||
const markedCells = document.querySelectorAll('#bingo-board .bingo-cell.bg-yellow-300');
|
||||
const indices = [];
|
||||
markedCells.forEach(cell => {
|
||||
// Ensure dataset.index exists and is a valid number
|
||||
if (cell.dataset && typeof cell.dataset.index !== 'undefined') {
|
||||
const index = parseInt(cell.dataset.index, 10);
|
||||
if (!isNaN(index)) {
|
||||
indices.push(index);
|
||||
}
|
||||
} else {
|
||||
console.warn('Marked cell found without a valid data-index:', cell);
|
||||
}
|
||||
});
|
||||
return indices;
|
||||
}
|
||||
|
||||
// --- Load State on Page Load ---
|
||||
function loadFromLocalStorage() {
|
||||
const savedSize = localStorage.getItem(LS_BOARD_SIZE) || '5'; // Default to 5
|
||||
const savedItemsText = localStorage.getItem(LS_CELL_ITEMS);
|
||||
const savedDisplayedItems = localStorage.getItem(LS_DISPLAYED_ITEMS);
|
||||
const savedMarkedIndices = localStorage.getItem(LS_MARKED_INDICES);
|
||||
const configIsOpen = localStorage.getItem(LS_CONFIG_OPEN) === 'true';
|
||||
|
||||
// Restore Config Pane State
|
||||
const pane = document.getElementById('config-pane');
|
||||
if (configIsOpen) {
|
||||
pane.classList.remove('config-pane-closed');
|
||||
pane.classList.add('config-pane-open');
|
||||
} else {
|
||||
pane.classList.remove('config-pane-open');
|
||||
pane.classList.add('config-pane-closed');
|
||||
}
|
||||
|
||||
// Restore Config Inputs
|
||||
const size = parseInt(savedSize, 10); // Parse size here
|
||||
document.getElementById('board-size').value = savedSize;
|
||||
|
||||
// Update container width based on loaded size
|
||||
updateBoardContainerMaxWidth(size);
|
||||
updateBoardContainerMaxWidth(size, '#board-header');
|
||||
|
||||
if (savedItemsText) {
|
||||
try {
|
||||
currentItems = JSON.parse(savedItemsText); // Restore original items array
|
||||
document.getElementById('cell-contents').value = currentItems.join('\n');
|
||||
} catch (e) {
|
||||
showNotification('Error parsing saved items. Resetting items.', 'error');
|
||||
localStorage.removeItem(LS_CELL_ITEMS);
|
||||
currentItems = [];
|
||||
document.getElementById('cell-contents').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Restore Board only if essential data exists
|
||||
if (savedDisplayedItems) {
|
||||
try {
|
||||
displayedItems = JSON.parse(savedDisplayedItems); // Restore displayed items array
|
||||
} catch (e) {
|
||||
showNotification('Error parsing saved board state. Please generate again.', 'error');
|
||||
localStorage.removeItem(LS_DISPLAYED_ITEMS);
|
||||
localStorage.removeItem(LS_MARKED_INDICES);
|
||||
displayedItems = [];
|
||||
document.getElementById('bingo-board').innerHTML = '<div class="bingo-cell">Error loading board. Generate New.</div>';
|
||||
return; // Stop board restore
|
||||
}
|
||||
|
||||
const requiredItems = size * size;
|
||||
|
||||
// Check if the loaded data is consistent
|
||||
if (!Array.isArray(displayedItems) || displayedItems.length !== requiredItems) {
|
||||
showNotification("Saved board data mismatch. Please generate again.", 'warning');
|
||||
localStorage.removeItem(LS_DISPLAYED_ITEMS);
|
||||
localStorage.removeItem(LS_MARKED_INDICES);
|
||||
document.getElementById('bingo-board').innerHTML = '<div class="bingo-cell">Data Mismatch. Generate New.</div>';
|
||||
return; // Stop loading the board state
|
||||
}
|
||||
|
||||
const board = document.getElementById('bingo-board');
|
||||
board.innerHTML = ''; // Clear placeholder
|
||||
board.style.gridTemplateColumns = `repeat(${size}, minmax(80px, 1fr))`;
|
||||
|
||||
const markedIndices = savedMarkedIndices ? JSON.parse(savedMarkedIndices) : [];
|
||||
|
||||
displayedItems.forEach((item, index) => {
|
||||
const cell = document.createElement('div');
|
||||
cell.classList.add('bingo-cell', 'cursor-pointer', 'hover:bg-green-200');
|
||||
cell.textContent = item;
|
||||
cell.dataset.index = index; // Ensure index is set for loading marks correctly
|
||||
cell.onclick = () => selectCell(cell);
|
||||
if (markedIndices.includes(index)) {
|
||||
cell.classList.add('bg-yellow-300'); // Apply saved mark
|
||||
}
|
||||
board.appendChild(cell);
|
||||
});
|
||||
} else {
|
||||
// If no saved board state, show the default message
|
||||
const board = document.getElementById('bingo-board');
|
||||
// Check if board div exists and has no children before adding message
|
||||
if (board && board.children.length === 0) {
|
||||
board.innerHTML = '<div class="bingo-cell">Generate a board using the config panel!</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper to manage board container width ---
|
||||
function updateBoardContainerMaxWidth(size, element = '#bingo-board-container') {
|
||||
const container = document.querySelector(element);
|
||||
|
||||
if (!container) return;
|
||||
|
||||
// Remove existing max-w classes
|
||||
const classList = container.classList;
|
||||
const existingMaxWidthClasses = Array.from(classList).filter(cls => cls.startsWith('max-w-'));
|
||||
classList.remove(...existingMaxWidthClasses);
|
||||
|
||||
// Determine and add the new max-w class
|
||||
let maxWidthClass = 'max-w-2xl'; // Default for 5x5
|
||||
if (size <= 3) {
|
||||
maxWidthClass = 'max-w-lg';
|
||||
} else if (size === 4) {
|
||||
maxWidthClass = 'max-w-xl';
|
||||
} else if (size === 5) {
|
||||
maxWidthClass = 'max-w-2xl';
|
||||
} else if (size === 6) {
|
||||
maxWidthClass = 'max-w-3xl';
|
||||
} else if (size === 7) {
|
||||
maxWidthClass = 'max-w-4xl';
|
||||
} else if (size >= 8) {
|
||||
maxWidthClass = 'max-w-5xl'; // Cap here, rely on scroll for larger
|
||||
}
|
||||
container.classList.add(maxWidthClass);
|
||||
}
|
||||
|
||||
function explodeBeans() {
|
||||
const container = document.querySelector('.centered');
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const newBean = document.createElement('img');
|
||||
newBean.src = '../bean.svg';
|
||||
newBean.style.position = 'absolute';
|
||||
newBean.style.width = '50px';
|
||||
newBean.style.height = '50px';
|
||||
newBean.style.transition = 'transform 1s ease-out, opacity 1s ease-out';
|
||||
newBean.style.opacity = '0';
|
||||
|
||||
// Initial position at the center
|
||||
newBean.style.transform = 'translate(0, 0) scale(0.5)';
|
||||
|
||||
container.appendChild(newBean);
|
||||
|
||||
// Random position around the original bean
|
||||
const angle = Math.random() * 2 * Math.PI;
|
||||
const distance = Math.random() * 100 + 50;
|
||||
|
||||
// Trigger the animation
|
||||
setTimeout(() => {
|
||||
newBean.style.opacity = '1';
|
||||
newBean.style.transform = `translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px) scale(1)`;
|
||||
}, 0);
|
||||
|
||||
// Remove the bean after animation
|
||||
setTimeout(() => {
|
||||
newBean.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
container.removeChild(newBean);
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Event Listener for Page Load ---
|
||||
document.addEventListener('DOMContentLoaded', loadFromLocalStorage);
|
||||
Loading…
Add table
Add a link
Reference in a new issue