diff --git a/public/redirect2.js b/public/redirect2.js index 972cb35..ae50a1c 100644 --- a/public/redirect2.js +++ b/public/redirect2.js @@ -28,8 +28,8 @@ // Small screens: force mobile targetPage = 'mobile.html' } else if (isTabletDevice || (width >= 500 && width < 1200)) { - // Tablets: iPad/Android tablet OR 500-1200px - targetPage = 'index.html' + // Tablets: iPad/Android tablet OR 500-1200px - use full app + targetPage = 'tablet-app.html' } else { // Desktop: >= 1200px targetPage = 'index.html' diff --git a/public/tablet-app.css b/public/tablet-app.css new file mode 100644 index 0000000..5d3dc9e --- /dev/null +++ b/public/tablet-app.css @@ -0,0 +1,471 @@ +/* Full-Screen Tablet App */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; +} + +html, body { + height: 100%; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; + background: #1a202c; + touch-action: manipulation; + user-select: none; +} + +.app-fullscreen { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + overflow: hidden; +} + +/* Header */ +.app-header { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + padding: 10px 15px; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); + flex-shrink: 0; +} + +.icon-btn { + background: rgba(255,255,255,0.2); + border: none; + color: white; + width: 44px; + height: 44px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + cursor: pointer; +} + +.header-info { + flex: 1; + text-align: center; +} + +.level-text { + color: white; + font-size: 18px; + font-weight: 700; +} + +.header-stats { + display: flex; + gap: 10px; +} + +.stat-item { + background: rgba(255,255,255,0.2); + padding: 8px 12px; + border-radius: 20px; + color: white; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; +} + +/* Progress Bar */ +.progress-bar-wrapper { + background: rgba(255,255,255,0.95); + padding: 8px 15px; + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + +.progress-bar { + flex: 1; + height: 8px; + background: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #48bb78, #38a169); + width: 0%; + transition: width 0.3s ease; +} + +.progress-text { + font-size: 14px; + font-weight: 600; + color: #4a5568; + min-width: 50px; +} + +/* Grid Container */ +.grid-container-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 15px; + overflow: hidden; + background: rgba(255,255,255,0.95); +} + +.crossword-grid { + display: grid; + background: #a0aec0; + padding: 6px; + border-radius: 10px; + gap: 2px; + box-shadow: 0 4px 15px rgba(0,0,0,0.15); +} + +/* Grid Cells */ +.grid-cell { + background: white; + border: 2px solid #cbd5e0; + display: flex; + align-items: center; + justify-content: center; + position: relative; + font-weight: bold; + transition: all 0.2s; + cursor: pointer; +} + +.grid-cell.clue-cell { + background: linear-gradient(135deg, #4a5568, #2d3748); + color: white; + padding: 4px; + font-size: 10px; + font-weight: 600; + line-height: 1.2; + text-align: left; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + cursor: default; + pointer-events: none; +} + +.clue-text { + font-size: 10px; + line-height: 1.2; + word-wrap: break-word; + width: 100%; +} + +.arrow { + position: absolute; + color: #4299e1; + font-weight: bold; + font-size: 18px; + right: 4px; + top: 50%; + transform: translateY(-50%); +} + +.grid-cell.blocked-cell { + background: #2d3748; + border-color: #2d3748; + cursor: default; +} + +.grid-cell.letter-cell { + background: white; + font-size: 28px; + color: #2d3748; + cursor: pointer; +} + +.grid-cell.letter-cell:hover { + background: #f7fafc; + border-color: #4299e1; +} + +.grid-cell.letter-cell.active { + background: #ebf8ff; + border-color: #3182ce; + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.4); +} + +.grid-cell.letter-cell.highlighted { + background: #fef5e7; + border-color: #f39c12; +} + +.grid-cell.letter-cell.correct { + background: #c6f6d5; + border-color: #48bb78; + animation: correctPulse 0.5s ease; +} + +.grid-cell.letter-cell.incorrect { + background: #fed7d7; + border-color: #f56565; + animation: shake 0.4s ease; +} + +@keyframes correctPulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-5px); } + 75% { transform: translateX(5px); } +} + +@keyframes wordComplete { + 0% { box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7); } + 100% { box-shadow: 0 0 0 20px rgba(72, 187, 120, 0); } +} + +.word-complete { + animation: wordComplete 0.8s ease; +} + +/* On-Screen Keyboard */ +.keyboard-area { + background: #2d3748; + padding: 12px 10px 10px 10px; + flex-shrink: 0; + box-shadow: 0 -2px 10px rgba(0,0,0,0.2); +} + +.keyboard-row { + display: flex; + justify-content: center; + gap: 6px; + margin-bottom: 8px; +} + +.key-btn { + background: linear-gradient(135deg, #4a5568, #2d3748); + border: none; + color: white; + min-width: 50px; + height: 52px; + border-radius: 8px; + font-size: 20px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + display: flex; + align-items: center; + justify-content: center; +} + +.key-btn:active { + transform: scale(0.95); + background: linear-gradient(135deg, #667eea, #764ba2); +} + +.key-btn.key-action { + background: linear-gradient(135deg, #ed8936, #dd6b20); + min-width: 60px; +} + +.key-btn.key-del { + background: linear-gradient(135deg, #fc8181, #f56565); + min-width: 60px; +} + +.keyboard-actions { + display: flex; + gap: 8px; + margin-top: 10px; + justify-content: center; +} + +.action-btn { + background: linear-gradient(135deg, #48bb78, #38a169); + border: none; + color: white; + padding: 14px 28px; + border-radius: 10px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 3px 8px rgba(0,0,0,0.2); + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s; +} + +.action-btn:active { + transform: scale(0.97); +} + +.action-btn.secondary { + background: linear-gradient(135deg, #667eea, #764ba2); +} + +/* Side Menu */ +.side-menu { + position: fixed; + top: 0; + left: -300px; + width: 300px; + height: 100vh; + background: white; + box-shadow: 2px 0 15px rgba(0,0,0,0.3); + transition: left 0.3s ease; + z-index: 1000; +} + +.side-menu.active { + left: 0; +} + +.menu-header { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + color: white; + padding: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.menu-header h3 { + font-size: 20px; +} + +.close-menu { + background: none; + border: none; + color: white; + font-size: 24px; + cursor: pointer; +} + +.menu-items { + padding: 15px 0; +} + +.menu-item { + display: flex; + align-items: center; + gap: 15px; + padding: 18px 20px; + border: none; + background: none; + width: 100%; + text-align: left; + cursor: pointer; + font-size: 16px; + transition: background 0.2s; +} + +.menu-item:active { + background: #f8f9fa; +} + +.menu-item i { + width: 24px; + color: #4facfe; + font-size: 20px; +} + +/* Modal */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.7); + z-index: 2000; + justify-content: center; + align-items: center; +} + +.modal.active { + display: flex; +} + +.modal-content { + background: white; + border-radius: 20px; + padding: 40px; + text-align: center; + max-width: 450px; + width: 90%; + box-shadow: 0 10px 40px rgba(0,0,0,0.4); +} + +.success-icon { + font-size: 80px; + color: #48bb78; + margin-bottom: 20px; + animation: bounce 1s ease; +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-20px); } + 60% { transform: translateY(-10px); } +} + +.modal-content h2 { + color: #2d3748; + margin-bottom: 12px; + font-size: 28px; +} + +.modal-content p { + color: #4a5568; + margin-bottom: 24px; + font-size: 18px; +} + +.btn-primary { + background: linear-gradient(135deg, #48bb78, #38a169); + color: white; + border: none; + border-radius: 12px; + padding: 16px 32px; + cursor: pointer; + font-weight: 600; + font-size: 18px; + box-shadow: 0 4px 12px rgba(72, 187, 120, 0.4); +} + +.btn-primary:active { + transform: scale(0.98); +} + +/* Toast Notification */ +.toast { + position: fixed; + top: 80px; + left: 50%; + transform: translateX(-50%); + background: rgba(0,0,0,0.9); + color: white; + padding: 14px 28px; + border-radius: 25px; + font-size: 16px; + font-weight: 600; + z-index: 9999; + animation: toastFade 2.5s ease; + box-shadow: 0 4px 15px rgba(0,0,0,0.3); +} + +@keyframes toastFade { + 0%, 100% { opacity: 0; transform: translateX(-50%) translateY(-10px); } + 15%, 85% { opacity: 1; transform: translateX(-50%) translateY(0); } +} diff --git a/public/tablet-app.html b/public/tablet-app.html new file mode 100644 index 0000000..49e9cb1 --- /dev/null +++ b/public/tablet-app.html @@ -0,0 +1,138 @@ + + + + + + + + + Kruiswoord Pro + + + + + +
+ +
+ +
+ Niveau 1 +
+
+
+ + 100 +
+
+ + 3 +
+
+
+ + +
+
+
+
+ 0% +
+ + +
+
+ +
+
+ + +
+
+ + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ + +
+
+ + +
+ + +
+ + + +
+ + + + + diff --git a/public/tablet-app.js b/public/tablet-app.js new file mode 100644 index 0000000..e55a441 --- /dev/null +++ b/public/tablet-app.js @@ -0,0 +1,657 @@ +// Full-Featured Tablet Crossword App +class TabletCrosswordApp { + constructor() { + this.currentLevel = 1 + this.coins = 100 + this.hints = 3 + this.currentPuzzle = null + this.userAnswers = {} + this.selectedCell = null + this.selectedWord = null + this.gridElement = document.getElementById('crosswordGrid') + this.cells = [] + + this.init() + } + + init() { + this.loadGameState() + this.loadPuzzle(this.currentLevel) + this.setupEventListeners() + this.updateUI() + this.enableFullscreen() + } + + enableFullscreen() { + // Request fullscreen on first interaction + document.addEventListener('click', () => { + if (document.documentElement.requestFullscreen && !document.fullscreenElement) { + document.documentElement.requestFullscreen().catch(() => {}) + } + }, { once: true }) + } + + setupEventListeners() { + // Menu + document.getElementById('menuBtn').addEventListener('click', () => { + document.getElementById('sideMenu').classList.add('active') + }) + document.getElementById('closeMenu').addEventListener('click', () => { + document.getElementById('sideMenu').classList.remove('active') + }) + + // On-screen keyboard + document.querySelectorAll('.key-btn').forEach(btn => { + if (!btn.id) { + btn.addEventListener('click', () => { + const letter = btn.textContent.trim() + this.handleLetterInput(letter) + }) + } + }) + + document.getElementById('delKey').addEventListener('click', () => this.handleDelete()) + document.getElementById('hintKey').addEventListener('click', () => this.useHintForCurrentWord()) + + // Actions + document.getElementById('checkBtn').addEventListener('click', () => this.checkAllAnswers()) + document.getElementById('clearBtn').addEventListener('click', () => this.clearGrid()) + + // Menu items + document.getElementById('newGame').addEventListener('click', () => this.newGame()) + document.getElementById('dailyPuzzle').addEventListener('click', () => { + this.showToast('Dagelijkse puzzel komt binnenkort!') + document.getElementById('sideMenu').classList.remove('active') + }) + document.getElementById('levelSelect').addEventListener('click', () => { + this.showToast('Niveau selectie komt binnenkort!') + document.getElementById('sideMenu').classList.remove('active') + }) + + document.getElementById('nextBtn').addEventListener('click', () => this.nextPuzzle()) + + // Prevent accidental zoom/scroll + document.addEventListener('gesturestart', e => e.preventDefault()) + document.addEventListener('gesturechange', e => e.preventDefault()) + } + + loadPuzzle(level) { + if (typeof DUTCH_PUZZLES !== 'undefined') { + this.currentPuzzle = DUTCH_PUZZLES[`level${level}`] || DUTCH_PUZZLES.level1 + } + + this.renderGrid() + this.updateProgress() + this.updateUI() + } + + renderGrid() { + if (!this.currentPuzzle) return + + const grid = this.currentPuzzle.grid + const words = this.currentPuzzle.words + + this.gridElement.innerHTML = '' + this.cells = [] + + const rows = grid.length + const cols = Math.max(...grid.map(row => row.length)) + 1 + const cellSize = this.calculateCellSize(rows, cols) + + this.gridElement.style.gridTemplateColumns = `repeat(${cols}, ${cellSize}px)` + this.gridElement.style.gridTemplateRows = `repeat(${rows}, ${cellSize}px)` + + // Create 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] || '#' + const cell = this.createCell(cellValue, row, gridCol, words, cellSize) + this.gridElement.appendChild(cell) + + if (cell.classList.contains('letter-cell')) { + this.cells.push({ element: cell, row, col: gridCol }) + } + } + } + } + } + + calculateCellSize(rows, cols) { + const headerHeight = 66 + const progressHeight = 40 + const keyboardHeight = 230 + const padding = 30 + + const availableHeight = window.innerHeight - headerHeight - progressHeight - keyboardHeight - padding + const availableWidth = window.innerWidth - padding + + const maxFromHeight = Math.floor(availableHeight / rows) + const maxFromWidth = Math.floor(availableWidth / cols) + + let cellSize = Math.min(maxFromHeight, maxFromWidth) + cellSize = Math.max(50, Math.min(75, 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.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.textContent = '→' + cell.appendChild(text) + cell.appendChild(arrow) + } else { + cell.classList.add('blocked-cell') + } + } else { + cell.classList.add('letter-cell') + const key = `${row}-${col}` + cell.textContent = this.userAnswers[key] || '' + + // Click to select and highlight word + cell.addEventListener('click', () => { + this.selectCell(cell, row, col) + this.highlightWord(row, col) + }) + } + + 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 + } + + selectCell(cell, row, col) { + this.cells.forEach(c => c.element.classList.remove('active')) + cell.classList.add('active') + this.selectedCell = { element: cell, row, col } + } + + highlightWord(row, col) { + // Clear previous highlights + this.cells.forEach(c => c.element.classList.remove('highlighted')) + + // Find word containing this cell + const word = this.findWordContainingCell(row, col) + if (!word) return + + this.selectedWord = word + + // Highlight all cells in this word + if (word.direction === 'horizontal') { + for (let c = word.startCol; c < word.startCol + word.answer.length; c++) { + const cellData = this.cells.find(cell => cell.row === word.startRow && cell.col === c) + if (cellData) { + cellData.element.classList.add('highlighted') + } + } + } else { + for (let r = word.startRow; r < word.startRow + word.answer.length; r++) { + const cellData = this.cells.find(cell => cell.row === r && cell.col === word.startCol) + if (cellData) { + cellData.element.classList.add('highlighted') + } + } + } + } + + findWordContainingCell(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 + } + } else if (word.direction === 'vertical') { + if (word.startCol === col && row >= word.startRow && row < word.startRow + word.answer.length) { + return word + } + } + } + return null + } + + handleLetterInput(letter) { + if (!this.selectedCell) { + // Auto-select first empty cell + const firstEmpty = this.cells.find(c => !c.element.textContent) + if (firstEmpty) { + this.selectCell(firstEmpty.element, firstEmpty.row, firstEmpty.col) + this.highlightWord(firstEmpty.row, firstEmpty.col) + } else { + return + } + } + + const { element, row, col } = this.selectedCell + element.textContent = letter + const key = `${row}-${col}` + this.userAnswers[key] = letter + + this.saveGameState() + this.updateProgress() + + // Check if word is complete + this.checkWordComplete() + + // Move to next cell in word + this.moveToNextInWord() + } + + handleDelete() { + if (!this.selectedCell) return + + const { element, row, col } = this.selectedCell + + if (element.textContent) { + // Delete current cell + element.textContent = '' + const key = `${row}-${col}` + delete this.userAnswers[key] + element.classList.remove('correct', 'incorrect') + } else { + // Move to previous cell and delete + this.moveToPreviousInWord() + if (this.selectedCell) { + const { element: prevElement, row: prevRow, col: prevCol } = this.selectedCell + prevElement.textContent = '' + const key = `${prevRow}-${prevCol}` + delete this.userAnswers[key] + prevElement.classList.remove('correct', 'incorrect') + } + } + + this.saveGameState() + this.updateProgress() + } + + moveToNextInWord() { + if (!this.selectedCell || !this.selectedWord) return + + const { row, col } = this.selectedCell + const word = this.selectedWord + + let nextRow = row + let nextCol = col + + if (word.direction === 'horizontal') { + nextCol++ + if (nextCol >= word.startCol + word.answer.length) return + } else { + nextRow++ + if (nextRow >= word.startRow + word.answer.length) return + } + + const nextCell = this.cells.find(c => c.row === nextRow && c.col === nextCol) + if (nextCell) { + this.selectCell(nextCell.element, nextCell.row, nextCell.col) + } + } + + moveToPreviousInWord() { + if (!this.selectedCell || !this.selectedWord) return + + const { row, col } = this.selectedCell + const word = this.selectedWord + + let prevRow = row + let prevCol = col + + if (word.direction === 'horizontal') { + prevCol-- + if (prevCol < word.startCol) return + } else { + prevRow-- + if (prevRow < word.startRow) return + } + + const prevCell = this.cells.find(c => c.row === prevRow && c.col === prevCol) + if (prevCell) { + this.selectCell(prevCell.element, prevCell.row, prevCell.col) + } + } + + checkWordComplete() { + if (!this.selectedWord) return + + const word = this.selectedWord + let userWord = '' + let allFilled = true + + if (word.direction === 'horizontal') { + for (let c = word.startCol; c < word.startCol + word.answer.length; c++) { + const key = `${word.startRow}-${c}` + const letter = this.userAnswers[key] + if (!letter) { + allFilled = false + break + } + userWord += letter + } + } else { + for (let r = word.startRow; r < word.startRow + word.answer.length; r++) { + const key = `${r}-${word.startCol}` + const letter = this.userAnswers[key] + if (!letter) { + allFilled = false + break + } + userWord += letter + } + } + + if (allFilled) { + // Auto-validate word + if (userWord === word.answer) { + this.animateWordSuccess(word) + } else { + this.animateWordError(word) + } + } + } + + animateWordSuccess(word) { + const cells = [] + + if (word.direction === 'horizontal') { + for (let c = word.startCol; c < word.startCol + word.answer.length; c++) { + const cellData = this.cells.find(cell => cell.row === word.startRow && cell.col === c) + if (cellData) cells.push(cellData.element) + } + } else { + for (let r = word.startRow; r < word.startRow + word.answer.length; r++) { + const cellData = this.cells.find(cell => cell.row === r && cell.col === word.startCol) + if (cellData) cells.push(cellData.element) + } + } + + cells.forEach((cell, index) => { + setTimeout(() => { + cell.classList.remove('incorrect', 'highlighted') + cell.classList.add('correct', 'word-complete') + + setTimeout(() => { + cell.classList.remove('word-complete') + }, 800) + }, index * 100) + }) + + this.showToast('Correct! 🎉') + this.checkPuzzleComplete() + } + + animateWordError(word) { + const cells = [] + + if (word.direction === 'horizontal') { + for (let c = word.startCol; c < word.startCol + word.answer.length; c++) { + const cellData = this.cells.find(cell => cell.row === word.startRow && cell.col === c) + if (cellData) cells.push(cellData.element) + } + } else { + for (let r = word.startRow; r < word.startRow + word.answer.length; r++) { + const cellData = this.cells.find(cell => cell.row === r && cell.col === word.startCol) + if (cellData) cells.push(cellData.element) + } + } + + cells.forEach(cell => { + cell.classList.remove('correct') + cell.classList.add('incorrect') + + setTimeout(() => { + cell.classList.remove('incorrect') + }, 1000) + }) + + this.showToast('Probeer opnieuw') + } + + checkPuzzleComplete() { + const allCorrect = this.cells.every(cell => { + const row = cell.row + const col = cell.col + const key = `${row}-${col}` + const userAnswer = this.userAnswers[key] + const correctAnswer = this.getCorrectAnswer(row, col) + return userAnswer === correctAnswer + }) + + if (allCorrect) { + setTimeout(() => this.showSuccess(), 500) + } + } + + checkAllAnswers() { + this.cells.forEach(cell => { + const row = cell.row + const col = cell.col + const key = `${row}-${col}` + const userAnswer = this.userAnswers[key] + const correctAnswer = this.getCorrectAnswer(row, col) + + if (userAnswer === correctAnswer) { + cell.element.classList.add('correct') + cell.element.classList.remove('incorrect') + } else if (userAnswer) { + cell.element.classList.add('incorrect') + cell.element.classList.remove('correct') + } + }) + + this.checkPuzzleComplete() + } + + 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 + } + + useHintForCurrentWord() { + if (this.hints <= 0) { + this.showToast('Geen hints meer!') + return + } + + if (!this.selectedWord) { + this.showToast('Selecteer eerst een woord') + return + } + + const word = this.selectedWord + const emptyCells = [] + + if (word.direction === 'horizontal') { + for (let c = word.startCol; c < word.startCol + word.answer.length; c++) { + const key = `${word.startRow}-${c}` + if (!this.userAnswers[key]) { + emptyCells.push({ row: word.startRow, col: c, letter: word.answer[c - word.startCol] }) + } + } + } else { + for (let r = word.startRow; r < word.startRow + word.answer.length; r++) { + const key = `${r}-${word.startCol}` + if (!this.userAnswers[key]) { + emptyCells.push({ row: r, col: word.startCol, letter: word.answer[r - word.startRow] }) + } + } + } + + if (emptyCells.length === 0) { + this.showToast('Woord al compleet!') + return + } + + const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)] + const key = `${randomCell.row}-${randomCell.col}` + this.userAnswers[key] = randomCell.letter + + const cellData = this.cells.find(c => c.row === randomCell.row && c.col === randomCell.col) + if (cellData) { + cellData.element.textContent = randomCell.letter + } + + this.hints-- + this.updateUI() + this.saveGameState() + this.showToast('Hint gebruikt!') + this.checkWordComplete() + } + + clearGrid() { + if (confirm('Alles wissen?')) { + this.userAnswers = {} + this.cells.forEach(cell => { + cell.element.textContent = '' + cell.element.classList.remove('correct', 'incorrect', 'highlighted') + }) + this.saveGameState() + this.updateProgress() + this.showToast('Gewist') + } + } + + newGame() { + if (confirm('Nieuw spel starten?')) { + this.currentLevel = 1 + this.userAnswers = {} + this.loadPuzzle(1) + this.saveGameState() + document.getElementById('sideMenu').classList.remove('active') + this.showToast('Nieuw spel gestart!') + } + } + + showSuccess() { + this.coins += 50 + this.hints += 1 + this.updateUI() + this.saveGameState() + document.getElementById('successModal').classList.add('active') + } + + nextPuzzle() { + document.getElementById('successModal').classList.remove('active') + this.currentLevel++ + this.userAnswers = {} + this.loadPuzzle(this.currentLevel) + this.saveGameState() + this.showToast(`Niveau ${this.currentLevel}`) + } + + updateProgress() { + const total = this.cells.length + const filled = Object.keys(this.userAnswers).length + 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 + } + + showToast(message) { + const toast = document.createElement('div') + toast.className = 'toast' + toast.textContent = message + document.body.appendChild(toast) + + setTimeout(() => { + toast.remove() + }, 2500) + } + + // LocalStorage - Game State Persistence + saveGameState() { + const state = { + currentLevel: this.currentLevel, + coins: this.coins, + hints: this.hints, + userAnswers: this.userAnswers, + timestamp: Date.now() + } + localStorage.setItem('crosswordGameState', JSON.stringify(state)) + } + + loadGameState() { + const saved = localStorage.getItem('crosswordGameState') + if (saved) { + const state = JSON.parse(saved) + this.currentLevel = state.currentLevel || 1 + this.coins = state.coins || 100 + this.hints = state.hints || 3 + this.userAnswers = state.userAnswers || {} + } + } +} + +// Initialize +document.addEventListener('DOMContentLoaded', () => { + new TabletCrosswordApp() +}) diff --git a/public/tablet.html b/public/tablet.html index 203543a..90668f6 100644 --- a/public/tablet.html +++ b/public/tablet.html @@ -4,7 +4,7 @@ Kruiswoord Pro - +