WebUntis Name Learning Quiz

Lern-Quiz mit Auswahl- oder Eingabemodus, Fortschritt, Konfetti und Live-Umschaltung. Non-commercial use only. Attribution required.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WebUntis Name Learning Quiz
// @namespace    https://greasyfork.org/en/scripts/556966-webuntis-name-learning-quiz
// @version      2.2
// @description  Lern-Quiz mit Auswahl- oder Eingabemodus, Fortschritt, Konfetti und Live-Umschaltung. Non-commercial use only. Attribution required.
// @match        *.webuntis.com/*
// @grant        none
// @author       Simon Pirker
// @license      CC BY-NC 4.0; https://creativecommons.org/licenses/by-nc/4.0/
// ==/UserScript==

(function () {
  'use strict';
  console.log('[Quiz+] Script gestartet.');

  // === STYLES ===
  const style = document.createElement('style');
  style.textContent = `


    #quizContainer {
  position: fixed;
  bottom: 10%;
  left: 50%;
  transform: translateX(-50%);
  background: white;
  border-radius: 16px;
  padding: 25px 30px;
  box-shadow: 0 6px 16px rgba(0,0,0,0.3);
  z-index: 10000;
  display: none;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 400px;
  max-height: 80vh;
  overflow-y: auto;
}

    #quizContainer img {
      width: 150px;
      height: 150px;
      object-fit: cover;
      border-radius: 12px;
      margin-bottom: 15px;
    }
    .quiz-option {
      background: #f0f0f0;
      border: none;
      border-radius: 8px;
      padding: 10px 20px;
      font-size: 1em;
      margin: 5px;
      cursor: pointer;
      transition: background 0.2s;
      width: 250px;
    }
    .quiz-option:hover { background: #FFD700; }
    #quizFeedback {
      margin-top: 10px;
      font-weight: bold;
      font-size: 1.1em;
      min-height: 1.5em;
    }
    #quizProgress {
      margin-top: 8px;
      font-size: 0.9em;
      color: #333;
    }
    #quizEndButton {
      background: #e74c3c;
      color: white;
      border: none;
      border-radius: 8px;
      padding: 8px 16px;
      margin-top: 15px;
      cursor: pointer;
    }
    #quizEndButton:hover { background: #c0392b; }
    #startQuizButton {
      margin: 10px;
      background: #4CAF50;
      color: white;
      font-size: 1.1em;
      border: none;
      border-radius: 50px;
      padding: 10px 20px;
      cursor: pointer;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
    #startQuizButton:hover { background: #45a049; }
    #quizModeSelect {
      display: flex;
      gap: 15px;
      margin-bottom: 10px;
      justify-content: center;
      align-items: center;
    }
    #quizInput {
      border: 1px solid #ccc;
      border-radius: 6px;
      padding: 8px;
      width: 200px;
      margin: 5px 0;
      font-size: 1em;
    }
  `;
  document.head.appendChild(style);

  // === DATA ===
  let students = [];
  let quizList = [];
  let currentStudent = null;
  let mode = 'multiple'; // 'multiple' or 'input'
    let totalAnswers = 0;
let correctAnswers = 0;

  // === UI CREATION ===
  const quizContainer = document.createElement('div');
  quizContainer.id = 'quizContainer';
  quizContainer.innerHTML = `
    <div id="quizModeSelect">
      <label><input type="radio" name="quizMode" value="multiple" checked> Auswahl</label>
      <label><input type="radio" name="quizMode" value="input"> Eingabe</label>
    </div>
    <img id="quizImage" src="" alt="Student">
    <div id="quizOptions"></div>
    <input id="quizInput" type="text" placeholder="Name eingeben" style="display:none;">
    <button id="quizSubmit" style="display:none;">Überprüfen</button>
    <div id="quizFeedback"></div>
    <div id="quizProgress"></div>
    <button id="quizEndButton">🚪 Lernen beenden</button>
  `;
  document.body.appendChild(quizContainer);

  // === HELPERS ===
  function getStudents() {
    // ✅ Alle Schüler inkludieren (keine Filterung nach Abwesenheit)
    const cards = Array.from(document.querySelectorAll('.studentCard__container'));
    return cards.map(c => ({
      firstName: c.querySelector('.studentCard__firstName')?.innerText.trim() || '',
      lastName: c.querySelector('.studentCard__lastName')?.innerText.trim() || '',
      img: c.querySelector('img')?.src || '',
      id: Math.random().toString(36).substr(2, 9)
    }));
  }

  function pickRandom(arr, n, excludeId) {
    const filtered = arr.filter(s => s.id !== excludeId);
    return filtered.sort(() => Math.random() - 0.5).slice(0, n);
  }

  function levenshtein(a, b) {
    const matrix = Array(a.length + 1).fill(null).map(() => Array(b.length + 1).fill(null));
    for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
    for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
    for (let i = 1; i <= a.length; i++) {
      for (let j = 1; j <= b.length; j++) {
        const cost = a[i - 1] === b[j - 1] ? 0 : 1;
        matrix[i][j] = Math.min(
          matrix[i - 1][j] + 1,
          matrix[i][j - 1] + 1,
          matrix[i - 1][j - 1] + cost
        );
      }
    }
    return matrix[a.length][b.length];
  }

  function isNameMatch(input, student) {
    const clean = s => s.toLowerCase().trim();
    const user = clean(input);
    const fn = clean(student.firstName);
    const ln = clean(student.lastName);
    return (
      levenshtein(user, fn) <= 1 ||
      levenshtein(user, ln) <= 1 ||
      levenshtein(user, `${fn} ${ln}`) <= 1
    );
  }

  function updateProgress() {
    const total = students.length;
    const done = total - quizList.length;
    document.getElementById('quizProgress').textContent = `Fortschritt: ${done} / ${total} richtig`;
  }

  // === QUIZ LOGIC ===
  function showNextQuestion() {
if (quizList.length === 0) {
  quizContainer.style.display = 'none';
  const total = totalAnswers;
  const correct = correctAnswers;
  const wrong = total - correct;

  launchConfetti();
  showStatsOverlay(correct, wrong, total);
  return;
}

    currentStudent = quizList[Math.floor(Math.random() * quizList.length)];
    const wrong = pickRandom(students, 4, currentStudent.id);
    const options = [currentStudent, ...wrong];
for (let i = options.length - 1; i > 0; i--) {
  const j = Math.floor(Math.random() * (i + 1));
  [options[i], options[j]] = [options[j], options[i]];
}

    const quizImage = document.getElementById('quizImage');
    const optContainer = document.getElementById('quizOptions');
    const inputField = document.getElementById('quizInput');
    const submitBtn = document.getElementById('quizSubmit');
    const feedback = document.getElementById('quizFeedback');

    quizImage.src = currentStudent.img || '';
    feedback.textContent = '';
    optContainer.innerHTML = '';
    inputField.value = '';

    if (mode === 'multiple') {
      optContainer.style.display = 'block';
      inputField.style.display = 'none';
      submitBtn.style.display = 'none';
      options.forEach(opt => {
        const btn = document.createElement('button');
        btn.className = 'quiz-option';
        btn.textContent = `${opt.firstName} ${opt.lastName}`;
        btn.onclick = () => checkAnswer(opt.firstName + ' ' + opt.lastName);
        optContainer.appendChild(btn);
      });
    } else {
      optContainer.style.display = 'none';
      inputField.style.display = 'block';
      submitBtn.style.display = 'block';
      inputField.focus();
      submitBtn.onclick = () => checkAnswer(inputField.value);
    }

    updateProgress();
    quizContainer.style.display = 'flex';
  }

  function checkAnswer(answer) {
    const feedback = document.getElementById('quizFeedback');
    let correct = false;
    if (mode === 'multiple') {
      const fullName = `${currentStudent.firstName} ${currentStudent.lastName}`;
      correct = answer.trim().toLowerCase() === fullName.trim().toLowerCase();
    } else {
      correct = isNameMatch(answer, currentStudent);
    }

    if (correct) {
  feedback.textContent = '✅ Korrekt!';
  feedback.style.color = '#27ae60';
  quizList = quizList.filter(s => s.id !== currentStudent.id);
  correctAnswers++;
} else {
  feedback.textContent = `❌ Richtig ist: ${currentStudent.firstName} ${currentStudent.lastName}`;
  feedback.style.color = '#e74c3c';
}
totalAnswers++;

    setTimeout(showNextQuestion, 1300);
  }

  function endQuiz() {
    quizList = [];
      totalAnswers = 0;
correctAnswers = 0;
    quizContainer.style.display = 'none';

    console.log('[Quiz+] Lernen beendet.');
  }

function launchConfetti() {
  const confetti = document.createElement('canvas');
  confetti.style.position = 'fixed';
  confetti.style.top = '0';
  confetti.style.left = '0';
  confetti.style.width = '100%';
  confetti.style.height = '100%';
  confetti.style.pointerEvents = 'none';
  confetti.style.zIndex = '999999';
  document.body.appendChild(confetti);

  const ctx = confetti.getContext('2d');
  const pieces = Array.from({ length: 200 }, () => ({
    x: Math.random() * window.innerWidth,
    y: Math.random() * window.innerHeight - window.innerHeight,
    size: 4 + Math.random() * 6,
    color: `hsl(${Math.random() * 360},100%,50%)`,
    speed: 2 + Math.random() * 3
  }));

  let duration = 7000; // 7 Sekunden sichtbar
  let start = performance.now();

  function draw(now) {
    const elapsed = now - start;
    ctx.clearRect(0, 0, confetti.width, confetti.height);
    pieces.forEach(p => {
      p.y += p.speed;
      if (p.y > window.innerHeight) p.y = -10;
      ctx.fillStyle = p.color;
      ctx.fillRect(p.x, p.y, p.size, p.size);
    });
    if (elapsed < duration) requestAnimationFrame(draw);
    else confetti.remove();
  }
  requestAnimationFrame(draw);
}

    function showStatsOverlay(correct, wrong, total) {
  const overlay = document.createElement('div');
  overlay.style.position = 'fixed';
  overlay.style.top = '0';
  overlay.style.left = '0';
  overlay.style.width = '100%';
  overlay.style.height = '100%';
  overlay.style.background = 'rgba(0,0,0,0.75)';
  overlay.style.display = 'flex';
  overlay.style.flexDirection = 'column';
  overlay.style.justifyContent = 'center';
  overlay.style.alignItems = 'center';
  overlay.style.zIndex = '100000';

  const box = document.createElement('div');
  box.style.background = 'white';
  box.style.padding = '20px 30px';
  box.style.borderRadius = '12px';
  box.style.textAlign = 'center';
  box.style.boxShadow = '0 6px 14px rgba(0,0,0,0.3)';
  box.innerHTML = `<h2>📊 Lernstatistik</h2><canvas id="statsChart" width="200" height="200"></canvas>
    <p>Gesamt: <b>${total}</b><br>
    ✅ Richtig: <b>${correct}</b><br>
    ❌ Falsch: <b>${wrong}</b></p>`;

  const btn = document.createElement('button');
  btn.textContent = 'Schließen';
  btn.style.marginTop = '15px';
  btn.style.padding = '8px 18px';
  btn.style.border = 'none';
  btn.style.borderRadius = '8px';
  btn.style.background = '#3498db';
  btn.style.color = 'white';
  btn.style.cursor = 'pointer';
  btn.onclick = () => overlay.remove();

  box.appendChild(btn);
  overlay.appendChild(box);
  document.body.appendChild(overlay);

  // Kreisdiagramm zeichnen
  const ctx = document.getElementById('statsChart').getContext('2d');
  const correctAngle = (correct / total) * 2 * Math.PI;
  const wrongAngle = (wrong / total) * 2 * Math.PI;

  ctx.clearRect(0, 0, 200, 200);
  // richtig (grün)
  ctx.beginPath();
  ctx.moveTo(100, 100);
  ctx.arc(100, 100, 90, 0, correctAngle);
  ctx.fillStyle = '#27ae60';
  ctx.fill();

  // falsch (rot)
  ctx.beginPath();
  ctx.moveTo(100, 100);
  ctx.arc(100, 100, 90, correctAngle, correctAngle + wrongAngle);
  ctx.fillStyle = '#e74c3c';
  ctx.fill();

  // Kreisrand
  ctx.beginPath();
  ctx.arc(100, 100, 90, 0, 2 * Math.PI);
  ctx.strokeStyle = '#444';
  ctx.lineWidth = 2;
  ctx.stroke();
}

  // === ADD START BUTTON ===
  function addStartButton(container) {
    if (document.getElementById('startQuizButton')) return;
    const btn = document.createElement('button');
    btn.id = 'startQuizButton';
    btn.textContent = '🧠 Lern-Quiz starten';
    container.appendChild(btn);

    btn.addEventListener('click', () => {
      students = getStudents();
      if (students.length === 0) {
        alert('Keine Schüler gefunden!');
        return;
      }
      quizList = [...students];
      console.log(`[Quiz+] Starte mit ${quizList.length} Schülern.`);
      showNextQuestion();
    });
  }

  // === EVENT LISTENERS ===
  document.getElementById('quizEndButton').addEventListener('click', endQuiz);

  // Radiobuttons → sofortiger Moduswechsel
  document.querySelectorAll('input[name="quizMode"]').forEach(radio => {
    radio.addEventListener('change', e => {
      mode = e.target.value;
      console.log('[Quiz+] Modus gewechselt:', mode);
      showNextQuestion(); // 🔁 sofort aktualisieren
    });
  });

  // "Enter" löst Überprüfung aus
  document.getElementById('quizInput').addEventListener('keydown', e => {
    if (e.key === 'Enter') {
      e.preventDefault();
      document.getElementById('quizSubmit').click();
    }
  });

  // === OBSERVER ===
  const observer = new MutationObserver(() => {
    const container = document.getElementById('classregPageForm.studentWidgets');
    if (container) addStartButton(container);
  });
  observer.observe(document.body, { childList: true, subtree: true });
})();