// Tablet-Optimized Crossword Game class TabletCrosswordGame { constructor() { this.currentLevel = 1 this.coins = 100 this.hints = 3 this.currentPuzzle = null this.userAnswers = {} this.selectedCell = null this.gridElement = document.getElementById('crosswordGrid') this.init() } init() { this.loadPuzzle(this.currentLevel) this.setupEventListeners() this.updateUI() } setupEventListeners() { document.getElementById('menuBtn').addEventListener('click', () => { document.getElementById('sideMenu').classList.add('active') }) document.getElementById('closeMenu').addEventListener('click', () => { document.getElementById('sideMenu').classList.remove('active') }) document.getElementById('checkBtn').addEventListener('click', () => this.checkAnswers()) document.getElementById('clearBtn').addEventListener('click', () => this.clearGrid()) document.getElementById('hintBtn').addEventListener('click', () => this.showHintModal()) document.getElementById('skipBtn').addEventListener('click', () => this.skipPuzzle()) document.getElementById('useHintBtn').addEventListener('click', () => this.useHint()) document.getElementById('cancelHintBtn').addEventListener('click', () => this.hideHintModal()) document.getElementById('nextBtn').addEventListener('click', () => this.nextPuzzle()) document.getElementById('dailyPuzzle').addEventListener('click', () => { this.showFeedback('Dagelijkse puzzel komt binnenkort!') document.getElementById('sideMenu').classList.remove('active') }) document.getElementById('levelSelect').addEventListener('click', () => { this.showFeedback('Niveau selectie komt binnenkort!') document.getElementById('sideMenu').classList.remove('active') }) } loadPuzzle(level) { if (typeof DUTCH_PUZZLES !== 'undefined') { this.currentPuzzle = DUTCH_PUZZLES[`level${level}`] || DUTCH_PUZZLES.level1 } this.userAnswers = {} this.renderGrid() this.updateProgress() this.updateUI() } renderGrid() { if (!this.currentPuzzle) return const grid = this.currentPuzzle.grid const words = this.currentPuzzle.words this.gridElement.innerHTML = '' const rows = grid.length const cols = Math.max(...grid.map(row => row.length)) + 1 // Tablet-specific calculation - fill available space const cellSize = this.calculateCellSize(rows, cols) this.gridElement.style.gridTemplateColumns = `repeat(${cols}, ${cellSize}px)` this.gridElement.style.gridTemplateRows = `repeat(${rows}, ${cellSize}px)` // Create grid cells for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { if (col === 0) { const clueData = this.getClueForRow(row, words) this.gridElement.appendChild(this.createClueCell(clueData, cellSize)) } else { const gridCol = col - 1 const cellValue = grid[row]?.[gridCol] || '#' this.gridElement.appendChild(this.createCell(cellValue, row, gridCol, words, cellSize)) } } } this.attachCellListeners() } calculateCellSize(rows, cols) { // Tablet layout: sidebar is 180px, header is ~70px, progress is ~50px, padding is 30px each side const headerHeight = 70 const progressHeight = 50 const sidebarWidth = 180 const padding = 60 // Total padding const availableHeight = window.innerHeight - headerHeight - progressHeight - padding const availableWidth = window.innerWidth - sidebarWidth - padding const maxFromHeight = Math.floor(availableHeight / rows) const maxFromWidth = Math.floor(availableWidth / cols) let cellSize = Math.min(maxFromHeight, maxFromWidth) // Tablet range: 45-80px for better fit and touch cellSize = Math.max(45, Math.min(80, cellSize)) console.log('Tablet grid:', { screenWidth: window.innerWidth, screenHeight: window.innerHeight, availableHeight, availableWidth, rows, cols, cellSize, totalGridHeight: rows * cellSize, totalGridWidth: cols * cellSize }) return cellSize } getClueForRow(row, words) { return words.find(w => w.direction === 'horizontal' && w.startRow === row) } createClueCell(clueData, cellSize) { const cell = document.createElement('div') cell.className = 'grid-cell clue-cell' cell.style.width = `${cellSize}px` cell.style.height = `${cellSize}px` if (clueData) { const text = document.createElement('div') text.className = 'clue-text' text.textContent = clueData.clue const arrow = document.createElement('div') arrow.className = 'arrow arrow-right' arrow.textContent = '→' cell.appendChild(text) cell.appendChild(arrow) } else { cell.classList.add('blocked-cell') } return cell } createCell(value, row, col, words, cellSize) { const cell = document.createElement('div') cell.className = 'grid-cell' cell.style.width = `${cellSize}px` cell.style.height = `${cellSize}px` cell.dataset.row = row cell.dataset.col = col if (value === '#') { const clueData = this.getClueAtPosition(row, col, words) if (clueData) { cell.classList.add('clue-cell') const text = document.createElement('div') text.className = 'clue-text' text.textContent = clueData.clue const arrow = document.createElement('div') arrow.className = 'arrow arrow-right' arrow.textContent = '→' cell.appendChild(text) cell.appendChild(arrow) } else { cell.classList.add('blocked-cell') } } else { cell.classList.add('input-cell') const input = document.createElement('input') input.type = 'text' input.maxLength = 1 input.dataset.row = row input.dataset.col = col const fontSize = Math.max(20, Math.min(36, cellSize * 0.5)) input.style.fontSize = `${fontSize}px` const key = `${row}-${col}` if (this.userAnswers[key]) { input.value = this.userAnswers[key] } cell.appendChild(input) } return cell } getClueAtPosition(row, col, words) { for (const word of words) { if (word.direction === 'horizontal' && word.startRow === row && word.startCol - 1 === col) { return word } } return null } attachCellListeners() { const inputs = this.gridElement.querySelectorAll('input') inputs.forEach(input => { input.addEventListener('input', (e) => this.handleInput(e)) input.addEventListener('focus', (e) => this.selectCell(e.target)) input.addEventListener('click', (e) => this.selectCell(e.target)) }) // Keyboard navigation document.addEventListener('keydown', (e) => this.handleKeyboard(e)) } handleInput(e) { const input = e.target const value = input.value.toUpperCase() input.value = value const row = parseInt(input.dataset.row) const col = parseInt(input.dataset.col) const key = `${row}-${col}` this.userAnswers[key] = value if (value && value.length === 1) { this.moveToNextCell(input) } this.updateProgress() } selectCell(input) { document.querySelectorAll('.grid-cell.input-cell').forEach(cell => { cell.classList.remove('active') }) input.parentElement.classList.add('active') this.selectedCell = input } moveToNextCell(currentInput) { const inputs = Array.from(this.gridElement.querySelectorAll('input')) const currentIndex = inputs.indexOf(currentInput) if (currentIndex !== -1 && currentIndex < inputs.length - 1) { const nextInput = inputs[currentIndex + 1] nextInput.focus() } } handleKeyboard(e) { if (!this.selectedCell) return const inputs = Array.from(this.gridElement.querySelectorAll('input')) const currentIndex = inputs.indexOf(this.selectedCell) if (currentIndex === -1) return switch (e.key) { case 'ArrowRight': if (currentIndex < inputs.length - 1) { inputs[currentIndex + 1].focus() } e.preventDefault() break case 'ArrowLeft': if (currentIndex > 0) { inputs[currentIndex - 1].focus() } e.preventDefault() break case 'Backspace': if (!this.selectedCell.value && currentIndex > 0) { inputs[currentIndex - 1].focus() } break } } checkAnswers() { let correct = 0 let total = 0 const inputs = this.gridElement.querySelectorAll('input') inputs.forEach(input => { const row = parseInt(input.dataset.row) const col = parseInt(input.dataset.col) const userAnswer = input.value.toUpperCase() const correctAnswer = this.getCorrectAnswer(row, col) if (correctAnswer) { total++ const cell = input.parentElement if (userAnswer === correctAnswer) { correct++ cell.classList.add('correct') cell.classList.remove('incorrect') } else if (userAnswer) { cell.classList.add('incorrect') cell.classList.remove('correct') } } }) const percentage = total > 0 ? Math.round((correct / total) * 100) : 0 if (percentage === 100) { this.showSuccess() } else { this.showFeedback(`${correct}/${total} correct (${percentage}%)`) } } getCorrectAnswer(row, col) { for (const word of this.currentPuzzle.words) { if (word.direction === 'horizontal') { if (word.startRow === row && col >= word.startCol && col < word.startCol + word.answer.length) { return word.answer[col - word.startCol] } } else if (word.direction === 'vertical') { if (word.startCol === col && row >= word.startRow && row < word.startRow + word.answer.length) { return word.answer[row - word.startRow] } } } return null } clearGrid() { if (confirm('Alles wissen?')) { this.userAnswers = {} const inputs = this.gridElement.querySelectorAll('input') inputs.forEach(input => { input.value = '' input.parentElement.classList.remove('correct', 'incorrect') }) this.updateProgress() this.showFeedback('Gewist') } } showHintModal() { if (this.hints <= 0) { this.showFeedback('Geen hints meer!') return } document.getElementById('hintModal').classList.add('active') } hideHintModal() { document.getElementById('hintModal').classList.remove('active') } useHint() { if (this.hints <= 0) return const inputs = Array.from(this.gridElement.querySelectorAll('input')) const emptyInputs = inputs.filter(input => !input.value) if (emptyInputs.length === 0) { this.showFeedback('Alles al ingevuld!') this.hideHintModal() return } const randomInput = emptyInputs[Math.floor(Math.random() * emptyInputs.length)] const row = parseInt(randomInput.dataset.row) const col = parseInt(randomInput.dataset.col) const correctAnswer = this.getCorrectAnswer(row, col) if (correctAnswer) { randomInput.value = correctAnswer this.userAnswers[`${row}-${col}`] = correctAnswer this.hints-- this.updateUI() this.updateProgress() this.showFeedback('Letter onthuld!') this.hideHintModal() } } showSuccess() { this.coins += 50 this.hints += 1 this.updateUI() document.getElementById('successModal').classList.add('active') } nextPuzzle() { document.getElementById('successModal').classList.remove('active') this.currentLevel++ this.loadPuzzle(this.currentLevel) this.showFeedback(`Niveau ${this.currentLevel}`) } skipPuzzle() { if (confirm('Overslaan?')) { this.currentLevel++ this.loadPuzzle(this.currentLevel) this.showFeedback('Overgeslagen') } } updateProgress() { const inputs = this.gridElement.querySelectorAll('input') let filled = 0 let total = 0 inputs.forEach(input => { total++ if (input.value) filled++ }) const progress = total > 0 ? Math.round((filled / total) * 100) : 0 document.getElementById('progressFill').style.width = `${progress}%` document.getElementById('progressText').textContent = `${progress}%` } updateUI() { document.getElementById('coinsCount').textContent = this.coins document.getElementById('hintsCount').textContent = this.hints document.getElementById('levelNum').textContent = this.currentLevel } showFeedback(message) { const feedback = document.createElement('div') feedback.style.cssText = ` position: fixed; top: 90px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.85); color: white; padding: 12px 24px; border-radius: 24px; font-size: 16px; font-weight: 600; z-index: 9999; animation: fadeInOut 2.5s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.3); ` feedback.textContent = message document.body.appendChild(feedback) setTimeout(() => { feedback.remove() }, 2500) } } // Animation const style = document.createElement('style') style.textContent = ` @keyframes fadeInOut { 0% { opacity: 0; transform: translateX(-50%) translateY(-10px); } 15% { opacity: 1; transform: translateX(-50%) translateY(0); } 85% { opacity: 1; transform: translateX(-50%) translateY(0); } 100% { opacity: 0; transform: translateX(-50%) translateY(-10px); } } ` document.head.appendChild(style) // Initialize document.addEventListener('DOMContentLoaded', () => { new TabletCrosswordGame() })