// Minimal app logic for math training const state = { ops: new Set(), mode: null, // 'timed' or 'training' score: 0, currentProblem: null, answerBuffer: '', timerId: null, timeLeft: 60, sessionSolved: 0, sessionTarget: 20, settings: { timedSeconds: 60, maxResult: 40, maxOperand: 20, sessionProblems: 20, allowNegative: false, allowFraction: false, } } // elements const menuScreen = document.getElementById('menu-screen') const playScreen = document.getElementById('play-screen') const opsContainer = document.getElementById('ops') const modeButtons = document.querySelectorAll('.mode-btn') const opButtons = document.querySelectorAll('.op-btn') const backBtn = document.getElementById('back-btn') const problemEl = document.getElementById('problem') const answerEl = document.getElementById('answer') const feedbackEl = document.getElementById('feedback') const timerEl = document.getElementById('timer') const scoreEl = document.getElementById('score') const keypad = document.querySelectorAll('.key') const submitBtn = document.getElementById('submit') const clearBtn = document.getElementById('clear') const backspaceBtn = document.getElementById('backspace') const historyPanel = document.getElementById('history-panel') const settingTimed = document.getElementById('setting-timed') const settingMaxResult = document.getElementById('setting-max-result') const settingMaxOperand = document.getElementById('setting-max-operand') const settingSessionProblems = document.getElementById('setting-session-problems') const settingAllowNegative = document.getElementById('setting-allow-negative') const settingAllowFraction = document.getElementById('setting-allow-fraction') const saveSettingsBtn = document.getElementById('save-settings') const resetSettingsBtn = document.getElementById('reset-settings') const progressInner = document.getElementById('progress_inner') const dotBtn = document.getElementById('dot') const negateBtn = document.getElementById('negate') const summaryOverlay = document.getElementById('summary-overlay') const summaryText = document.getElementById('summary-text') const summaryBack = document.getElementById('summary-back') const statusEl = document.getElementById('status'); // load settings from localStorage function loadSettings(){ try{ const raw = localStorage.getItem('matma:settings') if (raw) { const s = JSON.parse(raw) state.settings = Object.assign(state.settings, s) } }catch(e){ console.warn('settings load failed', e) } // reflect to inputs settingTimed.value = state.settings.timedSeconds settingMaxResult.value = state.settings.maxResult settingMaxOperand.value = state.settings.maxOperand settingSessionProblems.value = state.settings.sessionProblems settingAllowNegative.checked = !!state.settings.allowNegative settingAllowFraction.checked = !!state.settings.allowFraction } function saveSettings(){ state.settings.timedSeconds = Math.max(5, Math.min(600, parseInt(settingTimed.value,10) || 60)) state.settings.maxResult = Math.max(1, Math.min(999, parseInt(settingMaxResult.value,10) || 40)) state.settings.maxOperand = Math.max(1, Math.min(999, parseInt(settingMaxOperand.value,10) || 20)) state.settings.sessionProblems = Math.max(1, Math.min(500, parseInt(settingSessionProblems.value,10) || 20)) state.settings.allowNegative = !!settingAllowNegative.checked state.settings.allowFraction = !!settingAllowFraction.checked try{ localStorage.setItem('matma:settings', JSON.stringify(state.settings)) }catch(e){console.warn('save failed',e)} // visual feedback const parent = saveSettingsBtn.parentElement; const feedback = document.createElement('span'); feedback.textContent = 'Zapisano!'; feedback.style.color = '#16a34a'; feedback.style.marginLeft = '12px'; parent.appendChild(feedback); setTimeout(()=> parent.removeChild(feedback), 2000); } function resetSettings(){ state.settings = {timedSeconds:60, maxResult:40, maxOperand:20, sessionProblems:20, allowNegative: false, allowFraction: false}; saveSettings(); loadSettings(); } saveSettingsBtn.addEventListener('click', saveSettings) resetSettingsBtn.addEventListener('click', resetSettings) loadSettings() // initial wiring opButtons.forEach(b => { b.addEventListener('click', () => { const op = b.dataset.op if (state.ops.has(op)) { state.ops.delete(op); b.classList.remove('active') } else { state.ops.add(op); b.classList.add('active') } }) }) modeButtons.forEach(b => b.addEventListener('click', () => { if (state.ops.size === 0) { const hint = document.querySelector('.hint-text'); hint.style.color = '#dc2626'; hint.style.fontWeight = '600'; setTimeout(() => { hint.style.color = ''; hint.style.fontWeight = ''; }, 2000); return; } modeButtons.forEach(x=>x.classList.remove('active')) b.classList.add('active') state.mode = b.dataset.mode startPlay() })) backBtn.addEventListener('click', ()=>{ stopTimer() playScreen.classList.add('hidden') menuScreen.classList.remove('hidden') feedbackEl.textContent = '' }) function startPlay(){ menuScreen.classList.add('hidden') playScreen.classList.remove('hidden') state.score = 0 scoreEl.textContent = state.score state.answerBuffer = '' answerEl.textContent = '' feedbackEl.textContent = '' state.currentProblem = generateProblem() renderProblem() if (state.mode === 'timed'){ progressInner.style.width = '0%' startTimer(state.settings.timedSeconds) statusEl.textContent = 'Na czas'; timerEl.classList.remove('hidden'); } else { progressInner.style.width = '0%' state.sessionSolved = 0 state.sessionTarget = Math.max(1, state.settings.sessionProblems) updateProgress() timerEl.classList.add('hidden'); } } function generateProblem(){ const ops = Array.from(state.ops) const op = ops[Math.floor(Math.random()*ops.length)] const maxOp = Math.max(1, state.settings.maxOperand) const maxRes = Math.max(1, state.settings.maxResult) let a = 0, b = 0 for (let i=0;i<200;i++){ if (op === 'div'){ b = randInt(1, maxOp) if (!state.settings.allowFraction){ const maxQByOp = Math.floor(maxOp / b) const maxQ = Math.min(maxRes, maxQByOp) if (maxQ < 0) continue const q = randInt(0, maxQ) a = q * b if (state.settings.allowNegative && Math.random() < 0.2) a = -a return {a,b,op} } else { const maxResultNByOp = Math.floor((10 * maxOp) / b) const maxResultN = Math.min(maxRes * 10, maxResultNByOp) if (maxResultN < 0) continue const result_n = randInt(0, maxResultN) if ((result_n * b) % 10 !== 0) continue a = (result_n * b) / 10 if (state.settings.allowNegative && Math.random() < 0.2) a = -a return {a,b,op} } } else if (op === 'mul'){ a = randInt(0, maxOp) b = randInt(0, maxOp) if (a * b <= maxRes) return {a,b,op} } else if (op === 'add'){ a = randInt(0, maxOp) b = randInt(0, maxOp) if (a + b <= maxRes) return {a,b,op} } else if (op === 'sub'){ a = randInt(0, maxOp) b = randInt(0, maxOp) if (!state.settings.allowNegative){ if (a < b) [a,b] = [b,a] if (a - b <= maxRes) return {a,b,op} } else { if (Math.abs(a - b) <= maxRes) return {a,b,op} } } } a = Math.min(maxOp, Math.floor(maxRes/2)) b = Math.min(maxOp, 1) return {a,b,op} } function randInt(min,max){ return Math.floor(Math.random()*(max-min+1))+min } function renderProblem(){ const p = state.currentProblem const map = {add: '+', sub: '−', mul: '×', div: '÷'} const symbol = map[p.op] || '?' problemEl.textContent = `${p.a} ${symbol} ${p.b}` } function submitAnswer(){ const buf = state.answerBuffer.trim() if (buf.length === 0) return const given = parseFloat(buf) const p = state.currentProblem let correct = null if (p.op === 'add') correct = p.a + p.b else if (p.op === 'sub') correct = p.a - p.b else if (p.op === 'mul') correct = p.a * p.b else if (p.op === 'div') correct = (p.b === 0) ? null : (p.a / p.b) const eps = state.settings.allowFraction ? 1e-6 : 1e-9 const isCorrect = (correct !== null) && (Math.abs(given - correct) < eps) feedbackEl.style.opacity = 1; if (isCorrect) { state.score += 1 feedbackEl.textContent = 'Dobrze!' feedbackEl.classList.add('correct'); feedbackEl.classList.remove('incorrect'); } else { const correctAnswer = Number.isFinite(correct) ? parseFloat(correct.toFixed(2)) : '—'; feedbackEl.textContent = `Poprawna odpowiedź: ${correctAnswer}` feedbackEl.classList.add('incorrect'); feedbackEl.classList.remove('correct'); } scoreEl.textContent = state.score state.answerBuffer = '' answerEl.textContent = '' if (state.mode === 'training'){ state.sessionSolved++ updateProgress() if (state.sessionSolved >= state.sessionTarget){ saveSessionToHistory() setTimeout(showSummary, 1200); return } } setTimeout(() => { feedbackEl.style.opacity = 0; setTimeout(() => { state.currentProblem = generateProblem() renderProblem() }, 200); }, 1200); } submitBtn.addEventListener('click', submitAnswer) keypad.forEach(k => { k.addEventListener('click', ()=>{ const v = k.textContent.trim() if (!/^[0-9]$/.test(v)) return if (state.answerBuffer.length >= 12) return state.answerBuffer += v answerEl.textContent = state.answerBuffer }) }) if (dotBtn){ dotBtn.addEventListener('click', ()=>{ if (!state.settings.allowFraction) return if (state.answerBuffer.includes('.')) return if (state.answerBuffer.length === 0) state.answerBuffer = '0' state.answerBuffer += '.' answerEl.textContent = state.answerBuffer }) } if (negateBtn){ negateBtn.addEventListener('click', ()=>{ if (!state.settings.allowNegative) return if (state.answerBuffer.startsWith('-')) state.answerBuffer = state.answerBuffer.slice(1) else state.answerBuffer = '-' + state.answerBuffer answerEl.textContent = state.answerBuffer }) } clearBtn.addEventListener('click', ()=>{ state.answerBuffer = '' answerEl.textContent = '' }) backspaceBtn.addEventListener('click', ()=>{ state.answerBuffer = state.answerBuffer.slice(0,-1) answerEl.textContent = state.answerBuffer }) function startTimer(seconds){ state.timeLeft = seconds timerEl.textContent = state.timeLeft stopTimer(); state.timerId = setInterval(()=>{ state.timeLeft -= 1 timerEl.textContent = state.timeLeft const pct = Math.max(0, (state.timeLeft / seconds) * 100) progressInner.style.width = pct + '%' if (state.timeLeft <= 0) { stopTimer() endSession() } },1000) } function stopTimer(){ if (state.timerId) clearInterval(state.timerId) state.timerId = null } function endSession(){ saveSessionToHistory() showSummary(); } function showSummary(){ const total = state.mode === 'timed' ? state.score : state.sessionTarget; const correct = state.score; const pct = total > 0 ? Math.round((correct / total) * 100) : 0; if (state.mode === 'timed') { summaryText.textContent = `Koniec czasu! Zdobyłeś ${correct} punktów.`; } else { summaryText.textContent = `Twój wynik: ${correct} / ${total} poprawnie (${pct}%)`; } summaryOverlay.classList.remove('hidden'); } summaryBack.addEventListener('click', ()=>{ summaryOverlay.classList.add('hidden'); playScreen.classList.add('hidden'); menuScreen.classList.remove('hidden'); }); function updateProgress(){ if (state.mode === 'training'){ const pct = Math.min(100, Math.round((state.sessionSolved/state.sessionTarget)*100)) progressInner.style.width = pct + '%' statusEl.textContent = `${state.sessionSolved}/${state.sessionTarget}` } } // history persistence function loadHistory(){ try{ const raw = localStorage.getItem('matma:history') return raw ? JSON.parse(raw) : [] }catch(e){ return [] } } function saveSessionToHistory(){ const h = loadHistory() const sessionData = { ts: Date.now(), mode: state.mode, score: state.score, ops: Array.from(state.ops), duration: state.mode === 'timed' ? state.settings.timedSeconds : null, problems: state.mode === 'training' ? state.sessionTarget : null }; h.unshift(sessionData); while(h.length>50) h.pop() try{ localStorage.setItem('matma:history', JSON.stringify(h)) }catch(e){ } } function renderHistory(){ const h = loadHistory() historyPanel.innerHTML = '' if (h.length === 0) { historyPanel.innerHTML = '
Brak zapisanych sesji.
' return } h.forEach(item => { const d = new Date(item.ts).toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); const modeText = item.mode === 'timed' ? `Na czas (${item.duration}s)` : `Trening (${item.problems} zadań)`; const opsText = item.ops.join(', ').replace('add','+').replace('sub','-').replace('mul','×').replace('div','÷'); const el = document.createElement('div') el.className = 'history-item' el.innerHTML = `