quiz-engine.js (6112B)
1 /** 2 * Quiz Engine - Renderiza questões na página 3 */ 4 5 function _renderQuestion(q) { 6 if (!q) return; 7 const body = document.getElementById('quiz-body'); 8 if (!body) return; 9 10 const counter = document.getElementById('q-counter'); 11 if (counter) { 12 counter.textContent = `${_qState.qIndex + 1} de ${_qState.questions.length}`; 13 } 14 15 const prog = document.getElementById('prog'); 16 if (prog) { 17 const pct = ((_qState.qIndex + 1) / _qState.questions.length) * 100; 18 prog.style.width = pct + '%'; 19 } 20 21 if (q.type === 'mc') { 22 body.innerHTML = `<div class="mc-area fade"> 23 <p class="q-text">${md(q.text)}</p> 24 <div class="options"> 25 ${q.options 26 .map( 27 (o, i) => `<button class="opt" data-opt="${esc(o)}" data-answer="${esc(q.answer)}" onclick="selectOpt(this)" aria-label="Opção ${LETRAS[i]}: ${o}"> 28 <span class="opt-letter">${LETRAS[i]}</span> 29 <span>${md(o)}</span> 30 </button>` 31 ) 32 .join('')} 33 </div> 34 <div class="feedback-line" id="feedback" aria-live="polite" aria-atomic="true"><span class="feedback-dot"></span><span id="feedback-msg"></span></div> 35 ${q.comment ? `<div class="comment-block" id="comment"><div class="comment-label">comentário</div><div>${md(q.comment)}</div></div>` : ''} 36 </div>`; 37 } else { 38 // Modo Flashcard para questões abertas 39 const isRevealed = _qState.flipped || false; 40 body.innerHTML = `<div class="flashcard-area fade"> 41 <p class="q-text">${md(q.text)}</p> 42 <div class="flashcard-simple" id="flashcard"> 43 ${!isRevealed ? `<button class="btn btn-reveal" onclick="flipFlashcard()">Revelar Resposta</button>` : `<div class="ref-label">resposta de referência</div><div class="ref-content">${md(q.reference)}</div>`} 44 </div> 45 <div class="feedback-line" id="feedback" aria-live="polite" aria-atomic="true"><span class="feedback-dot"></span><span id="feedback-msg"></span></div> 46 ${q.comment ? `<div class="comment-block" id="comment"><div class="comment-label">comentário</div><div>${md(q.comment)}</div></div>` : ''} 47 <div class="flashcard-btns" id="flashcard-btns"> 48 <button class="btn btn-ok" onclick="markFlashcardOk()">✓ Acertei</button> 49 <button class="btn btn-bad" onclick="markFlashcardBad()">✗ Errei</button> 50 </div> 51 </div>`; 52 } 53 } 54 55 function selectOpt(btn) { 56 if (_qState.answered) return; 57 _qState.answered = true; 58 const chosen = btn.dataset.opt; 59 const correct = btn.dataset.answer; 60 btn.closest('.options').querySelectorAll('.opt').forEach((b) => { 61 b.disabled = true; 62 if (b.dataset.opt === correct) b.classList.add('correct'); 63 else if (b === btn) b.classList.add('wrong'); 64 else b.classList.add('revealed'); 65 }); 66 if (chosen === correct) { 67 _qState.acertos++; 68 setFeedback(true, 'Correto'); 69 } else { 70 _qState.erros++; 71 setFeedback(false, 'Incorreto — a correta está destacada'); 72 } 73 document.getElementById('comment')?.classList.add('show'); 74 document.getElementById('btn-next').style.display = ''; 75 } 76 77 function flipFlashcard() { 78 const card = document.getElementById('flashcard'); 79 if (card) { 80 _qState.flipped = true; 81 _renderQuestion(_qState.questions[_qState.qIndex]); 82 } 83 } 84 85 function markFlashcardOk() { 86 _qState.answered = true; 87 _qState.acertos++; 88 setFeedback(true, 'Marcado como acertado'); 89 document.getElementById('comment')?.classList.add('show'); 90 document.getElementById('flashcard-btns').innerHTML = ''; 91 document.getElementById('btn-next').style.display = ''; 92 } 93 94 function markFlashcardBad() { 95 _qState.answered = true; 96 _qState.erros++; 97 setFeedback(false, 'Marcado como errado'); 98 document.getElementById('comment')?.classList.add('show'); 99 document.getElementById('flashcard-btns').innerHTML = ''; 100 document.getElementById('btn-next').style.display = ''; 101 } 102 103 function setFeedback(ok, msg) { 104 const fb = document.getElementById('feedback'); 105 fb.className = 'feedback-line show ' + (ok ? 'ok' : 'bad'); 106 document.getElementById('feedback-msg').textContent = msg; 107 } 108 109 function _buildResult() { 110 const { acertos, erros, questions } = _qState; 111 const total = questions.length; 112 const pct = total ? Math.round((acertos / total) * 100) : 0; 113 let nivel, detalhe; 114 if (pct >= 90) { 115 nivel = 'Excelente'; 116 detalhe = 'Domínio consistente do conteúdo. Mantenha revisão periódica.'; 117 } else if (pct >= 70) { 118 nivel = 'Bom'; 119 detalhe = 'Base sólida com pontos a refinar. Revise os erros e releia os comentários.'; 120 } else if (pct >= 50) { 121 nivel = 'Regular'; 122 detalhe = 'Compreensão parcial, com lacunas importantes. Retome o material teórico.'; 123 } else { 124 nivel = 'Insuficiente'; 125 detalhe = 'Os conceitos centrais ainda não estão consolidados. Estude o tema e refaça.'; 126 } 127 document.getElementById('res-pct').textContent = pct + '%'; 128 document.getElementById('res-a').textContent = acertos; 129 document.getElementById('res-a2').textContent = acertos; 130 document.getElementById('res-e').textContent = erros; 131 document.getElementById('res-t').textContent = total; 132 document.getElementById('res-msg-body').innerHTML = `<strong>${nivel}</strong> — ${acertos} de ${total} (${pct}%). ${detalhe}`; 133 } 134 135 function _saveProgress(key) { 136 try { 137 localStorage.setItem(PROGRESS_PREFIX + key, JSON.stringify({ 138 questions: _qState.questions, 139 qIndex: _qState.qIndex, 140 acertos: _qState.acertos, 141 erros: _qState.erros, 142 flipped: _qState.flipped, 143 })); 144 } catch (e) {} 145 } 146 147 function _loadProgress(key) { 148 try { 149 const s = localStorage.getItem(PROGRESS_PREFIX + key); 150 if (!s) return false; 151 const d = JSON.parse(s); 152 if (!d.questions || !d.questions.length) return false; 153 Object.assign(_qState, { 154 questions: d.questions, 155 qIndex: d.qIndex || 0, 156 acertos: d.acertos || 0, 157 erros: d.erros || 0, 158 flipped: d.flipped || false, 159 }); 160 return true; 161 } catch (e) { 162 return false; 163 } 164 } 165 166 function _clearProgress(key) { 167 try { 168 localStorage.removeItem(PROGRESS_PREFIX + key); 169 } catch (e) {} 170 }