/** * * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2025-2026 Florian Fischer * * * The JavaScript code in this page is free software: you can * redistribute it and/or modify it under the terms of the GNU * General Public License (GNU GPL) as published by the Free Software * Foundation, either version 3 of the License, or (at your option) * any later version. The code is distributed WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. * * As additional permission under GNU GPL version 3 section 7, you * may distribute non-source (e.g., minimized or compacted) forms of * that code without the copy of the GNU GPL normally required by * section 4, provided you include this license notice and a URL * through which recipients can access the Corresponding Source. * * @licend The above is the entire license notice * for the JavaScript code in this page. * */ // Observer der auf 'resize' events unseres Kuchens reagiert und den zweiten // Kuchen und die Streak-Anzeige gegebenfalls anpasst. let kuchenResizeObserver; // Berechne die Streaks, wenn die Seite komplett geladen wurde. window.onload = function() { berechneStreaks(); const params = new URLSearchParams(location.search); // Ist in der URL die Klasse per Search-Parameter ?klasse=... spezifiziert? const klasse = params.get('klasse'); if (klasse) { wechselKlasse(klasse); } } var streaks; // Berechne den Streak für alle Klassen. // Die globale Variable daten.streakEreignisse muss definiert sein und ein Iterable // aller Ereignisse, die für den Streak relevant sind, enthalten. // Ereignisse ohne positives Delta beenden einen Streak. function berechneStreaks() { // Es gibt keine Streak-Ereignisse if (!daten.streakEreignisse) { return; } // Variable um den laufenden Streak per Klasse zu bestimmen streaks = Object.fromEntries(daten.klassen.map(klasse => [klasse.bezeichner, 0])); for (let ereignis of daten.streakEreignisse) { const klasse = `${ereignis.Jahrgang}${ereignis.Bezeichner}`; // Das Streak Ereignis bringt keine Punkte und unterbricht damit den Streak. if (ereignis.Delta == 0) { streaks[klasse] = 0; // Erhöhe den Streak. } else { streaks[klasse] = streaks[klasse] + 1; } } } // Wechsel zur Ansicht der Klasse. function wechselKlasse(klasse) { zeichne(klasse); zeichneStreak(klasse); listeStreakEreignisse(klasse); } // Zeichne die Kuchen Graphik für die aktuelle Klasse. function zeichne(klasse) { // Wir haben keine Daten zum Zeichnen if (!streaks) { return; } const kuchenNode = document.getElementById("kuchen"); // Entferne alle bestehenden Kind-Objekte vom Kuchen-Element. while (kuchenNode.firstChild) { kuchenNode.removeChild(kuchenNode.firstChild); } const kuchenHintergrund = document.createElement("IMG"); kuchenHintergrund.src = "img/kuchen.png" kuchenHintergrund.classList.add("kuchen-hintergrund"); // Füge das Hintergrundbild dem DOM hinzu kuchenNode.appendChild(kuchenHintergrund); const kuchenVordergrund = document.createElement("IMG"); kuchenVordergrund.src = "img/kuchen.png" kuchenVordergrund.classList.add("kuchen-vordergrund"); // Passe die Größe des Kuchens anhand des Streaks an const streak = streaks[klasse]; const proportion = streak/streak_anzahl * 100; if (proportion == 100) { zeichneKonfetti(); } else { versteckeKonfetti(); } kuchenVordergrund.style.maxWidth = `${proportion}%`; kuchenVordergrund.style.maxHeight = `${proportion}%`; kuchenNode.appendChild(kuchenVordergrund); // Erzeuge ein Observer der auf Größenveränderungen des Kuchens reagiert. let streakNode = document.getElementById("streak"); kuchenResizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { // Positioniere den kleinen Kuchen und die Streak-Anzeige passend zur // echten Anzeigegröße. kuchenVordergrund.style.top = `-${entry.contentRect.height/2 + kuchenVordergrund.offsetHeight/2}px`; kuchenVordergrund.style.left = `${entry.contentRect.width/2 - kuchenVordergrund.offsetWidth/2}px`; streakNode.style.top = `-${kuchenVordergrund.offsetHeight}px` } }); kuchenResizeObserver.observe(kuchenHintergrund); } // Zeichne den Streak für die aktuelle Klasse. function zeichneStreak(klasse) { // Es gibt keine Streak-Ereignisse if (!streaks) { return; } const streak = streaks[klasse]; let streakNode = document.getElementById("streak"); // Schreibe den aktuellen Streak in das Streak span-Element streakNode.innerHTML = `${streak}/${streak_anzahl}`; } // Erzeuge eine Liste aller Ereignisse der Klasse. // Die globale Variable daten.streakEreignisse muss definiert sein und ein Iterable aller // Ereignisse, die angezeigt werden sollen, enthalten. function listeStreakEreignisse(klasse) { let liste = document.getElementById("ereignisse"); while (liste.firstChild) { liste.removeChild(liste.firstChild); } // Erzeuge ein Mapping von Klassenbezeichner auf die entsprechende Klasse let klassen = {}; daten.klassen.forEach(klasse => { klassen[klasse.bezeichner] = klasse }); for (let ereignis of daten.streakEreignisse) { let klassenBezeichner = `${ereignis.Jahrgang}${ereignis.Bezeichner}` // Das Ereignis ist nicht für diese Klasse if (klassenBezeichner !== klasse) { continue; } // Erzeuge ein Listenelement, das alle Infos des Ereignisses enthält let ereignisNode = document.createElement("li"); ereignisNode.classList.add("ereignis"); // Setze den Hintergrund des Ereignisses auf 40% der Klassenfarbe const farbe = klassen[klassenBezeichner].farbe; if (klasse) { ereignisNode.style.backgroundColor = `${farbe}40`; } // Span, das die Meta-Informationen (Klasse, Datum) eines Ereignisses enthält let ereignisUrsache = document.createElement("span"); ereignisUrsache.innerHTML = `${klassenBezeichner}` if (ereignis.Datum) { ereignisUrsache.innerHTML += ` ${ereignis.Datum}` } ereignisUrsache.classList.add("ereignis-klasse"); ereignisNode.appendChild(ereignisUrsache); // Die Veränderung des Punktestands durch das Ereignis let ereignisDelta = document.createElement("span"); ereignisDelta.innerHTML = `${ereignis.Delta}`; ereignisDelta.classList.add("ereignis-delta"); // Ergänze positive und negative CSS Klassen if (ereignis.Delta > 0) { ereignisDelta.classList.add("positiv"); } else { ereignisDelta.classList.add("negativ"); } ereignisNode.appendChild(ereignisDelta); // Die Beschreibung des Ereignisses let ereignisBeschreibung = document.createElement("span"); ereignisBeschreibung.innerHTML = `${ereignis.Beschreibung}`; ereignisBeschreibung.classList.add("ereignis-beschreibung"); ereignisNode.appendChild(ereignisBeschreibung); // Füge das neue Ereignis-Listenelement der Ereignis-Liste hinzu. // Jedes neue Element wird als erstes Kind eingefügt, damit // neuere Ereignisse weiter oben in der Liste stehen liste.insertBefore(ereignisNode, liste.firstChild); } } function getRandomColor() { const colors = ['#ff6347', '#ffa500', '#32cd32', '#1e90ff', '#ff69b4']; return colors[Math.floor(Math.random() * colors.length)]; } function zeichneKonfetti() { const konfettiWrapper = document.querySelector('.konfetti-wrapper'); // Erzeuge konfetti for (let i = 0; i < 50; i++) { const konfetti = document.createElement('div'); konfetti.classList.add('konfetti-stueck'); konfetti.style.left = `${Math.random() * 100}%`; konfetti.style.setProperty('--fall-dauer', `${Math.random() * 3 + 3}s`); konfetti.style.setProperty('--konfetti-farbe', getRandomColor()); konfettiWrapper.appendChild(konfetti); } } function versteckeKonfetti() { const konfettiWrapper = document.querySelector('.konfetti-wrapper'); while (konfettiWrapper.firstChild) { konfettiWrapper.removeChild(konfettiWrapper.firstChild); } }