/* VerIQ test engine (vanilla JS, no deps) */
(function () {
"use strict";
var state = {
token: null,
tasks: [],
timeLimitS: 0,
startedAt: 0,
current: 0,
answers: {}, // task_id -> choice index
timerId: null,
finished: false
};
var $ = function (id) { return document.getElementById(id); };
function show(id) {
["loading", "testArea", "submitArea", "errorArea"].forEach(function (s) {
$(s).style.display = (s === id) ? "" : "none";
});
}
function showError(msg) {
$("errorArea").textContent = msg;
show("errorArea");
}
function fmtTime(s) {
s = Math.max(0, Math.floor(s));
var m = Math.floor(s / 60), r = s % 60;
return (m < 10 ? "0" : "") + m + ":" + (r < 10 ? "0" : "") + r;
}
function startTimer() {
var tick = function () {
var elapsed = (Date.now() - state.startedAt) / 1000;
var left = state.timeLimitS - elapsed;
var el = $("timer");
el.textContent = fmtTime(left);
el.className = "timer" + (left < 120 ? " low" : "");
if (left <= 0 && !state.finished) {
// auto-submit with current answers
submit(true);
}
};
state.timerId = setInterval(tick, 500);
tick();
}
function renderTask() {
var t = state.tasks[state.current];
if (!t) return;
$("qCounter").textContent = "Task " + (state.current + 1) + " / " + state.tasks.length;
$("qCategory").textContent = t.category;
$("qText").textContent = t.prompt.text;
var svgBox = $("qSvg");
svgBox.innerHTML = t.prompt.svg || "";
svgBox.style.display = t.prompt.svg ? "" : "none";
var box = $("choices");
box.innerHTML = "";
t.choices.forEach(function (c, i) {
var div = document.createElement("div");
div.className = "choice" + (state.answers[t.id] === i ? " sel" : "");
var letter = document.createElement("span");
letter.className = "letter";
letter.textContent = String.fromCharCode(65 + i);
div.appendChild(letter);
if (c.svg) {
var sv = document.createElement("span");
sv.innerHTML = c.svg;
div.appendChild(sv);
}
var txt = document.createElement("span");
txt.textContent = c.text || "";
div.appendChild(txt);
div.addEventListener("click", function () {
state.answers[t.id] = i;
renderTask();
updateAnswered();
});
box.appendChild(div);
});
$("progressBar").style.width =
Math.round((state.current / state.tasks.length) * 100) + "%";
$("prevBtn").disabled = state.current === 0;
$("nextBtn").textContent =
state.current === state.tasks.length - 1 ? "Finish ✓" : "Next →";
}
function updateAnswered() {
$("answeredCount").textContent = Object.keys(state.answers).length;
}
function next() {
if (state.current < state.tasks.length - 1) {
state.current++;
renderTask();
} else {
openSubmit();
}
}
function prev() {
if (state.current > 0) {
state.current--;
renderTask();
}
}
function openSubmit() {
var answered = Object.keys(state.answers).length;
var elapsed = Math.round((Date.now() - state.startedAt) / 1000);
$("finishSummary").textContent =
"Answered " + answered + " of " + state.tasks.length +
" tasks in " + fmtTime(elapsed) + ". Unanswered tasks count as wrong. " +
"You can go back and review before submitting.";
show("submitArea");
}
function submit(auto) {
if (state.finished) return;
state.finished = true;
clearInterval(state.timerId);
var elapsed = (Date.now() - state.startedAt) / 1000;
var payload = {
session_token: state.token,
answers: state.answers,
total_time_s: Math.round(elapsed * 10) / 10
};
$("finishBtn").disabled = true;
$("finishBtn").textContent = auto ? "Time is up — submitting…" : "Submitting…";
fetch("/api/finish", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
}).then(function (r) {
if (!r.ok) return r.json().then(function (e) { throw new Error(e.detail || r.status); });
return r.json();
}).then(function (data) {
try { localStorage.removeItem("veriq_session"); } catch (e) {}
window.location.href = "/result/" + data.result_id;
}).catch(function (e) {
state.finished = false;
$("finishBtn").disabled = false;
$("finishBtn").textContent = "Submit & get my signed result";
showError("Submit failed: " + e.message);
});
}
function init() {
// resume session if page reloaded mid-test
var saved = null;
try { saved = JSON.parse(localStorage.getItem("veriq_session") || "null"); } catch (e) {}
if (saved && saved.token && saved.tasks && saved.tasks.length &&
Date.now() - saved.startedAt < saved.timeLimitS * 1000) {
state.token = saved.token;
state.tasks = saved.tasks;
state.timeLimitS = saved.timeLimitS;
state.startedAt = saved.startedAt;
state.answers = saved.answers || {};
state.current = saved.current || 0;
$("sessionInfo").textContent = "session restored";
show("testArea");
renderTask();
updateAnswered();
startTimer();
return;
}
fetch("/api/start", { method: "POST" }).then(function (r) {
if (!r.ok) return r.json().then(function (e) { throw new Error(e.detail || r.status); });
return r.json();
}).then(function (data) {
state.token = data.session_token;
state.tasks = data.tasks;
state.timeLimitS = data.time_limit_s;
state.startedAt = Date.now();
state.current = 0;
state.answers = {};
try {
localStorage.setItem("veriq_session", JSON.stringify({
token: state.token, tasks: state.tasks,
timeLimitS: state.timeLimitS, startedAt: state.startedAt,
answers: {}, current: 0
}));
} catch (e) {}
$("sessionInfo").textContent = "40 tasks · one attempt";
show("testArea");
renderTask();
updateAnswered();
startTimer();
}).catch(function (e) {
showError("Could not start the test: " + e.message +
(String(e.message).indexOf("429") >= 0 ? " — too many attempts from your network, try again later." : ""));
});
}
// persist state on navigation within test
window.addEventListener("beforeunload", function () {
if (state.token && !state.finished) {
try {
var s = JSON.parse(localStorage.getItem("veriq_session") || "{}");
s.answers = state.answers;
s.current = state.current;
localStorage.setItem("veriq_session", JSON.stringify(s));
} catch (e) {}
}
});
$("nextBtn").addEventListener("click", next);
$("prevBtn").addEventListener("click", prev);
$("finishBtn").addEventListener("click", function () { submit(false); });
$("reviewBtn").addEventListener("click", function () {
state.current = 0;
show("testArea");
renderTask();
});
init();
})();