summaryrefslogtreecommitdiff
path: root/layouts/partials/quiz-runtime.html
diff options
context:
space:
mode:
Diffstat (limited to 'layouts/partials/quiz-runtime.html')
-rw-r--r--layouts/partials/quiz-runtime.html447
1 files changed, 447 insertions, 0 deletions
diff --git a/layouts/partials/quiz-runtime.html b/layouts/partials/quiz-runtime.html
new file mode 100644
index 0000000..9ce5aab
--- /dev/null
+++ b/layouts/partials/quiz-runtime.html
@@ -0,0 +1,447 @@
+<script>
+/* ── ESTADO DA APLICAÇÃO ── */
+let currentQuiz = null;
+/* questions, qIndex, acertos, erros, answered → usam _qState (quiz-engine.html) */
+
+const BASE_TITLE = document.title;
+
+let activeFloater = null;
+let currentTextComments = {};
+let lastSubjectRoute = null;
+
+/* ── UTILITÁRIOS ── */
+/* shuffle, esc, md → quiz-parser.html | _qState, _renderQuestion, etc. → quiz-engine.html */
+
+function slugifyText(s) {
+ return String(s || '')
+ .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
+ .toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
+}
+
+function show(id) {
+ ['page-home','page-quiz','page-result','page-text'].forEach(p => {
+ const el = document.getElementById(p);
+ if (el) el.classList.toggle('active', p === id);
+ });
+}
+
+/* ── ROTEAMENTO ── */
+function encodeHash(type, a, b, c) {
+ if (type === 'home') return '';
+ if (type === 'disc') return '#disc/' + encodeURIComponent(a);
+ if (type === 'assunto') return '#assunto/' + [a, b].map(encodeURIComponent).join('/');
+ if (type === 'quiz') return '#quiz/' + [a, b, c].map(encodeURIComponent).join('/');
+ if (type === 'simulado') return '#simulado/' + [a, b].map(encodeURIComponent).join('/');
+ return '';
+}
+
+function decodeHash(hash) {
+ if (!hash || hash === '#') return { type: 'home' };
+ const parts = (hash.startsWith('#') ? hash.slice(1) : hash)
+ .split('/').map(s => { try { return decodeURIComponent(s); } catch(e) { return s; } });
+
+ const type = parts[0];
+ if (type === 'disc' && parts.length >= 2)
+ return { type: 'disc', disc: parts[1] };
+ if (type === 'assunto' && parts.length >= 3)
+ return { type: 'assunto', discFolder: parts[1], slug: parts[2] };
+ if (type === 'quiz' && parts.length >= 4)
+ return { type: 'quiz', discFolder: parts[1], slug: parts[2], quizId: parts[3] };
+ if (type === 'simulado' && parts.length >= 3)
+ return { type: 'simulado', discFolder: parts[1], quizId: parts[2] };
+
+ return { type: 'home' };
+}
+
+function navigate(hash, replace) {
+ const url = window.location.pathname + window.location.search + (hash || '');
+ if (replace) history.replaceState({ hash }, '', url || window.location.pathname);
+ else history.pushState({ hash }, '', url || window.location.pathname);
+}
+
+function renderRoute(route) {
+ if (route.type === 'disc') _showDisc(route.disc);
+ else if (route.type === 'assunto') _openSubject(route.discFolder, route.slug);
+ else if (route.type === 'quiz') _startQuiz(route.discFolder, route.slug, route.quizId);
+ else if (route.type === 'simulado')_startSimulado(route.discFolder, route.quizId);
+ else _showHome();
+}
+
+window.addEventListener('popstate', () => renderRoute(decodeHash(window.location.hash)));
+
+/* ── INTERFACE: HOME ── */
+function showHome() { navigate('', false); _showHome(); }
+
+function _showHome() {
+ closeFloater();
+ buildHome();
+ show('page-home');
+ document.getElementById('breadcrumb').textContent = '';
+ document.title = BASE_TITLE;
+}
+
+function buildHome() {
+ const el = document.getElementById('page-home');
+ el.innerHTML = `
+ <div class="disc-grid">
+ ${DATA.map(disc => {
+ const n = disc.subjects.length;
+ const nq = disc.subjects.reduce((acc, s) => acc + s.quizzes.length, 0) + (disc.quizzes ? disc.quizzes.length : 0);
+ const meta = [
+ n ? `${n} assunto${n > 1 ? 's' : ''}` : null,
+ nq ? `${nq} quiz${nq > 1 ? 'zes' : ''}` : null,
+ ].filter(Boolean).join(' · ') || 'sem conteúdo';
+ return `
+ <a class="disc-card fade" href="${encodeHash('disc', disc.discipline)}"
+ onclick="openDisc('${esc(disc.discipline)}');return false;">
+ <div class="disc-card-name">${esc(disc.discipline)}</div>
+ <div class="disc-card-meta">${meta}</div>
+ </a>`;
+ }).join('')}
+ </div>`;
+}
+
+/* ── INTERFACE: DISCIPLINA ── */
+function openDisc(discName) { navigate(encodeHash('disc', discName), false); _showDisc(discName); }
+
+function _showDisc(discName) {
+ closeFloater();
+ const disc = DATA.find(d => d.discipline === discName);
+ if (!disc) { _showHome(); return; }
+
+ const introHtml = disc.intro && disc.intro.content
+ ? `<div class="disc-intro">
+ <article class="prose disc-intro-prose">${disc.intro.content}</article>
+ ${disc.intro.references && disc.intro.references.length
+ ? `<div class="references disc-intro-refs">
+ <div class="ref-head">referências</div>
+ <div class="ref-list">${disc.intro.references.map(r => `<p>${esc(r)}</p>`).join('')}</div>
+ </div>`
+ : ''}
+ </div>`
+ : '';
+
+ const simuladosHtml = disc.quizzes && disc.quizzes.length ? `
+ <div class="subject-group">
+ <div class="sq-label" style="margin-bottom:10px;">Simulados da Disciplina</div>
+ ${disc.quizzes.map(qz => `
+ <a class="sq-item" href="${encodeHash('simulado', disc.folderKey, qz.id)}"
+ onclick="startSimulado('${esc(disc.folderKey)}','${esc(qz.id)}');return false;"
+ style="border-bottom:none; background:var(--surface); padding: 10px; border-radius: 4px; margin-bottom: 5px;">
+ <span class="sq-name" style="color:var(--amber); font-weight:bold;">${esc(qz.title)}</span>
+ <span class="sq-meta">${qz.questions.length} questões</span>
+ </a>
+ `).join('')}
+ </div>
+ ` : '';
+
+ const subjectsHtml = disc.subjects.map(s => {
+ const bits = [];
+ if (s.text) bits.push('texto');
+ if (s.quizzes.length) bits.push(`${s.quizzes.length} quiz${s.quizzes.length > 1 ? 'zes' : ''}`);
+ const meta = bits.join(' · ') || 'vazio';
+ return `
+ <a class="subject-row" href="${encodeHash('assunto', disc.folderKey, s.slug)}"
+ onclick="openSubject('${esc(disc.folderKey)}','${esc(s.slug)}');return false;">
+ <span class="subject-row-name">${esc(s.name)}</span>
+ <span class="subject-row-meta">${meta}</span>
+ </a>`;
+ }).join('');
+
+ const el = document.getElementById('page-home');
+ el.innerHTML = `
+ <div class="discipline fade">
+ <div class="disc-back"><a href="#" onclick="showHome();return false;">← disciplinas</a></div>
+ <div class="discipline-name">${esc(disc.discipline)}</div>
+ ${introHtml}
+ ${simuladosHtml}
+ <div class="subject-list">${subjectsHtml || '<p class="empty-note">sem assuntos ainda</p>'}</div>
+ </div>`;
+
+ show('page-home');
+ document.getElementById('breadcrumb').textContent = disc.discipline;
+ document.title = disc.discipline + ' · ' + BASE_TITLE;
+}
+
+/* ── INTERFACE: ASSUNTO ── */
+function openSubject(discFolder, slug) {
+ navigate(encodeHash('assunto', discFolder, slug), false);
+ _openSubject(discFolder, slug);
+}
+
+function _openSubject(discFolder, slug) {
+ closeFloater();
+ const disc = DATA.find(d => d.folderKey === discFolder);
+ if (!disc) { _showHome(); return; }
+ const sub = disc.subjects.find(s => s.slug === slug);
+ if (!sub) { _showDisc(disc.discipline); return; }
+
+ lastSubjectRoute = { discFolder, slug, discName: disc.discipline, subjectName: sub.name };
+
+ document.getElementById('text-discipline').textContent = disc.discipline;
+ document.getElementById('text-title').textContent = sub.name;
+
+ const qzWrap = document.getElementById('text-quizzes');
+ const qzList = document.getElementById('text-quizzes-list');
+
+ if (sub.quizzes.length) {
+ qzWrap.style.display = '';
+ qzList.innerHTML = sub.quizzes.map(qz => {
+ const mc = qz.questions.filter(q => q.type === 'mc').length;
+ const open = qz.questions.filter(q => q.type === 'open').length;
+ const meta = [
+ mc ? `${mc} mc` : null,
+ open ? `${open} abertas` : null,
+ ].filter(Boolean).join(' · ') || `${qz.questions.length} questões`;
+ return `
+ <a class="sq-item" href="${encodeHash('quiz', discFolder, slug, qz.id)}"
+ onclick="startQuiz('${esc(discFolder)}','${esc(slug)}','${esc(qz.id)}');return false;">
+ <span class="sq-name">${esc(qz.title)}</span>
+ <span class="sq-meta">${meta}</span>
+ </a>`;
+ }).join('');
+ } else {
+ qzWrap.style.display = 'none';
+ qzList.innerHTML = '';
+ }
+
+ const prose = document.getElementById('prose');
+ if (sub.text && sub.text.content) {
+ prose.innerHTML = sub.text.content;
+ currentTextComments = {};
+ buildToc(prose);
+ } else {
+ prose.innerHTML = '<p class="empty-note">Texto ainda não disponível para este assunto.</p>';
+ document.getElementById('text-toc').style.display = 'none';
+ }
+
+ const refContainer = document.getElementById('text-references-container');
+ const refList = document.getElementById('text-references-list');
+ const refs = sub.text ? (sub.text.references || []) : [];
+
+ if (refs.length) {
+ refContainer.style.display = 'block';
+ refList.innerHTML = refs.map(r => `<p>${esc(r)}</p>`).join('');
+ } else {
+ refContainer.style.display = 'none';
+ }
+
+ document.getElementById('breadcrumb').textContent = `${disc.discipline} · ${sub.name}`;
+ document.title = sub.name + ' · ' + BASE_TITLE;
+ show('page-text');
+ window.scrollTo(0, 0);
+}
+
+/* ── INTERFACE: SUMÁRIO (TOC) ── */
+function buildToc(proseEl) {
+ const tocWrap = document.getElementById('text-toc');
+ const tocList = document.getElementById('text-toc-list');
+ tocList.innerHTML = '';
+
+ const headings = proseEl.querySelectorAll('h2, h3');
+ if (!headings.length) { tocWrap.style.display = 'none'; return; }
+
+ const used = new Set();
+ headings.forEach(h => {
+ if (!h.id) {
+ let base = slugifyText(h.textContent) || 'sec';
+ let id = base, i = 2;
+ while (used.has(id) || document.getElementById(id)) { id = base + '-' + i++; }
+ h.id = id;
+ }
+ used.add(h.id);
+ });
+
+ headings.forEach(h => {
+ const li = document.createElement('li');
+ li.className = 'toc-' + h.tagName.toLowerCase();
+ const a = document.createElement('a');
+ a.href = '#' + h.id;
+ a.textContent = h.textContent;
+ a.addEventListener('click', e => {
+ e.preventDefault();
+ h.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ history.replaceState(history.state, '', '#' + h.id);
+ });
+ li.appendChild(a);
+ tocList.appendChild(li);
+ });
+
+ tocWrap.style.display = '';
+}
+
+/* ── INTERFACE: COMPONENTES FLUTUANTES ── */
+document.addEventListener('click', e => {
+ const footnoteLink = e.target.closest('a[href^="#fn:"]');
+ const closeBtn = e.target.closest('.f-close');
+
+ if (closeBtn) { closeFloater(); return; }
+
+ if (footnoteLink) {
+ e.preventDefault(); e.stopPropagation(); closeFloater();
+ const targetId = footnoteLink.getAttribute('href').substring(1);
+ const footnoteElement = document.getElementById(targetId);
+ if (footnoteElement) {
+ const contentClone = footnoteElement.cloneNode(true);
+ const backLink = contentClone.querySelector('.footnote-backref');
+ if (backLink) backLink.remove();
+ const parentParagraph = footnoteLink.closest('p, li');
+ const quoteText = parentParagraph ? parentParagraph.innerText.substring(0, 40) + '…' : 'Nota';
+ openFloater(footnoteLink, quoteText, contentClone.innerHTML);
+ footnoteLink.classList.add('active');
+ }
+ } else if (activeFloater && !e.target.closest('.floater')) {
+ closeFloater();
+ }
+});
+
+function openFloater(target, quote, innerHTMLContent) {
+ const f = document.createElement('div');
+ f.className = 'floater';
+ f.innerHTML = `
+ <div class="f-label"><span>comentário</span><span class="f-close">×</span></div>
+ <div class="f-quote">${quote}</div>
+ <div>${innerHTMLContent}</div>`;
+ document.body.appendChild(f);
+
+ const r = target.getBoundingClientRect();
+ const top = window.scrollY + r.bottom + 8;
+ const padding = 20;
+ let left = window.scrollX + r.left;
+ const maxLeft = window.innerWidth - f.offsetWidth - padding;
+
+ if (left > maxLeft) left = Math.max(padding, maxLeft);
+
+ f.style.top = top + 'px';
+ f.style.left = left + 'px';
+ activeFloater = f;
+}
+
+function closeFloater() {
+ if (activeFloater) { activeFloater.remove(); activeFloater = null; }
+ document.querySelectorAll('a[href^="#fn:"]').forEach(a => a.classList.remove('active'));
+}
+
+document.addEventListener('keydown', e => { if (e.key === 'Escape') closeFloater(); });
+
+/* ── MOTOR: QUIZ E SIMULADO ── */
+function startQuiz(discFolder, slug, quizId) {
+ navigate(encodeHash('quiz', discFolder, slug, quizId), false);
+ _startQuiz(discFolder, slug, quizId);
+}
+
+function _startQuiz(discFolder, slug, quizId) {
+ closeFloater();
+ const disc = DATA.find(d => d.folderKey === discFolder);
+ if (!disc) { _showHome(); return; }
+ const sub = disc.subjects.find(s => s.slug === slug);
+ if (!sub) { _showDisc(disc.discipline); return; }
+ const quiz = sub.quizzes.find(q => q.id === quizId);
+ if (!quiz) { _openSubject(discFolder, slug); return; }
+
+ lastSubjectRoute = { discFolder, slug, discName: disc.discipline, subjectName: sub.name };
+ currentQuiz = {
+ isSimulado: false, discFolder, slug, quizId,
+ subjectName: sub.name, quizTitle: quiz.title
+ };
+
+ _qState.questions = prepareQuizQuestions(quiz.questions);
+ _qState.qIndex = _qState.acertos = _qState.erros = 0;
+ _clearProgress('spa_' + discFolder + '_' + slug + '_' + quizId);
+
+ document.getElementById('breadcrumb').textContent = `${disc.discipline} · ${sub.name} · ${quiz.title}`;
+ document.title = `${quiz.title} · ${sub.name} · ${BASE_TITLE}`;
+
+ show('page-quiz');
+ renderQuestion();
+}
+
+function startSimulado(discFolder, quizId) {
+ navigate(encodeHash('simulado', discFolder, quizId), false);
+ _startSimulado(discFolder, quizId);
+}
+
+function _startSimulado(discFolder, quizId) {
+ closeFloater();
+ const disc = DATA.find(d => d.folderKey === discFolder);
+ if (!disc) { _showHome(); return; }
+ const quiz = disc.quizzes.find(q => q.id === quizId);
+ if (!quiz) { _showDisc(disc.discipline); return; }
+
+ lastSubjectRoute = null;
+ currentQuiz = {
+ isSimulado: true, discFolder, quizId,
+ subjectName: disc.discipline, quizTitle: quiz.title
+ };
+
+ _qState.questions = prepareQuizQuestions(quiz.questions);
+ _qState.qIndex = _qState.acertos = _qState.erros = 0;
+ _clearProgress('spa_sim_' + discFolder + '_' + quizId);
+
+ document.getElementById('breadcrumb').textContent = `${disc.discipline} · ${quiz.title}`;
+ document.title = `${quiz.title} · ${disc.discipline} · ${BASE_TITLE}`;
+
+ show('page-quiz');
+ renderQuestion();
+}
+
+function restartQuiz() {
+ if (currentQuiz.isSimulado) {
+ _clearProgress('spa_sim_' + currentQuiz.discFolder + '_' + currentQuiz.quizId);
+ _startSimulado(currentQuiz.discFolder, currentQuiz.quizId);
+ } else {
+ _clearProgress('spa_' + currentQuiz.discFolder + '_' + currentQuiz.slug + '_' + currentQuiz.quizId);
+ _startQuiz(currentQuiz.discFolder, currentQuiz.slug, currentQuiz.quizId);
+ }
+}
+
+function backToSubject() {
+ if (currentQuiz && currentQuiz.isSimulado) {
+ openDisc(currentQuiz.subjectName);
+ } else if (lastSubjectRoute) {
+ navigate(encodeHash('assunto', lastSubjectRoute.discFolder, lastSubjectRoute.slug), false);
+ _openSubject(lastSubjectRoute.discFolder, lastSubjectRoute.slug);
+ } else {
+ showHome();
+ }
+}
+
+function renderQuestion() {
+ _renderQuestion(_qState.questions[_qState.qIndex]);
+ const progKey = currentQuiz
+ ? 'spa_' + (currentQuiz.isSimulado
+ ? 'sim_' + currentQuiz.discFolder + '_' + currentQuiz.quizId
+ : currentQuiz.discFolder + '_' + currentQuiz.slug + '_' + currentQuiz.quizId)
+ : null;
+ if (progKey) _saveProgress(progKey);
+}
+
+/* selectOpt, showRef, selfJudge, setFeedback → definidos em quiz-parser.html */
+
+function nextQuestion() {
+ _qState.qIndex++;
+ if (_qState.qIndex >= _qState.questions.length) { showResult(); return; }
+ const body = document.getElementById('quiz-body');
+ body.classList.add('fade-out');
+ setTimeout(() => { body.classList.remove('fade-out'); renderQuestion(); }, 100);
+}
+
+/* ── TELA DE RESULTADOS ── */
+function showResult() {
+ show('page-result');
+ _buildResult();
+ document.getElementById('breadcrumb').textContent = currentQuiz.subjectName + ' · ' + currentQuiz.quizTitle;
+}
+
+/* ── INICIALIZAÇÃO DE ROTA ── */
+(function init() {
+ const route = decodeHash(window.location.hash);
+ if (route.type !== 'home') {
+ history.replaceState({ hash: window.location.hash }, '', window.location.href);
+ renderRoute(route);
+ } else {
+ history.replaceState({ hash: '' }, '', window.location.pathname + window.location.search);
+ _showHome();
+ }
+})();
+</script>