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 const LS_BACKGROUND_TYPE = 'beango_backgroundType'; // 'solid' or 'gradient' const LS_SOLID_COLOR = 'beango_solidColor'; const LS_SOLID_COLOR_OPACITY = 'beango_solidColorOpacity'; const LS_GRADIENT_COLOR_1 = 'beango_gradientColor1'; const LS_GRADIENT_COLOR_1_OPACITY = 'beango_gradientColor1Opacity'; const LS_GRADIENT_COLOR_2 = 'beango_gradientColor2'; const LS_GRADIENT_COLOR_2_OPACITY = 'beango_gradientColor2Opacity'; const LS_GRADIENT_DIRECTION = 'beango_gradientDirection'; const LS_ORIGINAL_ITEMS = 'beango_originalItems'; // User's raw input const LS_HEADER_TEXT = 'beango_headerText'; const LS_HEADER_IMAGE_URL = 'beango_headerImageUrl'; const LS_HEADER_TEXT_COLOR = 'beango_headerTextColor'; const LS_HEADER_TEXT_COLOR_OPACITY = 'beango_headerTextColorOpacity'; const LS_HEADER_BG_COLOR = 'beango_headerBgColor'; const LS_HEADER_BG_OPACITY = 'beango_headerBgOpacity'; const LS_MARKED_COLOR = 'beango_markedColor'; const LS_MARKED_COLOR_OPACITY = 'beango_markedColorOpacity'; // (replaces LS_MARKED_OPACITY) const LS_MARKED_IMAGE_URL = 'beango_markedImageUrl'; const LS_MARKED_IMAGE_OPACITY = 'beango_markedImageOpacity'; const LS_CELL_BORDER_COLOR = 'beango_cellBorderColor'; const LS_CELL_BORDER_OPACITY = 'beango_cellBorderOpacity'; const LS_CELL_BORDER_WIDTH = 'beango_cellBorderWidth'; const LS_CELL_BG_COLOR = 'beango_cellBgColor'; const LS_CELL_BG_OPACITY = 'beango_cellBgOpacity'; const LS_CELL_BG_IMAGE_URL = 'beango_cellBgImageUrl'; const LS_CELL_BG_IMAGE_OPACITY = 'beango_cellBgImageOpacity'; const LS_CELL_TEXT_COLOR = 'beango_cellTextColor'; const LS_CELL_TEXT_OPACITY = 'beango_cellTextOpacity'; const LS_CELL_OUTLINE_COLOR = 'beango_cellOutlineColor'; const LS_CELL_OUTLINE_OPACITY = 'beango_cellOutlineOpacity'; const LS_CELL_OUTLINE_WIDTH = 'beango_cellOutlineWidth'; const LS_MARKED_BORDER_COLOR = 'beango_markedBorderColor'; const LS_MARKED_BORDER_OPACITY = 'beango_markedBorderOpacity'; const LS_MARKED_BORDER_WIDTH = 'beango_markedBorderWidth'; const LS_BOARD_BG_COLOR = 'beango_boardBgColor'; const LS_BOARD_BG_COLOR_OPACITY = 'beango_boardBgColorOpacity'; // (replaces LS_BOARD_BG_OPACITY) const LS_BOARD_BG_IMAGE_URL = 'beango_boardBgImageUrl'; const LS_MARKED_CELL_TEXT_COLOR = 'beango_markedCellTextColor'; const LS_MARKED_CELL_TEXT_OPACITY = 'beango_markedCellTextOpacity'; const LS_MARKED_CELL_OUTLINE_COLOR = 'beango_markedCellOutlineColor'; const LS_MARKED_CELL_OUTLINE_OPACITY = 'beango_markedCellOutlineOpacity'; const LS_MARKED_CELL_OUTLINE_WIDTH = 'beango_markedCellOutlineWidth'; const LS_HEADER_TEXT_OUTLINE_COLOR = 'beango_headerTextOutlineColor'; const LS_HEADER_TEXT_OUTLINE_OPACITY = 'beango_headerTextOutlineOpacity'; const LS_HEADER_TEXT_OUTLINE_WIDTH = 'beango_headerTextOutlineWidth'; const LS_HEADER_TEXT_FONT_SIZE = 'beango_headerTextFontSize'; // New key const LS_HEADER_TEXT_FONT_FAMILY = 'beango_headerTextFontFamily'; // New key const LS_HEADER_TEXT_STYLE_ITALIC = 'beango_headerTextStyleItalic'; // New key for italic const LS_HEADER_TEXT_STYLE_BOLD = 'beango_headerTextStyleBold'; // New key for bold // --- Default Values --- const DEFAULT_SAMPLE_ITEMS = [ "Sample Item 1", "Sample Item 2", "Sample Item 3", "Sample Item 4", "Sample Item 5", "Sample Item 6", "Sample Item 7", "Free Space", "Sample Item 9", "Sample Item 10", "Sample Item 11", "Sample Item 12", "Sample Item 13", "Sample Item 14", "Sample Item 15", "Sample Item 16", "Sample Item 17", "Sample Item 18", "Sample Item 19", "Sample Item 20", "Sample Item 21", "Sample Item 22", "Sample Item 23", "Sample Item 24", "Sample Item 25" ]; const DEFAULT_SOLID_COLOR = '#ff7e5f'; // Default to first color of gradient const DEFAULT_SOLID_COLOR_OPACITY = 100; // const DEFAULT_GRADIENT_COLOR_1 = '#faaca8'; // Softer pink/orange const DEFAULT_GRADIENT_COLOR_1_OPACITY = 100; // const DEFAULT_GRADIENT_COLOR_2 = '#ddd6f3'; // Soft purple const DEFAULT_GRADIENT_COLOR_2_OPACITY = 100; // const DEFAULT_GRADIENT_DIRECTION = 'to right top'; // Changed direction const DEFAULT_HEADER_TEXT = 'Beango!'; // REVERTED default back to Beango! const DEFAULT_HEADER_IMAGE_URL = '/bean.svg'; // No default image const DEFAULT_HEADER_TEXT_COLOR = '#15803d'; // Tailwind green-700 (approx) const DEFAULT_HEADER_TEXT_COLOR_OPACITY = 100; // const DEFAULT_HEADER_BG_COLOR = '#ffffff'; const DEFAULT_HEADER_BG_OPACITY = 100; const DEFAULT_MARKED_COLOR = '#e9ecef'; // Light grey for marked cells const DEFAULT_MARKED_COLOR_OPACITY = 40; // (replaces DEFAULT_MARKED_OPACITY) - Significantly lower const DEFAULT_MARKED_IMAGE_URL = 'https://www.svgrepo.com/download/286496/cross.svg'; // Default cross image const DEFAULT_MARKED_IMAGE_OPACITY = 70; // const DEFAULT_CELL_BORDER_COLOR = '#808080'; // Default dark grey const DEFAULT_CELL_BORDER_OPACITY = 80; // Slightly transparent const DEFAULT_CELL_BORDER_WIDTH = 1; // const DEFAULT_CELL_BG_COLOR = '#f8f9fa'; // Off-white const DEFAULT_CELL_BG_OPACITY = 95; // Slightly transparent const DEFAULT_CELL_BG_IMAGE_URL = ''; // Default no image const DEFAULT_CELL_BG_IMAGE_OPACITY = 100; // const DEFAULT_CELL_TEXT_COLOR = '#000000'; const DEFAULT_CELL_TEXT_OPACITY = 100; const DEFAULT_CELL_OUTLINE_COLOR = '#ffffff'; const DEFAULT_CELL_OUTLINE_OPACITY = 100; const DEFAULT_CELL_OUTLINE_WIDTH = 0; // Default 0px - Cleaner look const DEFAULT_MARKED_BORDER_COLOR = DEFAULT_CELL_BORDER_COLOR; // Match default border color const DEFAULT_MARKED_BORDER_OPACITY = 50; // Less opaque than default border const DEFAULT_MARKED_BORDER_WIDTH = 0; // No border for marked cells const DEFAULT_BOARD_BG_COLOR = '#ffffff'; // Default white const DEFAULT_BOARD_BG_COLOR_OPACITY = 100; // (replaces DEFAULT_BOARD_BG_OPACITY) const DEFAULT_BOARD_BG_IMAGE_URL = ''; // Default no image const DEFAULT_MARKED_CELL_TEXT_COLOR = '#000000'; // Keep black for readability const DEFAULT_MARKED_CELL_TEXT_OPACITY = 50; const DEFAULT_MARKED_CELL_OUTLINE_COLOR = '#ffffff'; const DEFAULT_MARKED_CELL_OUTLINE_OPACITY = 100; const DEFAULT_MARKED_CELL_OUTLINE_WIDTH = 0; // Default 0px - No outline for marked const DEFAULT_HEADER_TEXT_OUTLINE_COLOR = '#ffffff'; // Default outline white const DEFAULT_HEADER_TEXT_OUTLINE_OPACITY = 100; const DEFAULT_HEADER_TEXT_OUTLINE_WIDTH = 0; // Default no outline const DEFAULT_HEADER_TEXT_FONT_SIZE = 36; // New default (approx text-4xl) const DEFAULT_HEADER_TEXT_FONT_FAMILY = 'sans-serif'; // New default const DEFAULT_HEADER_TEXT_STYLE_ITALIC = false; // Default not italic const DEFAULT_HEADER_TEXT_STYLE_BOLD = true; // Default bold // --- 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); } // --- Helper Function --- function hexToRgba(hex, opacityPercent = 100) { // Ensure opacityPercent is a number before using it let numericOpacity = parseInt(opacityPercent, 10); // Try parsing // If parsing failed (e.g., input was not a number string) or input was null/undefined, default to 100 if (isNaN(numericOpacity)) { numericOpacity = 100; } // Remove hash if it exists hex = hex.replace('#', ''); // Handle short hex codes if (hex.length === 3) { hex = hex.split('').map(char => char + char).join(''); } // Ensure hex is 6 digits if (hex.length !== 6) { console.warn(`Invalid hex color: ${hex}. Using fallback.`); hex = '000000'; // Default to black on error } const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); // Clamp and normalize opacity using the correctly parsed/defaulted value const clampedOpacityPercent = Math.max(0, Math.min(100, numericOpacity)); const opacity = clampedOpacityPercent / 100; return `rgba(${r}, ${g}, ${b}, ${opacity})`; } // --- Event Listener Helper Functions --- START // Helper function for standard input/select elements function setupInputListener(elementId, eventType, saveFn, refreshFn) { const element = document.getElementById(elementId); if (element) { element.addEventListener(eventType, () => { if (saveFn) saveFn(); if (refreshFn) refreshFn(); }); } else { console.warn(`Element with ID ${elementId} not found for listener setup.`); } } // Helper function for opacity sliders with value display function setupOpacitySliderListener(sliderId, valueSpanId, saveFn, refreshFn) { const slider = document.getElementById(sliderId); const valueSpan = document.getElementById(valueSpanId); if (slider) { slider.addEventListener('input', (e) => { if (valueSpan) valueSpan.textContent = e.target.value; if (saveFn) saveFn(); if (refreshFn) refreshFn(); }); } else { console.warn(`Element with ID ${sliderId} not found for listener setup.`); } } // --- Event Listener Helper Functions --- END // --- Functions --- // --- Function to set the HTML background (solid or gradient) --- function setBackground() { const backgroundType = document.querySelector('input[name="background-type"]:checked').value; const solidColorPicker = document.getElementById('background-color-picker'); const gradientColor1Picker = document.getElementById('gradient-color-1'); const gradientColor2Picker = document.getElementById('gradient-color-2'); const gradientDirectionSelect = document.getElementById('gradient-direction'); const htmlStyle = document.documentElement.style; let backgroundValue = ''; if (backgroundType === 'solid') { const color = solidColorPicker.value; const opacity = parseInt(document.getElementById('background-color-opacity-slider').value, 10); backgroundValue = hexToRgba(color, opacity); htmlStyle.background = backgroundValue; // Set solid color as background htmlStyle.backgroundRepeat = 'no-repeat'; htmlStyle.backgroundAttachment = 'fixed'; } else { // gradient const color1 = gradientColor1Picker.value; const opacity1 = parseInt(document.getElementById('gradient-color-1-opacity-slider').value, 10); const color2 = gradientColor2Picker.value; const opacity2 = parseInt(document.getElementById('gradient-color-2-opacity-slider').value, 10); const direction = gradientDirectionSelect.value; const rgba1 = hexToRgba(color1, opacity1); const rgba2 = hexToRgba(color2, opacity2); let gradientValue; if (direction === 'radial') { gradientValue = `radial-gradient(circle, ${rgba1}, ${rgba2})`; } else { // Handles 'deg' and direction keywords gradientValue = `linear-gradient(${direction}, ${rgba1}, ${rgba2})`; } backgroundValue = gradientValue; htmlStyle.background = gradientValue; htmlStyle.backgroundRepeat = 'no-repeat'; htmlStyle.backgroundAttachment = 'fixed'; } // htmlStyle.background = backgroundValue; // Already set inside if/else return backgroundValue; // Return the CSS value for injection (might need adjustment?) } // --- Function to manage visibility of background controls --- function toggleBackgroundControls() { const backgroundType = document.querySelector('input[name="background-type"]:checked').value; const solidSettings = document.getElementById('solid-color-settings'); const gradientSettings = document.getElementById('gradient-color-settings'); if (backgroundType === 'solid') { solidSettings.style.display = 'block'; gradientSettings.style.display = 'none'; } else { solidSettings.style.display = 'none'; gradientSettings.style.display = 'block'; } } // --- Save current background settings to localStorage --- function saveBackgroundSettings() { const backgroundType = document.querySelector('input[name="background-type"]:checked').value; localStorage.setItem(LS_BACKGROUND_TYPE, backgroundType); if (backgroundType === 'solid') { localStorage.setItem(LS_SOLID_COLOR, document.getElementById('background-color-picker').value); localStorage.setItem(LS_SOLID_COLOR_OPACITY, document.getElementById('background-color-opacity-slider').value); // Optionally remove gradient keys localStorage.removeItem(LS_GRADIENT_COLOR_1); localStorage.removeItem(LS_GRADIENT_COLOR_1_OPACITY); localStorage.removeItem(LS_GRADIENT_COLOR_2); localStorage.removeItem(LS_GRADIENT_COLOR_2_OPACITY); localStorage.removeItem(LS_GRADIENT_DIRECTION); } else { localStorage.setItem(LS_GRADIENT_COLOR_1, document.getElementById('gradient-color-1').value); localStorage.setItem(LS_GRADIENT_COLOR_1_OPACITY, document.getElementById('gradient-color-1-opacity-slider').value); localStorage.setItem(LS_GRADIENT_COLOR_2, document.getElementById('gradient-color-2').value); localStorage.setItem(LS_GRADIENT_COLOR_2_OPACITY, document.getElementById('gradient-color-2-opacity-slider').value); localStorage.setItem(LS_GRADIENT_DIRECTION, document.getElementById('gradient-direction').value); // Optionally remove solid key localStorage.removeItem(LS_SOLID_COLOR); localStorage.removeItem(LS_SOLID_COLOR_OPACITY); } } 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) : []; } // --- Save current marked style settings to localStorage --- function saveMarkedStyleSettings() { localStorage.setItem(LS_MARKED_COLOR, document.getElementById('marked-color-picker').value); localStorage.setItem(LS_MARKED_COLOR_OPACITY, document.getElementById('marked-color-opacity-slider').value); localStorage.setItem(LS_MARKED_IMAGE_URL, document.getElementById('marked-image-url-input').value); localStorage.setItem(LS_MARKED_IMAGE_OPACITY, document.getElementById('marked-image-opacity-slider').value); // Save image opacity // Save marked border color and its opacity localStorage.setItem(LS_MARKED_BORDER_COLOR, document.getElementById('marked-border-color-picker').value); localStorage.setItem(LS_MARKED_BORDER_OPACITY, document.getElementById('marked-border-opacity-slider').value); // Save marked border width localStorage.setItem(LS_MARKED_BORDER_WIDTH, document.getElementById('marked-border-width-input').value); // Save marked text styles localStorage.setItem(LS_MARKED_CELL_TEXT_COLOR, document.getElementById('marked-cell-text-color-picker').value); localStorage.setItem(LS_MARKED_CELL_TEXT_OPACITY, document.getElementById('marked-cell-text-opacity-slider').value); localStorage.setItem(LS_MARKED_CELL_OUTLINE_COLOR, document.getElementById('marked-cell-outline-color-picker').value); localStorage.setItem(LS_MARKED_CELL_OUTLINE_OPACITY, document.getElementById('marked-cell-outline-opacity-slider').value); localStorage.setItem(LS_MARKED_CELL_OUTLINE_WIDTH, document.getElementById('marked-cell-outline-width-input').value); } // --- Apply saved marked styles to a cell --- function applyMarkedCellStyle(cell) { if (!cell) return; const isMarked = cell.classList.contains('marked'); const color = localStorage.getItem(LS_MARKED_COLOR) || DEFAULT_MARKED_COLOR; const colorOpacity = parseInt(localStorage.getItem(LS_MARKED_COLOR_OPACITY) || DEFAULT_MARKED_COLOR_OPACITY, 10); const imageUrl = localStorage.getItem(LS_MARKED_IMAGE_URL) || DEFAULT_MARKED_IMAGE_URL; const imageOpacity = parseInt(localStorage.getItem(LS_MARKED_IMAGE_OPACITY) || DEFAULT_MARKED_IMAGE_OPACITY, 10); const borderColor = localStorage.getItem(LS_MARKED_BORDER_COLOR) || DEFAULT_MARKED_BORDER_COLOR; const borderOpacity = parseInt(localStorage.getItem(LS_MARKED_BORDER_OPACITY) || DEFAULT_MARKED_BORDER_OPACITY, 10); // Reset potentially conflicting styles before applying new ones cell.style.backgroundColor = ''; cell.style.backgroundImage = ''; // Don't reset border color here, let applyCellStyle handle default if unmarked if (isMarked) { // Apply marked border color + opacity cell.style.borderColor = hexToRgba(borderColor, borderOpacity); // Apply marked border width let markedBorderWidth = parseInt(localStorage.getItem(LS_MARKED_BORDER_WIDTH) || DEFAULT_MARKED_BORDER_WIDTH, 10); if (isNaN(markedBorderWidth) || markedBorderWidth < 0) { // Add check for NaN and negative markedBorderWidth = DEFAULT_MARKED_BORDER_WIDTH; } cell.style.borderWidth = `${markedBorderWidth}px`; // Always apply the background color + opacity cell.style.backgroundColor = hexToRgba(color, colorOpacity); // Apply background image if URL exists via custom property if (imageUrl && imageUrl.trim() !== '') { cell.style.setProperty('--bg-image-url', `url('${imageUrl}')`); cell.style.setProperty('--bg-image-opacity', imageOpacity / 100); cell.style.backgroundSize = 'cover'; cell.style.backgroundPosition = 'center center'; cell.style.backgroundRepeat = 'no-repeat'; } else { // Explicitly clear background image custom property cell.style.removeProperty('--bg-image-url'); } // Apply marked text styles const textSpan = cell.querySelector('.bingo-cell-text'); if (textSpan) { const textColor = localStorage.getItem(LS_MARKED_CELL_TEXT_COLOR) || DEFAULT_MARKED_CELL_TEXT_COLOR; const textOpacity = parseInt(localStorage.getItem(LS_MARKED_CELL_TEXT_OPACITY) || DEFAULT_MARKED_CELL_TEXT_OPACITY, 10); const outlineColor = localStorage.getItem(LS_MARKED_CELL_OUTLINE_COLOR) || DEFAULT_MARKED_CELL_OUTLINE_COLOR; const outlineOpacity = parseInt(localStorage.getItem(LS_MARKED_CELL_OUTLINE_OPACITY) || DEFAULT_MARKED_CELL_OUTLINE_OPACITY, 10); let outlineWidth = parseFloat(localStorage.getItem(LS_MARKED_CELL_OUTLINE_WIDTH)); // Default check: // Add log before default check if (isNaN(outlineWidth) || outlineWidth < 0) { outlineWidth = DEFAULT_MARKED_CELL_OUTLINE_WIDTH; } const rgbaTextColor = hexToRgba(textColor, textOpacity); textSpan.style.color = rgbaTextColor; // Override default style if (outlineWidth > 0) { const rgbaOutlineColor = hexToRgba(outlineColor, outlineOpacity); const shadow = ` -${outlineWidth}px -${outlineWidth}px 0 ${rgbaOutlineColor}, ${outlineWidth}px -${outlineWidth}px 0 ${rgbaOutlineColor}, -${outlineWidth}px ${outlineWidth}px 0 ${rgbaOutlineColor}, ${outlineWidth}px ${outlineWidth}px 0 ${rgbaOutlineColor}, ${outlineWidth}px 0 0 ${rgbaOutlineColor}, -${outlineWidth}px 0 0 ${rgbaOutlineColor}, 0 ${outlineWidth}px 0 ${rgbaOutlineColor}, 0 -${outlineWidth}px 0 ${rgbaOutlineColor} `; textSpan.style.textShadow = shadow; // Override default style } else { textSpan.style.textShadow = "none"; // Override default style } } } else { // If unmarked, re-apply default styles (which resets bg color, bg image, border color, and TEXT styles) applyCellStyle(cell); } } // --- Re-apply styles to all currently marked cells --- function refreshMarkedCellStyles() { const markedCells = document.querySelectorAll('#bingo-board .bingo-cell.marked'); markedCells.forEach(cell => { applyMarkedCellStyle(cell); // Re-apply based on current settings }); } // --- Save current header settings to localStorage --- function saveHeaderSettings() { localStorage.setItem(LS_HEADER_TEXT, document.getElementById('header-text-input').value); localStorage.setItem(LS_HEADER_IMAGE_URL, document.getElementById('header-image-url-input').value); localStorage.setItem(LS_HEADER_TEXT_COLOR, document.getElementById('header-text-color-picker').value); localStorage.setItem(LS_HEADER_TEXT_COLOR_OPACITY, document.getElementById('header-text-color-opacity-slider').value); localStorage.setItem(LS_HEADER_TEXT_FONT_SIZE, document.getElementById('header-text-font-size-input').value); // Save font size localStorage.setItem(LS_HEADER_TEXT_FONT_FAMILY, document.getElementById('header-text-font-family-select').value); // Save font family // Save header background localStorage.setItem(LS_HEADER_BG_COLOR, document.getElementById('header-bg-color-picker').value); localStorage.setItem(LS_HEADER_BG_OPACITY, document.getElementById('header-bg-opacity-slider').value); // Save header text outline localStorage.setItem(LS_HEADER_TEXT_OUTLINE_COLOR, document.getElementById('header-text-outline-color-picker').value); localStorage.setItem(LS_HEADER_TEXT_OUTLINE_OPACITY, document.getElementById('header-text-outline-opacity-slider').value); localStorage.setItem(LS_HEADER_TEXT_OUTLINE_WIDTH, document.getElementById('header-text-outline-width-input').value); // Save header font style localStorage.setItem(LS_HEADER_TEXT_STYLE_ITALIC, document.getElementById('header-text-style-italic').checked); localStorage.setItem(LS_HEADER_TEXT_STYLE_BOLD, document.getElementById('header-text-style-bold').checked); } // --- Function to apply header background style --- function applyHeaderBgStyle() { const headerElement = document.getElementById('board-header'); if (!headerElement) return; const bgColor = localStorage.getItem(LS_HEADER_BG_COLOR) || DEFAULT_HEADER_BG_COLOR; const opacity = parseInt(localStorage.getItem(LS_HEADER_BG_OPACITY) || DEFAULT_HEADER_BG_OPACITY, 10); headerElement.style.backgroundColor = hexToRgba(bgColor, opacity); } // --- Function to update the header display --- function updateHeaderDisplay() { // Read text, applying default ONLY if the key is missing let headerText = localStorage.getItem(LS_HEADER_TEXT); if (headerText === null) { // Check if key exists headerText = DEFAULT_HEADER_TEXT; // Apply default only if key missing } // Read image URL: use stored value if key exists, otherwise use default let headerImageUrl = localStorage.getItem(LS_HEADER_IMAGE_URL); if (headerImageUrl === null) { // Check if key exists in localStorage headerImageUrl = DEFAULT_HEADER_IMAGE_URL; // Apply default ONLY if key is missing } const headerContainer = document.getElementById("custom-header-content"); if (!headerContainer) return; // Clear existing content headerContainer.innerHTML = ""; // Get default color and opacity let headerTextColor = localStorage.getItem(LS_HEADER_TEXT_COLOR); if (headerTextColor === null) { // Check if key exists before applying default headerTextColor = DEFAULT_HEADER_TEXT_COLOR; } let headerTextOpacity = localStorage.getItem(LS_HEADER_TEXT_COLOR_OPACITY); if (headerTextOpacity === null) { headerTextOpacity = DEFAULT_HEADER_TEXT_COLOR_OPACITY; } let headerStyleItalic = localStorage.getItem(LS_HEADER_TEXT_STYLE_ITALIC) === 'true'; // Convert string to boolean let headerStyleBold = localStorage.getItem(LS_HEADER_TEXT_STYLE_BOLD) === 'true'; // Convert string to boolean const rgbaDefaultColor = hexToRgba(headerTextColor, parseInt(headerTextOpacity, 10)); // Get outline settings let headerOutlineColor = localStorage.getItem(LS_HEADER_TEXT_OUTLINE_COLOR); if (headerOutlineColor === null) headerOutlineColor = DEFAULT_HEADER_TEXT_OUTLINE_COLOR; let headerOutlineOpacity = localStorage.getItem(LS_HEADER_TEXT_OUTLINE_OPACITY); if (headerOutlineOpacity === null) headerOutlineOpacity = DEFAULT_HEADER_TEXT_OUTLINE_OPACITY; let headerOutlineWidth = parseFloat(localStorage.getItem(LS_HEADER_TEXT_OUTLINE_WIDTH)); if (isNaN(headerOutlineWidth) || headerOutlineWidth < 0) { headerOutlineWidth = DEFAULT_HEADER_TEXT_OUTLINE_WIDTH; } let hasText = headerText && headerText.trim() !== ""; // Determine if we are currently using the default bean URL let isUsingDefaultBeanUrl = headerImageUrl === DEFAULT_HEADER_IMAGE_URL; // Determine if there is a custom image (non-empty URL that isn't the default bean) let hasCustomImage = headerImageUrl && headerImageUrl.trim() !== "" && !isUsingDefaultBeanUrl; // Add text if it exists if (hasText) { const h1 = document.createElement("h1"); // Remove default text-4xl and font-bold, apply font size/family/style/weight via style // h1.className = ""; // Get and apply font size let headerFontSize = parseInt(localStorage.getItem(LS_HEADER_TEXT_FONT_SIZE), 10); if (isNaN(headerFontSize) || headerFontSize <= 0) { headerFontSize = DEFAULT_HEADER_TEXT_FONT_SIZE; } h1.style.fontSize = `${headerFontSize}px`; // Apply font size // Get and apply font family const headerFontFamily = localStorage.getItem(LS_HEADER_TEXT_FONT_FAMILY) || DEFAULT_HEADER_TEXT_FONT_FAMILY; h1.style.fontFamily = headerFontFamily; // Apply font family // Apply font style and weight h1.style.fontStyle = headerStyleItalic ? 'italic' : 'normal'; h1.style.fontWeight = headerStyleBold ? 'bold' : 'normal'; h1.style.color = rgbaDefaultColor; // Apply default color+opacity to H1 // Apply outline to H1 if (headerOutlineWidth > 0) { const rgbaOutlineColor = hexToRgba(headerOutlineColor, parseInt(headerOutlineOpacity, 10)); const shadow = ` -${headerOutlineWidth}px -${headerOutlineWidth}px 0 ${rgbaOutlineColor}, ${headerOutlineWidth}px -${headerOutlineWidth}px 0 ${rgbaOutlineColor}, -${headerOutlineWidth}px ${headerOutlineWidth}px 0 ${rgbaOutlineColor}, ${headerOutlineWidth}px ${headerOutlineWidth}px 0 ${rgbaOutlineColor}, ${headerOutlineWidth}px 0 0 ${rgbaOutlineColor}, -${headerOutlineWidth}px 0 0 ${rgbaOutlineColor}, 0 ${headerOutlineWidth}px 0 ${rgbaOutlineColor}, 0 -${headerOutlineWidth}px 0 ${rgbaOutlineColor} `; h1.style.textShadow = shadow; } else { h1.style.textShadow = "none"; } // Split the header text by spaces, but keep the spaces as separate elements const parts = headerText.trim().split(/(\s+)/); parts.forEach(part => { if (!part) return; // Skip empty parts resulting from split if (part.match(/^\s+$/)) { // If the part is only whitespace, add it as a text node to preserve spacing h1.appendChild(document.createTextNode(part)); } else { // Check if the part matches the Word[color] pattern exactly // Regex: Start (^), Word (\S+?), literal \[, capture color ([^\\\]]+?), literal \], End ($) const tagMatch = part.match(/^(\S+?)\[([^\]]+?)\]$/); if (tagMatch) { const word = tagMatch[1]; const color = tagMatch[2].trim(); // Extract and trim color const span = document.createElement("span"); span.textContent = word; // Only the word part if (color) { // Check if color is not empty after trim span.style.color = color; // Apply specific color } // If color was empty or invalid, it inherits the default from h1 h1.appendChild(span); } else { // Part is just a regular word (or doesn't match the pattern) const span = document.createElement("span"); span.textContent = part; // Add the whole part as is // Inherits default color and outline from h1 h1.appendChild(span); } } }); headerContainer.appendChild(h1); } // Add custom image if URL exists and it's not the default bean URL if (hasCustomImage) { // create a div and an image element const div = document.createElement('div'); const img = document.createElement('img'); div.id = 'header-image-container'; img.id = 'header-image'; img.src = headerImageUrl; img.alt = "Custom Header Image"; img.className = "max-h-16 max-w-full object-contain"; // Adjust size as needed img.onerror = () => { img.remove(); // Remove broken image placeholder showNotification("Could not load custom header image.", "warning"); // No fallback logic here. If custom image fails, it's just gone. }; div.appendChild(img); headerContainer.appendChild(div); } // If there is NO text and NO custom image, AND we ended up using the default bean URL, add the default bean else if (!hasText && !hasCustomImage && isUsingDefaultBeanUrl) { addDefaultBean(headerContainer); } // Implicitly, if there IS text but NO custom image, nothing else is added here. // If the user set the URL to "", hasCustomImage will be false, and isUsingDefaultBeanUrl will be false, so nothing is added. } // Helper to add the default bean image function addDefaultBean(container) { // create a div and an image element const div = document.createElement('div'); const img = document.createElement('img'); div.id = 'bean-container'; img.id = 'bean'; img.src = '/bean.svg'; img.alt = 'Bean'; img.className = 'w-16 h-16 cursor-pointer'; // Use consistent size img.onclick = (event) => explodeBeans(event); div.appendChild(img); container.appendChild(div); } // --- Save current default cell style settings to localStorage --- function saveCellStyleSettings() { localStorage.setItem(LS_CELL_BORDER_COLOR, document.getElementById('cell-border-color-picker').value); localStorage.setItem(LS_CELL_BORDER_OPACITY, document.getElementById('cell-border-opacity-slider').value); localStorage.setItem(LS_CELL_BORDER_WIDTH, document.getElementById('cell-border-width-input').value); localStorage.setItem(LS_CELL_BG_COLOR, document.getElementById('cell-background-color-picker').value); localStorage.setItem(LS_CELL_BG_OPACITY, document.getElementById('cell-background-opacity-slider').value); localStorage.setItem(LS_CELL_BG_IMAGE_URL, document.getElementById('cell-background-image-url-input').value); localStorage.setItem(LS_CELL_BG_IMAGE_OPACITY, document.getElementById('cell-background-image-opacity-slider').value); // Save image opacity // Save text styles localStorage.setItem(LS_CELL_TEXT_COLOR, document.getElementById('cell-text-color-picker').value); localStorage.setItem(LS_CELL_TEXT_OPACITY, document.getElementById('cell-text-opacity-slider').value); localStorage.setItem(LS_CELL_OUTLINE_COLOR, document.getElementById('cell-outline-color-picker').value); localStorage.setItem(LS_CELL_OUTLINE_OPACITY, document.getElementById('cell-outline-opacity-slider').value); localStorage.setItem(LS_CELL_OUTLINE_WIDTH, document.getElementById('cell-outline-width-input').value); } // --- Apply saved default cell styles to a cell --- function applyCellStyle(cell) { if (!cell || cell.classList.contains('marked')) return; // Only apply to non-marked cells // Borders const borderColor = localStorage.getItem(LS_CELL_BORDER_COLOR) || DEFAULT_CELL_BORDER_COLOR; const borderOpacity = parseInt(localStorage.getItem(LS_CELL_BORDER_OPACITY) || DEFAULT_CELL_BORDER_OPACITY, 10); cell.style.borderColor = hexToRgba(borderColor, borderOpacity); // Apply border width let borderWidth = parseInt(localStorage.getItem(LS_CELL_BORDER_WIDTH) || DEFAULT_CELL_BORDER_WIDTH, 10); if (isNaN(borderWidth) || borderWidth < 0) { // Add check for NaN and negative borderWidth = DEFAULT_CELL_BORDER_WIDTH; } cell.style.borderWidth = `${borderWidth}px`; // Background Color / Image const bgColor = localStorage.getItem(LS_CELL_BG_COLOR) || DEFAULT_CELL_BG_COLOR; const bgOpacity = parseInt(localStorage.getItem(LS_CELL_BG_OPACITY) || DEFAULT_CELL_BG_OPACITY, 10); const bgImageUrl = localStorage.getItem(LS_CELL_BG_IMAGE_URL) || DEFAULT_CELL_BG_IMAGE_URL; const bgImageOpacity = parseInt(localStorage.getItem(LS_CELL_BG_IMAGE_OPACITY) || DEFAULT_CELL_BG_IMAGE_OPACITY, 10); cell.style.opacity = ''; // Reset direct opacity if (bgImageUrl) { cell.style.setProperty('--bg-image-url', `url('${bgImageUrl}')`); cell.style.setProperty('--bg-image-opacity', bgImageOpacity / 100); // Set image opacity property cell.style.backgroundColor = hexToRgba(bgColor, bgOpacity); } else { cell.style.removeProperty('--bg-image-url'); // NEW: Remove CSS custom property cell.style.removeProperty('--bg-image-opacity'); // Remove image opacity property cell.style.backgroundColor = hexToRgba(bgColor, bgOpacity); } // Text Styles const textSpan = cell.querySelector('.bingo-cell-text'); if (textSpan) { const textColor = localStorage.getItem(LS_CELL_TEXT_COLOR) || DEFAULT_CELL_TEXT_COLOR; const textOpacity = parseInt(localStorage.getItem(LS_CELL_TEXT_OPACITY) || DEFAULT_CELL_TEXT_OPACITY, 10); const outlineColor = localStorage.getItem(LS_CELL_OUTLINE_COLOR) || DEFAULT_CELL_OUTLINE_COLOR; const outlineOpacity = parseInt(localStorage.getItem(LS_CELL_OUTLINE_OPACITY) || DEFAULT_CELL_OUTLINE_OPACITY, 10); let outlineWidth = parseFloat(localStorage.getItem(LS_CELL_OUTLINE_WIDTH)); if (isNaN(outlineWidth) || outlineWidth < 0) outlineWidth = DEFAULT_CELL_OUTLINE_WIDTH; const rgbaTextColor = hexToRgba(textColor, textOpacity); textSpan.style.color = rgbaTextColor; textSpan.style.setProperty('--cell-text-color', rgbaTextColor); if (outlineWidth > 0) { const rgbaOutlineColor = hexToRgba(outlineColor, outlineOpacity); // Create outline using multiple text shadows const shadow = ` -${outlineWidth}px -${outlineWidth}px 0 ${rgbaOutlineColor}, ${outlineWidth}px -${outlineWidth}px 0 ${rgbaOutlineColor}, -${outlineWidth}px ${outlineWidth}px 0 ${rgbaOutlineColor}, ${outlineWidth}px ${outlineWidth}px 0 ${rgbaOutlineColor}, ${outlineWidth}px 0 0 ${rgbaOutlineColor}, -${outlineWidth}px 0 0 ${rgbaOutlineColor}, 0 ${outlineWidth}px 0 ${rgbaOutlineColor}, 0 -${outlineWidth}px 0 ${rgbaOutlineColor} `; textSpan.style.textShadow = shadow; textSpan.style.setProperty('--cell-text-outline', shadow); } else { textSpan.style.textShadow = 'none'; textSpan.style.removeProperty('--cell-text-outline'); } } } // --- Re-apply default styles to all non-marked cells --- function refreshCellStyles() { const cells = document.querySelectorAll('#bingo-board .bingo-cell:not(.marked)'); cells.forEach(cell => { applyCellStyle(cell); // Re-apply based on current settings }); } // --- Save board background settings to localStorage --- function saveBoardBgSettings() { localStorage.setItem(LS_BOARD_BG_COLOR, document.getElementById('board-bg-color-picker').value); localStorage.setItem(LS_BOARD_BG_IMAGE_URL, document.getElementById('board-bg-image-url-input').value); localStorage.setItem(LS_BOARD_BG_COLOR_OPACITY, document.getElementById('board-bg-color-opacity-slider').value); // Use new ID & key } // --- Apply board background style --- function applyBoardBgStyle() { const container = document.getElementById('bingo-board-container'); if (!container) return; const bgColor = localStorage.getItem(LS_BOARD_BG_COLOR) || DEFAULT_BOARD_BG_COLOR; const bgImageUrl = localStorage.getItem(LS_BOARD_BG_IMAGE_URL) || DEFAULT_BOARD_BG_IMAGE_URL; const opacityValue = parseInt(localStorage.getItem(LS_BOARD_BG_COLOR_OPACITY) || DEFAULT_BOARD_BG_COLOR_OPACITY, 10); // Apply opacity - REMOVED direct opacity setting // container.style.opacity = opacityValue; // Apply background image or color if (bgImageUrl) { container.style.backgroundImage = `url('${bgImageUrl}')`; container.style.backgroundSize = 'cover'; container.style.backgroundPosition = 'center center'; container.style.backgroundRepeat = 'no-repeat'; // Apply background color with its opacity as fallback / see-through container.style.backgroundColor = hexToRgba(bgColor, opacityValue); } else { container.style.backgroundImage = 'none'; container.style.backgroundColor = hexToRgba(bgColor, opacityValue); } } // 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(); const originalUserItems = getItemsFromInput(); // Get current text from textarea localStorage.setItem(LS_BOARD_SIZE, size); localStorage.setItem(LS_CELL_ITEMS, JSON.stringify(currentItems)); // Save items potentially on board (padded/sliced) localStorage.setItem(LS_DISPLAYED_ITEMS, JSON.stringify(displayedItems)); // Save displayed items localStorage.setItem(LS_MARKED_INDICES, JSON.stringify(markedIndices)); localStorage.setItem(LS_ORIGINAL_ITEMS, JSON.stringify(originalUserItems)); // Save user's raw input from textarea saveBackgroundSettings(); // Save background settings from inputs saveHeaderSettings(); // Save header settings from inputs saveMarkedStyleSettings(); // Save marked cell style settings saveCellStyleSettings(); // Save default cell style settings saveBoardBgSettings(); // Save board background settings } 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 // Get items from input and store them as the canonical list const originalUserItems = getItemsFromInput(); // localStorage.setItem(LS_ORIGINAL_ITEMS, JSON.stringify(originalUserItems)); // Moved to saveBoardState const requiredItems = size * size; let itemsForBoard = [...originalUserItems]; // Start with a copy of the user's raw input 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}`); } // currentItems will now hold the padded list for the board currentItems = [...itemsForBoard]; } else if (itemsForBoard.length > requiredItems) { notificationMessage = `Info: Found ${itemsForBoard.length} items, randomly selecting ${requiredItems}.`; notificationType = 'info'; itemsForBoard = shuffleArray(itemsForBoard).slice(0, requiredItems); // currentItems will now hold the selected subset for the board currentItems = [...itemsForBoard]; } else { // If exact number, currentItems are the same as originalUserItems currentItems = [...originalUserItems]; } // Save the items that are actually available for the board (potentially padded/sliced) // localStorage.setItem(LS_CELL_ITEMS, JSON.stringify(currentItems)); // Moved to saveBoardState // Shuffle the items specifically for display (use currentItems which has the right size) displayedItems = shuffleArray([...currentItems]); const board = document.getElementById("bingo-board"); board.innerHTML = ""; // Clear previous board board.style.gridTemplateColumns = `repeat(${size}, minmax(0, 1fr))`; displayedItems.forEach((item, index) => { const cell = document.createElement("div"); cell.classList.add("bingo-cell", "cursor-pointer"); // cell.textContent = item; // OLD WAY cell.dataset.index = index; // Add index for saving marks cell.onclick = () => selectCell(cell); applyCellStyle(cell); // Apply default styles upon creation // NEW: Create span for text content const textSpan = document.createElement("span"); textSpan.classList.add("bingo-cell-text"); textSpan.textContent = item; cell.appendChild(textSpan); board.appendChild(cell); }); clearMarks(false); // Clear previous marks visually AND their styles, but don't save yet equalizeCellSizes(); // Add this call // saveBoardState(); // REMOVED first call - Save the newly generated board state (size, items, displayed, marks) // Explicitly save background settings before reload - NO, save everything together // saveBackgroundSettings(); // Get the new background value and inject a style override before reload const newBackgroundValue = setBackground(); // Apply and get value let overrideStyle = document.getElementById('background-override-style'); if (!overrideStyle) { overrideStyle = document.createElement('style'); overrideStyle.id = 'background-override-style'; document.head.appendChild(overrideStyle); } // Use !important to try and force application during reload flash overrideStyle.textContent = `html { background: ${newBackgroundValue} !important; background-repeat: no-repeat !important; background-attachment: fixed !important; }`; showNotification(notificationMessage, notificationType); // *** Save the complete final state just before reload *** saveBoardState(); // Includes size, items, displayed, marks, original items, background, header, marked style // Reload the page to ensure correct rendering based on saved state location.reload(); } 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]; // OLD WAY // NEW: Update or create text span let textSpan = cell.querySelector(".bingo-cell-text"); if (!textSpan) { // Should exist, but create if missing textSpan = document.createElement("span"); textSpan.classList.add("bingo-cell-text"); cell.innerHTML = cell.appendChild(textSpan); } textSpan.textContent = displayedItems[index]; cell.classList.remove("marked"); // Clear marks visually applyMarkedCellStyle(cell); // Reset marked styles (which also calls applyCellStyle) }); // Explicitly remove any existing highlights BEFORE clearing the search input const currentlyHighlighted = board.querySelectorAll('.bingo-cell.highlighted'); currentlyHighlighted.forEach(cell => cell.classList.remove('highlighted')); clearSearch(); // Clear search highlights and input clearMarks(false); // Clear visual marks saveBoardState(); // Save the new randomized state (including cleared marks) showNotification('Board randomized!', 'success'); } function selectCell(cell) { cell.classList.toggle("marked"); // Toggle the dedicated 'marked' class applyMarkedCellStyle(cell); // Apply/remove styles based on new state and settings saveBoardState(); // RE-ADD: Save updated marks immediately after click } function clearMarks(save = true) { const cells = document.querySelectorAll('#bingo-board .bingo-cell'); cells.forEach(cell => { if (cell.classList.contains('marked')) { cell.classList.remove('marked'); // applyMarkedCellStyle(cell); // Redundant call removed // Apply default style ONLY to the cell just unmarked applyCellStyle(cell); } // Ensure styles are reset even if class was somehow missing // applyCellStyle(cell); // REMOVED Unconditional call }); 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 ONLY board content/structure settings --- function resetSettings() { // Clear localStorage relevant ONLY to the board structure/content localStorage.removeItem(LS_BOARD_SIZE); localStorage.removeItem(LS_CELL_ITEMS); // The items configured for the board (padded/sliced) localStorage.removeItem(LS_DISPLAYED_ITEMS); // The current layout localStorage.removeItem(LS_MARKED_INDICES); localStorage.removeItem(LS_ORIGINAL_ITEMS); // User's raw input // Reset global variables for board content currentItems = []; displayedItems = []; // Reset ONLY board-related form inputs document.getElementById('board-size').value = 5; // Default size document.getElementById('cell-contents').value = ''; document.getElementById('file-input').value = ''; // Clear file input // DO NOT reset background inputs // DO NOT reset header inputs // DO NOT reset marked style inputs // Clear the board display const board = document.getElementById('bingo-board'); board.innerHTML = '