UI overhaul: fire-gradient theme and visual polish; logic: added answer shuffling and improved Baserow error handling

This commit is contained in:
2026-04-16 06:54:54 +02:00
parent 12d2d51dc3
commit e993eef125
10 changed files with 342 additions and 216 deletions
+14 -16
View File
@@ -1,6 +1,5 @@
/**
* Normalize text for freitext comparison.
* Removes special characters, lowercases, collapses whitespace.
* "Villingen-Schwenningen" == "villingen schwenningen" == "villingenschwenningen"
*/
function normalizeText(text) {
@@ -38,19 +37,21 @@ function countOptions(question) {
return n;
}
function shuffleArray(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
/**
* Calculate score for an answer.
* @param {object} question Baserow row
* @param {string[]} selected e.g. ['A','C'] or ['Berlin']
* @param {number} timeRemaining seconds left (0 if no timer)
* @param {number} totalTime total seconds (0 if binary)
* @param {string} scoringMode 'binary' | 'time'
* @returns {{ points: number, correct: boolean }}
*/
function calculateScore(question, selected, timeRemaining, totalTime, scoringMode) {
if (!selected || selected.length === 0) return { points: 0, correct: false };
// Base points
let basis;
if (scoringMode === 'time' && totalTime > 0) {
const ratio = Math.max(0, Math.min(1, timeRemaining / totalTime));
@@ -62,13 +63,11 @@ function calculateScore(question, selected, timeRemaining, totalTime, scoringMod
const typ = (question['Typ'] || 'MC').toLowerCase();
const correctAnswers = parseCorrectAnswers(question);
// Freitext
if (typ === 'freitext') {
const ok = checkFreitext(selected[0], correctAnswers[0]);
return { points: ok ? basis : 0, correct: ok };
}
// MC / Wahr-Falsch
const correctSet = new Set(correctAnswers);
const totalCorrect = correctSet.size;
const totalIncorrect = countOptions(question) - totalCorrect;
@@ -91,7 +90,7 @@ function calculateScore(question, selected, timeRemaining, totalTime, scoringMod
}
/**
* Strip the correct answer from a question before sending to participants.
* Strip the correct answer and shuffle options before sending to participants.
*/
function sanitizeQuestion(question) {
const typ = (question['Typ'] || 'MC').toLowerCase();
@@ -104,23 +103,22 @@ function sanitizeQuestion(question) {
kategorie: question['Kategorie'] || '',
};
// Bild (Baserow file field → array of objects)
const bild = question['Bild'];
if (bild && Array.isArray(bild) && bild.length > 0) {
sanitized.bild = bild[0].url;
}
if (typ === 'freitext') {
// no options
} else {
if (typ !== 'freitext') {
const keys = ['A', 'B', 'C', 'D'];
for (const k of keys) {
const val = question[`Antwort ${k}`];
if (val) sanitized.antworten.push({ key: k, text: val });
}
// Shuffle answer order
sanitized.antworten = shuffleArray(sanitized.antworten);
}
return sanitized;
}
module.exports = { calculateScore, sanitizeQuestion, parseCorrectAnswers, normalizeText };
module.exports = { calculateScore, sanitizeQuestion, parseCorrectAnswers, normalizeText, shuffleArray };