aboutsummaryrefslogtreecommitdiff
path: root/kuchenstand.js
blob: 97c795edd4a87cadf4aafd44e8bb1fd766d96093 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/**
 *
 * @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 = `<b>${klassenBezeichner}</b>`
    if (ereignis.Datum) {
      ereignisUrsache.innerHTML += ` <small>${ereignis.Datum}</small>`
    }
    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 = `<i>${ereignis.Beschreibung}</i>`;
    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);
  }
}