/**
*
* @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);
}
}