(() => { const startBtn = document.getElementById('startBtn'); const timeEl = document.getElementById('time'); const progressEl = document.getElementById('progress'); const problemEl = document.getElementById('problem'); const optionsEl = document.getElementById('options'); const gameEl = document.getElementById('game'); const statusEl = document.getElementById('status'); const resultEl = document.getElementById('result'); const fxLayer = document.getElementById('fxLayer'); let sessionId = null; let totalQuestions = 20; let remaining = 60; let countdown = null; let inFlight = false; function setStatus(msg) { statusEl.textContent = msg || ''; } function showGame(show) { gameEl.classList.toggle('hidden', !show); } function showResult(score) { const { correct, total_answered, total } = score; resultEl.innerHTML = `
Результат: ${correct} из ${total}
Отвечено: ${total_answered}
`; resultEl.classList.remove('hidden'); document.getElementById('againBtn')?.addEventListener('click', () => { resultEl.classList.add('hidden'); start(); }); } function renderQuestion(q) { if (!q) return; problemEl.textContent = `${q.a} × ${q.b}`; optionsEl.innerHTML = ''; q.options.forEach(value => { const btn = document.createElement('button'); btn.className = 'option-btn'; btn.textContent = String(value); btn.addEventListener('click', () => answer(value, btn)); optionsEl.appendChild(btn); }); progressEl.textContent = `${q.index}/${totalQuestions}`; } function stopTimer() { if (countdown) { clearInterval(countdown); countdown = null; } } function startTimer(seconds) { remaining = seconds; timeEl.textContent = String(remaining); stopTimer(); countdown = setInterval(() => { remaining -= 1; if (remaining <= 0) { remaining = 0; timeEl.textContent = '0'; stopTimer(); } else { timeEl.textContent = String(remaining); } }, 1000); } async function start() { if (inFlight) return; inFlight = true; setStatus(''); showGame(false); startBtn.disabled = true; resultEl.classList.add('hidden'); try { const res = await fetch('/api/session/start', { method: 'POST' }); if (!res.ok) throw new Error('Не удалось начать сессию'); const data = await res.json(); sessionId = data.session_id; totalQuestions = data.total_questions; startTimer(data.remaining_seconds); renderQuestion(data.question); showGame(true); setStatus('Отвечайте как можно быстрее!'); } catch (e) { console.error(e); setStatus('Ошибка старта. Попробуйте ещё раз.'); } finally { startBtn.disabled = false; inFlight = false; } } function celebrateFromElement(el) { if (!fxLayer) return; const rect = el.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const count = 40; for (let i = 0; i < count; i++) { const p = document.createElement('span'); p.className = 'confetti'; // origin p.style.left = `${x}px`; p.style.top = `${y}px`; // random trajectory const angle = Math.random() * Math.PI * 2; const distance = 60 + Math.random() * 80; const tx = Math.cos(angle) * distance; const ty = Math.sin(angle) * distance + 40 + Math.random() * 60; // gravity pull const rot = (Math.random() * 720 - 360).toFixed(0) + 'deg'; p.style.setProperty('--tx', `${tx.toFixed(0)}px`); p.style.setProperty('--ty', `${ty.toFixed(0)}px`); p.style.setProperty('--rot', rot); const hue = Math.floor(Math.random() * 360); p.style.backgroundColor = `hsl(${hue} 90% 55%)`; p.style.animation = `confetti ${650 + Math.random() * 400}ms ease-out forwards`; fxLayer.appendChild(p); setTimeout(() => p.remove(), 1200); } } async function answer(value, clickedBtn) { if (!sessionId || inFlight) return; inFlight = true; // Disable all buttons to prevent double clicks const buttons = Array.from(optionsEl.querySelectorAll('button')); buttons.forEach(b => b.disabled = true); try { const res = await fetch(`/api/session/${sessionId}/answer`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ answer: value }), }); if (!res.ok) throw new Error('Не удалось отправить ответ'); const data = await res.json(); // feedback if (data.correct) { clickedBtn.classList.add('correct'); celebrateFromElement(clickedBtn); } else { clickedBtn.classList.add('wrong'); } // Small pause to show feedback await new Promise(r => setTimeout(r, data.correct ? 350 : 250)); if (data.finished) { stopTimer(); showGame(false); const reason = data.reason === 'timeout' ? 'Время вышло.' : 'Завершено!'; setStatus(reason); showResult(data.score); } else { timeEl.textContent = String(data.remaining_seconds); renderQuestion(data.question); } } catch (e) { console.error(e); setStatus('Ошибка ответа. Перезапустите сессию.'); } finally { inFlight = false; } } startBtn.addEventListener('click', start); })();