WebUntis Random Student Picker (Wheel Animation)

Zufällige Schülerauswahl mit realistischem Glücksrad (schnell → langsam). Überspringt abwesende Schüler. Non-commercial use only. Attribution required.

当前为 2025-11-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         WebUntis Random Student Picker (Wheel Animation)
// @namespace    https://greasyfork.org/en/scripts/556970-webuntis-random-student-picker-wheel-animation
// @version      1.4
// @description  Zufällige Schülerauswahl mit realistischem Glücksrad (schnell → langsam). Überspringt abwesende Schüler. Non-commercial use only. Attribution required.
// @match        https://neilo.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';

  // --- Styles ---
  const style = document.createElement('style');
  style.textContent = `
    .studentCard__container.highlighted {
      outline: 4px solid #FFD700;
      transform: scale(1.1);
      transition: transform 0.1s, outline 0.1s;
      z-index: 1000;
    }
    .winner-overlay {
      position: fixed;
      top: 0; left: 0;
      width: 100%; height: 100%;
      background: rgba(0,0,0,0.85);
      color: white;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      z-index: 9999;
      font-size: 1.8em;
    }
    .winner-overlay img {
      border-radius: 8px;
      width: 120px;
      height: auto;
      margin: 20px;
    }
    .winner-overlay button {
      background: #FFD700;
      color: black;
      border: none;
      border-radius: 8px;
      padding: 10px 20px;
      font-size: 1em;
      cursor: pointer;
      margin-top: 20px;
    }
    #winnerButton {
      margin: 10px;
      background: #FFD700;
      color: black;
      font-size: 1.2em;
      border: none;
      border-radius: 40px;
      padding: 10px 25px;
      cursor: pointer;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
  `;
  document.head.appendChild(style);

  // --- Add button to panel ---
  function addButton(container) {
    if (document.getElementById('winnerButton')) return;

    const button = document.createElement('button');
    button.id = 'winnerButton';
    button.innerHTML = '🎲 The winner is';
    container.appendChild(button);

    // --- PICKER LOGIC ---
    button.addEventListener('click', async () => {

      const students = Array.from(document.querySelectorAll('.studentCard__container'))
        .filter(s => !s.classList.contains('CRSWAbsent'));

      if (students.length === 0) {
        alert('Keine anwesenden Schüler gefunden!');
        return;
      }

      // Remove all highlights
      students.forEach(s => s.classList.remove('highlighted'));

      const total = students.length;

      // --- REAL WHEEL: acceleration → full speed → easing slowdown ---
      const totalSpins = total * 4 + Math.floor(Math.random() * total); // always fair
      let currentIndex = 0;

      // Easing curve duration
      const duration = 2500; // 2.5 seconds
      const startTime = performance.now();

      async function animate(now) {
          const elapsed = now - startTime;
          const t = Math.min(elapsed / duration, 1);

          // Quadratic ease-out: fast → slow
          const eased = t * (2 - t);

          // Calculate progress within total spins
          let progress = eased * totalSpins;

          // Make sure index never escapes array bounds
          const safeIndex = Math.floor(progress) % total;

          // Update highlight safely
          if (safeIndex !== currentIndex) {
              students.forEach(s => s.classList.remove('highlighted'));
              const elem = students[safeIndex];
              if (elem) elem.classList.add('highlighted');
              currentIndex = safeIndex;
          }

          if (t < 1) {
              requestAnimationFrame(animate);
          } else {
              finish();
          }
      }


      requestAnimationFrame(animate);

      function finish() {
        const winner = students[currentIndex];
        winner.classList.add('highlighted');

        const firstName = winner.querySelector('.studentCard__firstName')?.innerText || '';
        const lastName = winner.querySelector('.studentCard__lastName')?.innerText || '';
        const img = winner.querySelector('img')?.src || '';

        const overlay = document.createElement('div');
        overlay.className = 'winner-overlay';
        overlay.innerHTML = `
          <div>🎉 The winner is:</div>
          ${img ? `<img src="${img}">` : ''}
          <strong>${firstName} ${lastName}</strong>
          <button id="closeWinner">OK</button>
        `;
        document.body.appendChild(overlay);

        document.getElementById('closeWinner').addEventListener('click', () => {
          overlay.remove();
          students.forEach(s => s.classList.remove('highlighted'));
        });
      }
    });
  }

  // --- Observe DOM until the studentWidgets container appears ---
  const observer = new MutationObserver(() => {
    const container = document.getElementById('classregPageForm.studentWidgets');
    if (container) addButton(container);
  });

  observer.observe(document.body, { childList: true, subtree: true });
})();